@ferris1225/pi-subagents 1.0.0 → 1.0.1
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 +18 -14
- package/agents/reviewer.md +2 -0
- package/package.json +1 -1
- package/src/agents.ts +2 -7
- package/src/announcements.ts +4 -1
- package/src/completion.ts +7 -36
- package/src/config.ts +327 -364
- package/src/dispatch.ts +47 -198
- package/src/fixloop.ts +0 -16
- package/src/format.ts +2 -7
- package/src/index.ts +5 -2
- package/src/models.ts +0 -10
- package/src/monitor.ts +46 -98
- package/src/rpc-run.ts +1 -15
- package/src/runtime.ts +3 -15
- package/src/session-fork.ts +0 -4
- package/src/spawn.ts +542 -557
- package/src/tools.ts +5 -29
- package/src/ui.ts +3 -7
- package/src/widget.ts +107 -0
- package/src/worktree.ts +1 -1
- package/src/trajectory.ts +0 -312
package/src/dispatch.ts
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
buildReReviewBrief,
|
|
36
36
|
formatChainSummary,
|
|
37
37
|
shouldTriggerFixLoop,
|
|
38
|
-
summarizeChainResult,
|
|
39
38
|
type ChainStep,
|
|
40
39
|
} from "./fixloop.ts";
|
|
41
40
|
import { currentModelRef, resolveAgentModelPool } from "./models.ts";
|
|
@@ -62,7 +61,6 @@ import {
|
|
|
62
61
|
type SubagentDetails,
|
|
63
62
|
type SubagentLiveEvent,
|
|
64
63
|
} from "./spawn.ts";
|
|
65
|
-
import { trajectoryStore, summarizeToolArgs } from "./trajectory.ts";
|
|
66
64
|
import {
|
|
67
65
|
createWorktreeIsolation,
|
|
68
66
|
resolveWorktreeTarget,
|
|
@@ -74,13 +72,9 @@ import {
|
|
|
74
72
|
const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
|
|
75
73
|
export const FORK_CONTINUATION_PROMPT =
|
|
76
74
|
"Continue from the retained context above. Review the prior work, then take the most useful next step toward completing the existing objective without repeating completed work.";
|
|
77
|
-
|
|
75
|
+
const WORKTREE_ISOLATION_INSTRUCTIONS =
|
|
78
76
|
"You are running in a temporary detached Git worktree. Work only in the current cwd; do not create another worktree or manually copy/apply changes to the original checkout. The parent dispatcher will integrate your tracked, deleted, and untracked changes when this thread finally settles.";
|
|
79
77
|
|
|
80
|
-
export function buildWorktreeTaskPrompt(task: string): string {
|
|
81
|
-
return `${WORKTREE_ISOLATION_INSTRUCTIONS}\n\nTask: ${task}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
78
|
function withWorktreeSystemPrompt(agent: AgentConfig): AgentConfig {
|
|
85
79
|
return {
|
|
86
80
|
...agent,
|
|
@@ -231,7 +225,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
231
225
|
],
|
|
232
226
|
parameters: SubagentParams,
|
|
233
227
|
|
|
234
|
-
async execute(_toolCallId, params,
|
|
228
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
235
229
|
monitor.beginTurn();
|
|
236
230
|
const config = await loadConfig(runtime.configPath);
|
|
237
231
|
// Pick up concurrency changes from /subagents-setup without a restart.
|
|
@@ -242,29 +236,25 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
242
236
|
const finishRun = (
|
|
243
237
|
runId: number,
|
|
244
238
|
status: "done" | "failed",
|
|
245
|
-
opts?: { silent?: boolean
|
|
239
|
+
opts?: { silent?: boolean },
|
|
246
240
|
): void => {
|
|
247
241
|
monitor.setStatus(runId, status); // stamps endedAt for the elapsed time
|
|
248
|
-
const run =
|
|
242
|
+
const run = monitor.removeRun(runId);
|
|
249
243
|
if (!run) return; // already finished — stay idempotent
|
|
250
|
-
if (opts?.retain) monitor.setRetained(runId, true);
|
|
251
244
|
if (opts?.silent || !runtime.sessionActive) return;
|
|
252
245
|
const icon = status === "done" ? "✓" : "✗";
|
|
253
246
|
ctx.ui.notify(`${icon} ${monitor.summarize(run)}`, status === "done" ? "info" : "error");
|
|
254
247
|
};
|
|
255
248
|
|
|
256
249
|
// Live sub-agent activity → concise one-line status ("thinking",
|
|
257
|
-
// "read src/index.ts", ...), never a raw args blob.
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
// args summary). The live handler only updates monitor state; finishing
|
|
261
|
-
// (removeRun + notify) is
|
|
262
|
-
// owned by the queue task / launchInLoop. That keeps a startup retry —
|
|
250
|
+
// "read src/index.ts", ...), never a raw args blob. The live handler
|
|
251
|
+
// only updates monitor state; finishing (removeRun + notify) is owned
|
|
252
|
+
// by the queue task / launchInLoop. That keeps a startup retry —
|
|
263
253
|
// which fires a transient "failed" status before relaunching — from
|
|
264
254
|
// ripping the row out early, and lets the queue task decide between
|
|
265
255
|
// delivering a reviewer's result and starting an auto-fix chain.
|
|
266
256
|
const makeLiveHandler =
|
|
267
|
-
(runId: number,
|
|
257
|
+
(runId: number, generation?: number) =>
|
|
268
258
|
(e: SubagentLiveEvent): void => {
|
|
269
259
|
if (generation !== undefined && runtime.threads.get(runId)?.generation !== generation) return;
|
|
270
260
|
switch (e.kind) {
|
|
@@ -295,31 +285,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
295
285
|
monitor.setActivity(runId, "responding");
|
|
296
286
|
break;
|
|
297
287
|
}
|
|
298
|
-
if (threadId !== undefined) {
|
|
299
|
-
const trajectory = trajectoryStore.get(threadId).trajectory;
|
|
300
|
-
switch (e.kind) {
|
|
301
|
-
case "status":
|
|
302
|
-
trajectory.append({ kind: "status", status: e.status });
|
|
303
|
-
break;
|
|
304
|
-
case "model":
|
|
305
|
-
trajectory.append({ kind: "candidate", model: e.model, fallbackFrom: e.fallbackFrom });
|
|
306
|
-
break;
|
|
307
|
-
case "usage":
|
|
308
|
-
trajectory.append({ kind: "usage", usage: { ...e.usage }, model: e.model });
|
|
309
|
-
break;
|
|
310
|
-
case "tool_start":
|
|
311
|
-
trajectory.append({
|
|
312
|
-
kind: "tool_start",
|
|
313
|
-
tool: e.toolName,
|
|
314
|
-
toolCallId: e.toolCallId,
|
|
315
|
-
summary: summarizeToolArgs(e.args),
|
|
316
|
-
});
|
|
317
|
-
break;
|
|
318
|
-
case "tool_end":
|
|
319
|
-
trajectory.append({ kind: "tool_end", tool: e.toolName, toolCallId: e.toolCallId, isError: e.isError });
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
288
|
};
|
|
324
289
|
|
|
325
290
|
const discovery = discoverAgents(ctx.cwd, {
|
|
@@ -396,21 +361,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
396
361
|
const pool = resolveDispatchModelPool(agent, config, sessionRef, vision);
|
|
397
362
|
const thinkingLevel = config.agentThinkingLevels[agent.name] ?? agent.thinking ?? config.thinkingLevel;
|
|
398
363
|
const runId = monitor.addRun(agent.name, task, pool.agent.model, thinkingLevel, meta);
|
|
399
|
-
|
|
400
|
-
const chainState = trajectoryStore.get(runId);
|
|
401
|
-
chainState.trajectory.append({
|
|
402
|
-
kind: "dispatch",
|
|
403
|
-
agent: agent.name,
|
|
404
|
-
task,
|
|
405
|
-
model: pool.agent.model,
|
|
406
|
-
thinking: thinkingLevel,
|
|
407
|
-
pool: pool.fallbackModelRefs,
|
|
408
|
-
vision,
|
|
409
|
-
isolation: "shared",
|
|
410
|
-
originalCwd: executionCwd,
|
|
411
|
-
isolationCwd: executionCwd,
|
|
412
|
-
});
|
|
413
|
-
const onLive = makeLiveHandler(runId, runId);
|
|
364
|
+
const onLive = makeLiveHandler(runId);
|
|
414
365
|
try {
|
|
415
366
|
const result = await runSingleAgentWithModelFallback(
|
|
416
367
|
{
|
|
@@ -429,32 +380,20 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
429
380
|
);
|
|
430
381
|
result.runId = runId;
|
|
431
382
|
result.isolation = "shared";
|
|
432
|
-
result.originalCwd = executionCwd;
|
|
433
|
-
result.isolationCwd = executionCwd;
|
|
434
383
|
runtime.retainSession(result);
|
|
435
384
|
monitor.setModel(runId, result.model, result.modelFallbackFrom);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
model: result.model,
|
|
440
|
-
});
|
|
441
|
-
// Keep the finished round in status state while the chain is
|
|
442
|
-
// still running, with a one-line summary of what it did; the whole
|
|
443
|
-
// group is dropped when the chain resolves (see removeChainGroup).
|
|
444
|
-
monitor.setSummary(runId, summarizeChainResult(result));
|
|
445
|
-
finishRun(runId, isFailedResult(result) ? "failed" : "done", { retain: true });
|
|
385
|
+
// The parent row represents the chain. Internal rounds leave live
|
|
386
|
+
// status as soon as they settle; their reports remain addressable by id.
|
|
387
|
+
finishRun(runId, isFailedResult(result) ? "failed" : "done", { silent: true });
|
|
446
388
|
runtime.registerRunResult(runId, result);
|
|
447
389
|
return { runId, result };
|
|
448
390
|
} catch (error) {
|
|
449
|
-
finishRun(runId, "failed", {
|
|
450
|
-
chainState.trajectory.append({ kind: "settled", status: "failed", model: pool.agent.model });
|
|
391
|
+
finishRun(runId, "failed", { silent: true });
|
|
451
392
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
452
393
|
const crashed: SingleResult = {
|
|
453
394
|
...queuedResult(pool.agent, task, thinkingLevel),
|
|
454
395
|
runId,
|
|
455
396
|
isolation: "shared",
|
|
456
|
-
originalCwd: executionCwd,
|
|
457
|
-
isolationCwd: executionCwd,
|
|
458
397
|
exitCode: 1,
|
|
459
398
|
stderr: errorMessage,
|
|
460
399
|
stopReason: signal.aborted ? "aborted" : "error",
|
|
@@ -473,7 +412,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
473
412
|
* Failures short-circuit: a crashed worker skips its re-review and delivers.
|
|
474
413
|
* The triggering reviewer stays in monitor state until the chain resolves.
|
|
475
414
|
*/
|
|
476
|
-
/** Drop
|
|
415
|
+
/** Drop any in-flight monitor row belonging to an auto-fix chain; the
|
|
477
416
|
* parent is removed separately (it does not carry the groupId). */
|
|
478
417
|
const removeChainGroup = (groupId: string): void => {
|
|
479
418
|
for (const run of [...monitor.getRuns()]) {
|
|
@@ -514,10 +453,20 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
514
453
|
};
|
|
515
454
|
fixController = runtime.backgroundQueue.enqueue(
|
|
516
455
|
serializeAutoFixChain(executionCwd, async (signal) => {
|
|
456
|
+
if (!ownsParent()) return;
|
|
457
|
+
// The parent id belongs to the stable logical thread and will point at
|
|
458
|
+
// the chain outcome. Archive the triggering review under its own id so
|
|
459
|
+
// every id advertised by the chain summary resolves to that exact step.
|
|
460
|
+
const initialStepRunId = monitor.reserveRunId();
|
|
461
|
+
const initialStepResult: SingleResult = {
|
|
462
|
+
...initialReviewerResult,
|
|
463
|
+
runId: initialStepRunId,
|
|
464
|
+
};
|
|
465
|
+
runtime.registerRunResult(initialStepRunId, initialStepResult);
|
|
517
466
|
const chain: ChainStep[] = [
|
|
518
|
-
{ runId:
|
|
467
|
+
{ runId: initialStepRunId, result: initialStepResult, relation: "initial review" },
|
|
519
468
|
];
|
|
520
|
-
let lastReviewer =
|
|
469
|
+
let lastReviewer = initialStepResult;
|
|
521
470
|
for (let round = 1; round <= config.maxFixRounds; round++) {
|
|
522
471
|
if (!runtime.sessionActive) break;
|
|
523
472
|
const fixBrief = buildFixTaskBrief(lastReviewer, round, config.maxFixRounds);
|
|
@@ -579,37 +528,34 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
579
528
|
return;
|
|
580
529
|
}
|
|
581
530
|
// Parking an auto-fix chain aborts its in-flight child but preserves the
|
|
582
|
-
// parent's
|
|
531
|
+
// parent's checkpoint and suppresses an aborted chain delivery.
|
|
583
532
|
if (controlledParent.state === "parked") {
|
|
584
533
|
clearOwnedController();
|
|
585
534
|
removeChainGroup(parentGroupId);
|
|
586
|
-
monitor.setRetained(parentRunId, false);
|
|
587
535
|
monitor.setStatus(parentRunId, "parked");
|
|
588
536
|
return;
|
|
589
537
|
}
|
|
590
|
-
// The chain is done (success, exhaustion, or abort): drop
|
|
591
|
-
//
|
|
538
|
+
// The chain is done (success, exhaustion, or abort): drop its monitor
|
|
539
|
+
// rows, then deliver one condensed
|
|
592
540
|
// summary. Register the parent's final state (the last chain result)
|
|
593
|
-
// before removal so subagent_wait can resolve it.
|
|
541
|
+
// before removal so subagent_wait can resolve it. Clone instead of
|
|
542
|
+
// mutating: the internal step remains addressable under its own run id.
|
|
594
543
|
const last = chain[chain.length - 1];
|
|
595
|
-
|
|
544
|
+
const parentResult: SingleResult = {
|
|
545
|
+
...last.result,
|
|
546
|
+
runId: parentRunId,
|
|
547
|
+
};
|
|
548
|
+
runtime.registerRunResult(parentRunId, parentResult);
|
|
596
549
|
removeChainGroup(parentGroupId);
|
|
597
550
|
monitor.removeRun(parentRunId);
|
|
598
|
-
runtime.retainSession(
|
|
551
|
+
runtime.retainSession(parentResult);
|
|
599
552
|
const parentThread = parentThreadAtStart;
|
|
553
|
+
parentThread.lastResult = parentResult;
|
|
600
554
|
parentThread.agentName = last.result.agent;
|
|
601
555
|
parentThread.task = last.result.task;
|
|
602
556
|
parentThread.sessionId = last.result.sessionId;
|
|
603
557
|
parentThread.sessionDir = last.result.sessionDir;
|
|
604
558
|
parentThread.state = isFailedResult(last.result) ? "failed" : "completed";
|
|
605
|
-
// The chain outcome settles the parent thread's trajectory: the
|
|
606
|
-
// last chain step is its final state.
|
|
607
|
-
const parentTrajectory = trajectoryStore.get(parentRunId);
|
|
608
|
-
parentTrajectory.trajectory.append({
|
|
609
|
-
kind: "settled",
|
|
610
|
-
status: parentThread.state === "failed" ? "failed" : "done",
|
|
611
|
-
model: last.result.model,
|
|
612
|
-
});
|
|
613
559
|
if (!runtime.sessionActive) {
|
|
614
560
|
clearOwnedController();
|
|
615
561
|
return;
|
|
@@ -642,7 +588,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
642
588
|
clearOwnedController();
|
|
643
589
|
removeChainGroup(parentGroupId);
|
|
644
590
|
if (controlledParent.state === "parked") {
|
|
645
|
-
monitor.setRetained(parentRunId, false);
|
|
646
591
|
monitor.setStatus(parentRunId, "parked");
|
|
647
592
|
return;
|
|
648
593
|
}
|
|
@@ -776,7 +721,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
776
721
|
return {
|
|
777
722
|
...failedStartResult(agentName, task, `Run #${existingThread?.id ?? "?"} has no active continuation worktree.`),
|
|
778
723
|
isolation,
|
|
779
|
-
originalCwd,
|
|
780
724
|
integrationStatus: worktree.state === "finalizing" ? "pending" : worktree.state,
|
|
781
725
|
};
|
|
782
726
|
}
|
|
@@ -787,7 +731,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
787
731
|
return {
|
|
788
732
|
...failedStartResult(agentName, task, error instanceof Error ? error.message : String(error)),
|
|
789
733
|
isolation,
|
|
790
|
-
originalCwd,
|
|
791
734
|
};
|
|
792
735
|
}
|
|
793
736
|
}
|
|
@@ -824,11 +767,9 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
824
767
|
...queuedResult(pool.agent, task, thinkingLevel),
|
|
825
768
|
runId,
|
|
826
769
|
isolation,
|
|
827
|
-
originalCwd,
|
|
828
|
-
isolationCwd: executionCwd,
|
|
829
770
|
...(isolation === "worktree" ? { integrationStatus: "pending" as const } : {}),
|
|
830
771
|
...(seed?.sessionId && seed.sessionDir
|
|
831
|
-
? { sessionId: seed.sessionId, sessionDir: seed.sessionDir
|
|
772
|
+
? { sessionId: seed.sessionId, sessionDir: seed.sessionDir }
|
|
832
773
|
: {}),
|
|
833
774
|
...(seed?.forkedFromRunId !== undefined ? { forkedFromRunId: seed.forkedFromRunId } : {}),
|
|
834
775
|
};
|
|
@@ -840,12 +781,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
840
781
|
let thread!: SubagentThread;
|
|
841
782
|
const control = new RpcRunControl(task, generation, (phase) => {
|
|
842
783
|
if (runtime.threads.get(runId)?.generation !== generation || phase === "settled") return;
|
|
843
|
-
// Orchestration transitions are part of the trajectory (retrying →
|
|
844
|
-
// retry event, park/stop → terminal control events).
|
|
845
|
-
const trajectory = trajectoryStore.get(runId).trajectory;
|
|
846
|
-
if (phase === "retrying") trajectory.append({ kind: "retry", reason: "retrying" });
|
|
847
|
-
else if (phase === "parked") trajectory.append({ kind: "park" });
|
|
848
|
-
else if (phase === "stopped") trajectory.append({ kind: "stop", reason: control.getStopMessage() });
|
|
849
784
|
const state: ThreadState =
|
|
850
785
|
phase === "queued" || phase === "starting"
|
|
851
786
|
? "queued"
|
|
@@ -866,45 +801,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
866
801
|
else if (state === "running") monitor.setStatus(runId, "running");
|
|
867
802
|
});
|
|
868
803
|
|
|
869
|
-
// Restart bumps the generation while preserving append-only history.
|
|
870
|
-
const trajectoryState = trajectoryStore.get(runId);
|
|
871
|
-
if (existingThread) {
|
|
872
|
-
trajectoryState.trajectory.restart();
|
|
873
|
-
trajectoryState.trajectory.append({
|
|
874
|
-
kind: "resume",
|
|
875
|
-
objective: newObjectiveOnResume ? task : undefined,
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
if (seed?.forkedFromRunId !== undefined) {
|
|
879
|
-
trajectoryState.trajectory.append({
|
|
880
|
-
kind: "fork",
|
|
881
|
-
sourceRunId: seed.forkedFromRunId,
|
|
882
|
-
childRunId: runId,
|
|
883
|
-
objective: seed.forkObjective,
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
trajectoryState.trajectory.append({
|
|
887
|
-
kind: "dispatch",
|
|
888
|
-
agent: agent.name,
|
|
889
|
-
task,
|
|
890
|
-
model: pool.agent.model,
|
|
891
|
-
thinking: thinkingLevel,
|
|
892
|
-
pool: pool.fallbackModelRefs,
|
|
893
|
-
vision,
|
|
894
|
-
resumed: existingThread !== undefined || seed !== undefined,
|
|
895
|
-
isolation,
|
|
896
|
-
originalCwd,
|
|
897
|
-
isolationCwd: executionCwd,
|
|
898
|
-
});
|
|
899
|
-
if (worktree && worktree !== previousWorktree) {
|
|
900
|
-
trajectoryState.trajectory.append({
|
|
901
|
-
kind: "worktree",
|
|
902
|
-
status: "created",
|
|
903
|
-
originalCwd,
|
|
904
|
-
isolationCwd: executionCwd,
|
|
905
|
-
worktreePath: worktree.worktreePath,
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
804
|
|
|
909
805
|
if (existingThread) {
|
|
910
806
|
thread = existingThread;
|
|
@@ -975,21 +871,9 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
975
871
|
if (thread.generation !== expectedGeneration) return undefined;
|
|
976
872
|
const finalization = await thread.worktree.finalize();
|
|
977
873
|
monitor.setIsolation(runId, "worktree", finalization.status);
|
|
978
|
-
trajectoryState.trajectory.append({
|
|
979
|
-
kind: "worktree",
|
|
980
|
-
status: finalization.status,
|
|
981
|
-
originalCwd: thread.cwd,
|
|
982
|
-
isolationCwd: thread.executionCwd,
|
|
983
|
-
worktreePath: finalization.worktreePath,
|
|
984
|
-
patchPath: finalization.patchPath,
|
|
985
|
-
integrated: finalization.integrated,
|
|
986
|
-
error: finalization.error,
|
|
987
|
-
});
|
|
988
874
|
if (result) {
|
|
989
875
|
result.runId = runId;
|
|
990
876
|
result.isolation = "worktree";
|
|
991
|
-
result.originalCwd = thread.cwd;
|
|
992
|
-
result.isolationCwd = thread.executionCwd;
|
|
993
877
|
result.integrationStatus = finalization.status;
|
|
994
878
|
result.integrationApplied = finalization.integrated;
|
|
995
879
|
result.integrationError = finalization.error;
|
|
@@ -1014,7 +898,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1014
898
|
}
|
|
1015
899
|
}
|
|
1016
900
|
if (finalization.status === "retained") {
|
|
1017
|
-
runtime.retainWorktreeArtifacts(finalization);
|
|
1018
901
|
if (!thread.isolationFailureNotified) {
|
|
1019
902
|
thread.isolationFailureNotified = true;
|
|
1020
903
|
try {
|
|
@@ -1048,13 +931,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1048
931
|
const discardUnusedWorktree = async (candidate: WorktreeIsolation | undefined): Promise<void> => {
|
|
1049
932
|
if (!candidate) return;
|
|
1050
933
|
try {
|
|
1051
|
-
|
|
1052
|
-
await candidate.discard();
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
// Compatibility for externally supplied/test handles. Production handles
|
|
1056
|
-
// expose discard(), so this fallback never integrates a seeded worktree.
|
|
1057
|
-
if (candidate.state === "active") await candidate.finalize();
|
|
934
|
+
await candidate.discard();
|
|
1058
935
|
} catch (error) {
|
|
1059
936
|
const retainedPath = existsSync(candidate.worktreePath)
|
|
1060
937
|
? candidate.worktreePath
|
|
@@ -1069,7 +946,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1069
946
|
...(existsSync(candidate.patchPath) ? { patchPath: candidate.patchPath } : {}),
|
|
1070
947
|
error: `Discarding unused continuation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1071
948
|
};
|
|
1072
|
-
runtime.retainWorktreeArtifacts(finalization);
|
|
1073
949
|
await persistRecoveryRecords(runtime.configPath, [
|
|
1074
950
|
recoveryRecordFromFinalization(runId, finalization),
|
|
1075
951
|
]).catch(() => undefined);
|
|
@@ -1439,12 +1315,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1439
1315
|
const childThread = runtime.threads.get(childRunId);
|
|
1440
1316
|
if (childThread) childThread.forkedFromRunId = runId;
|
|
1441
1317
|
monitor.setForkRelation(runId, childRunId);
|
|
1442
|
-
trajectoryStore.get(runId).trajectory.append({
|
|
1443
|
-
kind: "fork",
|
|
1444
|
-
sourceRunId: runId,
|
|
1445
|
-
childRunId,
|
|
1446
|
-
objective: forkObjective,
|
|
1447
|
-
});
|
|
1448
1318
|
const sourceResult = runtime.settledRuns.get(runId) ?? thread.lastResult;
|
|
1449
1319
|
if (sourceResult) sourceResult.forkChildRunIds = [...thread.forkChildRunIds];
|
|
1450
1320
|
return child;
|
|
@@ -1469,7 +1339,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1469
1339
|
}
|
|
1470
1340
|
};
|
|
1471
1341
|
|
|
1472
|
-
const onLive = makeLiveHandler(runId,
|
|
1342
|
+
const onLive = makeLiveHandler(runId, generation);
|
|
1473
1343
|
const queueController = runtime.backgroundQueue.enqueue(
|
|
1474
1344
|
async (backgroundSignal) => {
|
|
1475
1345
|
if (runtime.threads.get(runId)?.generation !== generation) return;
|
|
@@ -1518,8 +1388,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1518
1388
|
if (runtime.threads.get(runId)?.generation !== generation) return;
|
|
1519
1389
|
result.runId = runId;
|
|
1520
1390
|
result.isolation = isolation;
|
|
1521
|
-
result.originalCwd = originalCwd;
|
|
1522
|
-
result.isolationCwd = executionCwd;
|
|
1523
1391
|
result.forkedFromRunId = thread.forkedFromRunId;
|
|
1524
1392
|
result.forkChildRunIds = [...thread.forkChildRunIds];
|
|
1525
1393
|
thread.queueController = undefined;
|
|
@@ -1548,8 +1416,10 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1548
1416
|
const wantsFixLoop = shouldTriggerFixLoop(result, runConfig);
|
|
1549
1417
|
if (wantsFixLoop && isolation === "shared" && runtime.sessionActive) {
|
|
1550
1418
|
thread.state = "running";
|
|
1551
|
-
|
|
1552
|
-
|
|
1419
|
+
// The review being done does not mean the logical run is over:
|
|
1420
|
+
// the same row now represents the chain until it resolves.
|
|
1421
|
+
monitor.setStatus(runId, "running");
|
|
1422
|
+
monitor.setActivity(runId, "auto-fix chain running");
|
|
1553
1423
|
startFixLoop(result, `fix-${runId}`, runId, thread.executionCwd, vision);
|
|
1554
1424
|
return;
|
|
1555
1425
|
}
|
|
@@ -1578,15 +1448,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1578
1448
|
// Stamp the terminal monitor state before projecting it. This gives every
|
|
1579
1449
|
// path a fixed endedAt even when the row is removed immediately.
|
|
1580
1450
|
monitor.setStatus(runId, failed ? "failed" : "done");
|
|
1581
|
-
trajectoryState.trajectory.append({
|
|
1582
|
-
kind: "settled",
|
|
1583
|
-
status: failed ? "failed" : "done",
|
|
1584
|
-
model: result.model,
|
|
1585
|
-
isolation,
|
|
1586
|
-
...(result.integrationStatus && result.integrationStatus !== "pending"
|
|
1587
|
-
? { integrationStatus: result.integrationStatus }
|
|
1588
|
-
: {}),
|
|
1589
|
-
});
|
|
1590
1451
|
if (!runtime.sessionActive || !ownsSettlement()) return;
|
|
1591
1452
|
|
|
1592
1453
|
const modelLevel = failed && isModelLevelFailure(result);
|
|
@@ -1618,8 +1479,8 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1618
1479
|
() => {
|
|
1619
1480
|
if (runtime.threads.get(runId)?.generation !== generation) return;
|
|
1620
1481
|
// Queued park/stop owns publication and may still be finalizing an
|
|
1621
|
-
// isolated worktree. Do not expose a terminal monitor
|
|
1622
|
-
//
|
|
1482
|
+
// isolated worktree. Do not expose a terminal monitor state before
|
|
1483
|
+
// that owner records the checkpoint or aborted result.
|
|
1623
1484
|
if (thread.lifecycleOperation === "park" || thread.lifecycleOperation === "stop") return;
|
|
1624
1485
|
runtime.runControllers.delete(runId);
|
|
1625
1486
|
thread.queueController = undefined;
|
|
@@ -1629,7 +1490,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1629
1490
|
}
|
|
1630
1491
|
thread.state = "stopped";
|
|
1631
1492
|
monitor.setStatus(runId, "failed");
|
|
1632
|
-
trajectoryState.trajectory.append({ kind: "settled", status: "stopped", model: monitor.findRun(runId)?.model, isolation });
|
|
1633
1493
|
if (!runtime.sessionActive) {
|
|
1634
1494
|
monitor.removeRun(runId);
|
|
1635
1495
|
return;
|
|
@@ -1655,23 +1515,12 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1655
1515
|
...dispatchFailedResult(pool.agent, control.getObjective(), error, thinkingLevel),
|
|
1656
1516
|
runId,
|
|
1657
1517
|
isolation,
|
|
1658
|
-
originalCwd,
|
|
1659
|
-
isolationCwd: executionCwd,
|
|
1660
1518
|
forkedFromRunId: thread.forkedFromRunId,
|
|
1661
1519
|
};
|
|
1662
1520
|
await thread.finalizeIsolation(generation, crashed);
|
|
1663
1521
|
if (!ownsSettlement()) return;
|
|
1664
1522
|
thread.state = "failed";
|
|
1665
1523
|
monitor.setStatus(runId, "failed");
|
|
1666
|
-
trajectoryState.trajectory.append({
|
|
1667
|
-
kind: "settled",
|
|
1668
|
-
status: "failed",
|
|
1669
|
-
model: crashed.model,
|
|
1670
|
-
isolation,
|
|
1671
|
-
...(crashed.integrationStatus && crashed.integrationStatus !== "pending"
|
|
1672
|
-
? { integrationStatus: crashed.integrationStatus }
|
|
1673
|
-
: {}),
|
|
1674
|
-
});
|
|
1675
1524
|
finishRun(runId, "failed", { silent: true });
|
|
1676
1525
|
runtime.registerRunResult(runId, crashed);
|
|
1677
1526
|
runtime.runControllers.delete(runId);
|
package/src/fixloop.ts
CHANGED
|
@@ -86,22 +86,6 @@ export function chainKeyFragments(result: SingleResult): string[] {
|
|
|
86
86
|
return extractKeyFragments(getResultOutput(result)).slice(0, CHAIN_SUMMARY_FRAGMENTS_MAX);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/**
|
|
90
|
-
* Compact one-line outcome for a finished chain run, retained so
|
|
91
|
-
* each round reads as what it did: a reviewer reports its verdict plus the
|
|
92
|
-
* key fragments of what it found ("fail · src/index.ts · render()"), a worker
|
|
93
|
-
* the fragments of what it changed. Failed runs and runs with nothing
|
|
94
|
-
* distinctive get no summary.
|
|
95
|
-
*/
|
|
96
|
-
export function summarizeChainResult(result: SingleResult): string | undefined {
|
|
97
|
-
if (isFailedResult(result)) return undefined;
|
|
98
|
-
const verdict = result.agent === "reviewer" ? reviewVerdict(getResultOutput(result)) : undefined;
|
|
99
|
-
if (verdict === "pass") return "pass";
|
|
100
|
-
const fragments = chainKeyFragments(result);
|
|
101
|
-
if (verdict === "fail") return fragments.length > 0 ? `fail · ${fragments.join(" · ")}` : "fail";
|
|
102
|
-
return fragments.length > 0 ? fragments.join(" · ") : undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
89
|
/**
|
|
106
90
|
* Condensed, readable summary of a completed auto-fix chain: one line per step
|
|
107
91
|
* (run id, role, verdict / what changed) plus aggregate usage. Full per-step
|
package/src/format.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { AgentConfig } from "./agents.ts";
|
|
8
8
|
import { formatTaskSummary } from "./monitor.ts";
|
|
9
|
+
import { emptyUsage } from "./rpc-run.ts";
|
|
9
10
|
import {
|
|
10
11
|
getResultOutput,
|
|
11
12
|
isFailedResult,
|
|
@@ -15,14 +16,9 @@ import {
|
|
|
15
16
|
type UsageStats,
|
|
16
17
|
} from "./spawn.ts";
|
|
17
18
|
|
|
18
|
-
export function emptyUsage(): UsageStats {
|
|
19
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
19
|
export function queuedResult(agent: AgentConfig, task: string, thinking?: string): SingleResult {
|
|
23
20
|
return {
|
|
24
21
|
agent: agent.name,
|
|
25
|
-
agentSource: agent.source,
|
|
26
22
|
task,
|
|
27
23
|
exitCode: -1,
|
|
28
24
|
messages: [],
|
|
@@ -36,7 +32,6 @@ export function queuedResult(agent: AgentConfig, task: string, thinking?: string
|
|
|
36
32
|
export function failedStartResult(agentName: string, task: string, errorMessage: string): SingleResult {
|
|
37
33
|
return {
|
|
38
34
|
agent: agentName,
|
|
39
|
-
agentSource: "unknown",
|
|
40
35
|
task,
|
|
41
36
|
exitCode: 1,
|
|
42
37
|
messages: [],
|
|
@@ -61,7 +56,7 @@ export function dispatchFailedResult(agent: AgentConfig, task: string, error: un
|
|
|
61
56
|
};
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
function formatTokens(count: number): string {
|
|
65
60
|
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
|
|
66
61
|
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`;
|
|
67
62
|
return String(count);
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* The heavy lifting lives in focused modules:
|
|
6
6
|
* - dispatch.ts — the `subagent` tool (spawn, auto-fix chain, vision model)
|
|
7
7
|
* - tools.ts — subagent_control / subagent_wait / status / stop
|
|
8
|
-
* - announcements.ts — session-start recovery and
|
|
8
|
+
* - announcements.ts — session-start recovery, notices, and widget install
|
|
9
|
+
* - widget.ts — active-only TUI run status
|
|
9
10
|
* - runtime.ts — shared per-session state
|
|
10
11
|
*
|
|
11
12
|
* Also registers the `/subagents-setup` command and a `before_agent_start` hook
|
|
@@ -28,6 +29,7 @@ import { createRuntime } from "./runtime.ts";
|
|
|
28
29
|
import { runSetup } from "./setup.ts";
|
|
29
30
|
import { currentSubagentDepth } from "./spawn.ts";
|
|
30
31
|
import { registerLookupTools } from "./tools.ts";
|
|
32
|
+
import { clearActiveRunsWidget } from "./widget.ts";
|
|
31
33
|
|
|
32
34
|
export { matchRunIds };
|
|
33
35
|
|
|
@@ -57,7 +59,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
57
59
|
),
|
|
58
60
|
);
|
|
59
61
|
|
|
60
|
-
pi.on("session_shutdown", async () => {
|
|
62
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
63
|
+
clearActiveRunsWidget(ctx);
|
|
61
64
|
await runtime.shutdown();
|
|
62
65
|
});
|
|
63
66
|
|
package/src/models.ts
CHANGED
|
@@ -22,8 +22,6 @@ export interface ModelPickerItem {
|
|
|
22
22
|
value: string;
|
|
23
23
|
label: string;
|
|
24
24
|
description?: string;
|
|
25
|
-
/** Visible for diagnosis/search, but cannot be selected. */
|
|
26
|
-
disabled?: boolean;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export type ModelListEntry = Pick<
|
|
@@ -86,14 +84,6 @@ export function availableModelsInScope(ctx: ModelContext): readonly Model<Api>[]
|
|
|
86
84
|
return models.filter((model) => scopedRefs.has(modelRef(model)));
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
/** Model refs usable by setup, with an available current main model first. */
|
|
90
|
-
export function availableModelRefs(ctx: ModelContext): string[] {
|
|
91
|
-
const refs = [...new Set(availableModelsInScope(ctx).map(modelRef))];
|
|
92
|
-
const currentRef = currentModelRef(ctx);
|
|
93
|
-
if (!currentRef || !refs.includes(currentRef)) return refs;
|
|
94
|
-
return [currentRef, ...refs.filter((ref) => ref !== currentRef)];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
87
|
/**
|
|
98
88
|
* Resolve one agent's ordered runtime pool:
|
|
99
89
|
*
|