@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/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
- export const WORKTREE_ISOLATION_INSTRUCTIONS =
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, signal, _onUpdate, ctx) {
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; retain?: boolean },
239
+ opts?: { silent?: boolean },
246
240
  ): void => {
247
241
  monitor.setStatus(runId, status); // stamps endedAt for the elapsed time
248
- const run = opts?.retain ? monitor.findRun(runId) : monitor.removeRun(runId);
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. In parallel, every
258
- // live event is appended to the thread's append-only trajectory (status,
259
- // model-candidate changes, usage, tool starts/ends with a redacted
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, threadId?: number, generation?: 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
- // Chain rounds keep their own lifecycle trajectory.
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
- chainState.trajectory.append({
437
- kind: "settled",
438
- status: isFailedResult(result) ? "failed" : "done",
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", { retain: true });
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 every monitor row belonging to an auto-fix chain; the retained
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: parentRunId, result: initialReviewerResult, relation: "initial review" },
467
+ { runId: initialStepRunId, result: initialStepResult, relation: "initial review" },
519
468
  ];
520
- let lastReviewer = initialReviewerResult;
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 retained checkpoint and suppresses an aborted chain delivery.
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 the retained
591
- // parent row and its retained round rows, then deliver one condensed
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
- runtime.registerRunResult(parentRunId, last.result);
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(last.result);
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, resumed: true }
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
- if (candidate.discard) {
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, runId, generation);
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
- finishRun(runId, "done", { silent: true, retain: true });
1552
- monitor.setAnnotation(runId, "auto-fix chain running");
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/trajectory state
1622
- // before that owner records the checkpoint or aborted result.
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
- export function formatTokens(count: number): string {
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 feature notices
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
  *