@ferris1225/pi-subagents 4.1.24 → 4.2.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/runtime.ts CHANGED
@@ -25,7 +25,6 @@ import { isRunActiveStatus, monitor } from "./monitor.ts";
25
25
  import type { RpcRunControl } from "./rpc-run.ts";
26
26
  import type { StartBackgroundInternal } from "./thread-lifecycle.ts";
27
27
  import { isFailedResult, type SingleResult } from "./spawn.ts";
28
- import type { ReviewMode } from "./workflow.ts";
29
28
  import type { IsolationMode, WorktreeFinalization, WorktreeIsolation } from "./worktree.ts";
30
29
 
31
30
  export type ThreadState =
@@ -51,15 +50,12 @@ export interface SubagentThread {
51
50
  executionCwd: string;
52
51
  thinkingLevel?: ThinkingLevel;
53
52
  isolation: IsolationMode;
54
- /** Dispatch-time gate intensity; "none" skips the automatic post-writer
55
- * reviewer for this thread (kept across resumes and reloads). */
56
- review?: ReviewMode;
57
53
  worktree?: WorktreeIsolation;
58
54
  state: ThreadState;
59
55
  control: RpcRunControl;
60
56
  queueController?: AbortController;
61
- /** Resolves only after the current generation's top-level child, downstream
62
- * managed workflow, and queue work have fully quiesced and released their
57
+ /** Resolves only after the current generation's child process, isolation
58
+ * finalization, and queue work have fully quiesced and released their
63
59
  * concurrency slot. */
64
60
  generationCompletion: Promise<void>;
65
61
  /** Synchronous CAS used by lifecycle controls across their async preflight. */
@@ -122,8 +118,7 @@ export interface SubagentRuntime {
122
118
  /** Resume setup that has claimed a thread but has not yet enqueued its
123
119
  * next generation. Shutdown invalidates these claims and waits for cleanup. */
124
120
  preflightOperations: Set<Promise<void>>;
125
- /** Every session directory retained for this parent session, including
126
- * managed-workflow internals that are not directly controllable. */
121
+ /** Every session directory retained for this parent session. */
127
122
  sessionDirs: Set<string>;
128
123
  retainSession: (result: Pick<SingleResult, "sessionDir">) => void;
129
124
  retireThreadSession: (thread: SubagentThread) => void;
@@ -148,8 +143,6 @@ export function createRuntime(pi: ExtensionAPI, configPath: string): SubagentRun
148
143
  // Computing this at delivery (emit) time — not when the item was
149
144
  // pushed — reflects the current monitor state, since finishing runs
150
145
  // are removed from the monitor before their completion is pushed.
151
- // Managed-workflow parents remain "running" through reviewer and
152
- // documenter stages, so they are included without a special case.
153
146
  const active = monitor
154
147
  .getRuns()
155
148
  .filter((run) => isRunActiveStatus(run.status))
package/src/setup.ts CHANGED
@@ -40,11 +40,7 @@ import { promptSelectMany, promptSelectOne } from "./ui.ts";
40
40
  /** Short, selection-friendly descriptions for the built-in agents. */
41
41
  const MODULE_HINTS: Record<string, string> = {
42
42
  explorer: "read-only codebase recon (fast, read-only tools)",
43
- worker: "implement / fix / refactor / test (full tools)",
44
- cleaner: "apply proven cleanup and deduplicate code (full tools)",
45
- documenter: "sync diff or whole-codebase comments/docs (docs write)",
46
- synthesizer: "merge fan-out results/long sources into one brief (read-only)",
47
- reviewer: "read-only audits and pre-commit gates",
43
+ executor: "implement / fix / clean up / docs sync / merge results (full tools)",
48
44
  };
49
45
 
50
46
  function moduleLabel(name: string): string {
@@ -254,7 +250,6 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
254
250
  agentModels,
255
251
  // Full setup returns every agent to capability-aware Auto thinking.
256
252
  agentThinkingLevels: {},
257
- notifyOnReviewPass: base.notifyOnReviewPass,
258
253
  maxResultLines: base.maxResultLines,
259
254
  agentScope: base.agentScope,
260
255
  idleTimeoutSec: base.idleTimeoutSec,
package/src/spawn.ts CHANGED
@@ -87,16 +87,6 @@ export function getFinalOutput(messages: Message[]): string {
87
87
  return "";
88
88
  }
89
89
 
90
- /** Only the last standalone reviewer verdict line counts. */
91
- export function reviewVerdict(output: string): "pass" | "fail" | undefined {
92
- const lines = output.split("\n");
93
- for (let index = lines.length - 1; index >= 0; index--) {
94
- const match = /^\s*VERDICT:\s*REVIEW_(PASS|FAIL)\s*$/i.exec(lines[index]);
95
- if (match) return match[1].toUpperCase() === "PASS" ? "pass" : "fail";
96
- }
97
- return undefined;
98
- }
99
-
100
90
  export const RESULT_LINE_MAX = 200;
101
91
 
102
92
  export interface TruncatedOutput {
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Stable logical-thread generation lifecycle for background sub-agents.
3
3
  *
4
- * Dispatch owns workflow policy, the live stage projection, and internal role
5
- * briefs; this module owns one
4
+ * Dispatch owns tool policy and role briefs; this module owns one
6
5
  * stable parent generation end to end: managed-repository lane use,
7
- * worktree setup/finalization after downstream review, queue/process ownership,
6
+ * worktree setup/finalization, queue/process ownership,
8
7
  * retained-session resume, and guarded one-time terminal publication.
9
8
  */
10
9
 
@@ -19,7 +18,7 @@ import {
19
18
  resolveAgentTools,
20
19
  type AgentConfig,
21
20
  } from "./agents.ts";
22
- import { completionTriggersTurn, type CompletionMessageItem } from "./completion.ts";
21
+ import { type CompletionMessageItem } from "./completion.ts";
23
22
  import {
24
23
  DEFAULT_THINKING_LEVEL,
25
24
  loadConfig,
@@ -43,18 +42,8 @@ import {
43
42
  failedStartResult,
44
43
  formatCompletionBlock,
45
44
  modelLevelTakeoverNote,
46
- reviewFailFollowUpNote,
47
45
  queuedResult,
48
46
  } from "./format.ts";
49
- import {
50
- canStartManagedWorkflow,
51
- formatManagedWorkflowSummary,
52
- getManagedWorkflowPlan,
53
- workflowAgentAvailability,
54
- type ManagedWorkflowOutcome,
55
- type ManagedWorkflowPlan,
56
- type ReviewMode,
57
- } from "./workflow.ts";
58
47
  import {
59
48
  availableModelsInScope,
60
49
  currentModelRef,
@@ -63,19 +52,17 @@ import {
63
52
  resolveAgentModelRoute,
64
53
  resolveThinkingLevel,
65
54
  } from "./models.ts";
66
- import { monitor, sumUsage } from "./monitor.ts";
55
+ import { monitor } from "./monitor.ts";
67
56
  import { persistRecoveryRecords, recoveryRecordFromFinalization } from "./recovery.ts";
68
57
  import type { SubagentRuntime, SubagentThread, ThreadState } from "./runtime.ts";
69
58
  import { forkRetainedSession } from "./session-fork.ts";
70
59
  import {
71
60
  buildResumePrompt,
72
61
  getProjectRoot,
73
- getResultOutput,
74
62
  PROJECT_ROOTS_DIR_NAME,
75
63
  RpcRunControl,
76
64
  isFailedResult,
77
65
  isModelLevelFailure,
78
- reviewVerdict,
79
66
  runSingleAgentWithMainFallback,
80
67
  sessionExists,
81
68
  sweepProjectResultArtifacts,
@@ -136,10 +123,10 @@ async function canonicalManagedRepositoryRoot(cwd: string): Promise<string> {
136
123
 
137
124
  /** Run one operation under the canonical original-repository lane.
138
125
  *
139
- * Shared managed generations use the abortable overload for their complete
140
- * writer/reviewer workflow. Isolated generations use the non-abortable overload
141
- * only for their final worktree apply, so model work remains parallel while the
142
- * original checkout mutation cannot race a shared writer or reviewer snapshot.
126
+ * Shared write-capable generations use the abortable overload for their whole
127
+ * run. Isolated generations use the non-abortable overload only for their
128
+ * final worktree apply, so model work remains parallel while the original
129
+ * checkout mutation cannot race a shared writer.
143
130
  */
144
131
  export async function runInManagedRepositoryLane<T>(
145
132
  cwd: string,
@@ -285,8 +272,6 @@ export interface StartBackgroundOptions {
285
272
  environment?: DispatchEnvironment;
286
273
  seed?: SessionSeed;
287
274
  resumeReservation?: ResumeReservation;
288
- /** Gate intensity for this dispatch; a resume keeps the thread's mode. */
289
- review?: ReviewMode;
290
275
  }
291
276
 
292
277
  export type StartBackgroundInternal = (
@@ -341,20 +326,6 @@ export function resolveDispatchModelRoute(
341
326
  };
342
327
  }
343
328
 
344
- export interface ManagedWorkflowRequest extends DispatchEnvironment {
345
- plan: ManagedWorkflowPlan;
346
- initialResult: SingleResult;
347
- groupId: string;
348
- parentRunId: number;
349
- executionCwd: string;
350
- projectCwd: string;
351
- isolation: IsolationMode;
352
- /** Short identity of the isolated worktree shared by every workflow stage. */
353
- worktreeId?: string;
354
- signal: AbortSignal;
355
- rememberLatest: (result: SingleResult) => void;
356
- }
357
-
358
329
  interface BackgroundDispatcherOptions {
359
330
  runtime: SubagentRuntime;
360
331
  /** Live dispatch environment; resolved lazily so control operations work
@@ -373,7 +344,6 @@ interface BackgroundDispatcherOptions {
373
344
  mode: "single" | "parallel",
374
345
  background?: boolean,
375
346
  ) => (results: SingleResult[]) => SubagentDetails;
376
- runManagedWorkflow: (request: ManagedWorkflowRequest) => Promise<ManagedWorkflowOutcome>;
377
347
  }
378
348
 
379
349
  export function createBackgroundDispatcher(options: BackgroundDispatcherOptions): StartBackgroundInternal {
@@ -383,7 +353,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
383
353
  finishRun,
384
354
  makeLiveHandler,
385
355
  makeDetails,
386
- runManagedWorkflow,
387
356
  } = options;
388
357
 
389
358
  const startBackground: StartBackgroundInternal = async (
@@ -400,10 +369,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
400
369
  seed,
401
370
  resumeReservation,
402
371
  } = startOptions;
403
- // The dispatch-time gate choice is a thread property: resumes of a
404
- // review-exempt task stay exempt instead of surprising the caller with a
405
- // full gate after a reload.
406
- const review: ReviewMode = startOptions.review ?? existingThread?.review ?? "gate";
407
372
  if (!runtime.sessionActive) {
408
373
  return failedStartResult(agentName, task, "Parent session shut down before this subagent generation could start.");
409
374
  }
@@ -421,7 +386,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
421
386
  const agent = resolveLiveAgentTools(discoveredAgent);
422
387
  if (isolation === "worktree" && !isWorktreeCapableAgent(agent)) {
423
388
  return {
424
- ...failedStartResult(agentName, task, `Agent "${agentName}" is read-only; worktree isolation is available only to write-capable agents such as worker, cleaner, or documenter.`),
389
+ ...failedStartResult(agentName, task, `Agent "${agentName}" is read-only; worktree isolation is available only to write-capable agents such as executor.`),
425
390
  isolation,
426
391
  };
427
392
  }
@@ -516,7 +481,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
516
481
  thread.executionCwd = executionCwd;
517
482
  thread.thinkingLevel = thinkingLevel;
518
483
  thread.isolation = isolation;
519
- thread.review = review;
520
484
  thread.worktree = worktree;
521
485
  thread.state = "queued";
522
486
  thread.control = control;
@@ -540,7 +504,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
540
504
  executionCwd,
541
505
  thinkingLevel,
542
506
  isolation,
543
- review,
544
507
  worktree,
545
508
  state: "queued",
546
509
  control,
@@ -561,9 +524,10 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
561
524
  });
562
525
 
563
526
  const onLive = makeLiveHandler(runId, generation);
564
- const workflowAvailability = workflowAgentAvailability(runAgents);
565
- const reserveManagedLane =
566
- isolation === "shared" && canStartManagedWorkflow(agent, workflowAvailability);
527
+ // Shared write-capable runs serialize on the repository lane so their
528
+ // edits cannot race; the lane wait releases the process slot because it
529
+ // is write serialization, not pool pacing.
530
+ const reserveManagedLane = isolation === "shared" && isWriteCapableAgent(agent);
567
531
  const runGeneration = async (backgroundSignal: AbortSignal, controller: AbortController): Promise<void> => {
568
532
  if (runtime.threads.get(runId)?.generation !== generation) return;
569
533
  // The generation body owns a process slot from here (a lane wait, if
@@ -660,82 +624,12 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
660
624
  // exactly one aborted result.
661
625
  if (thread.lifecycleOperation === "stop") return;
662
626
 
663
- // A shutdown can win in the microtask gap after the top-level RPC
664
- // settles. Do not launch an obsolete downstream gate or replace the
665
- // stable top-level session with an aborted downstream attempt.
627
+ // A shutdown can win in the microtask gap after the child RPC
628
+ // settles. Never replace the stable top-level session with an
629
+ // aborted partial.
666
630
  if (backgroundSignal.aborted || lifecycleInterrupted() || !runtime.sessionActive) return;
667
631
 
668
632
  if (thread.retireOnSettle) runtime.retireThreadSession(thread);
669
- let workflowOutcome: ManagedWorkflowOutcome | undefined;
670
- const workflowPlan = getManagedWorkflowPlan(
671
- result,
672
- workflowAvailability,
673
- review,
674
- await producedWorkspaceChanges(thread),
675
- );
676
- if (workflowPlan && runtime.sessionActive) {
677
- // The continuation is runtime-initiated (gate review,
678
- // documentation sync): release this generation's
679
- // concurrency slot so managed chains never starve manual
680
- // dispatches. Cancellation and quiescence guarantees are
681
- // unchanged — the task stays abortable and awaited.
682
- runtime.backgroundQueue.suspend(controller);
683
- thread.state = "running";
684
- // The stable parent row now represents workflow ownership, not whichever
685
- // model stage ran most recently. Internal rows own their exact role/model/
686
- // thinking/timing telemetry and remain independently queryable.
687
- monitor.setManagedWorkflow(runId, true);
688
- monitor.setStatus(runId, "running");
689
- monitor.setActivity(runId, "managed workflow running");
690
- workflowOutcome = await runManagedWorkflow({
691
- plan: workflowPlan,
692
- initialResult: result,
693
- groupId: `workflow-${runId}`,
694
- parentRunId: runId,
695
- executionCwd: thread.executionCwd,
696
- projectCwd: originalCwd,
697
- isolation,
698
- ...(worktree ? { worktreeId: worktreeGroupId(worktree) } : {}),
699
- signal: backgroundSignal,
700
- ctx: runCtx,
701
- config: runConfig,
702
- agents: runAgents,
703
- rememberLatest: (latest) => {
704
- if (runtime.threads.get(runId) !== thread || thread.generation !== generation) return;
705
- thread.lastResult = latest;
706
- // Retained control follows the newest child session, but the live parent
707
- // row keeps the original top-level role/model/usage. The active internal
708
- // row already owns the current stage's role and telemetry.
709
- thread.agentName = latest.agent;
710
- thread.task = latest.task;
711
- thread.sessionId = latest.sessionId;
712
- thread.sessionDir = latest.sessionDir;
713
- runtime.retainSession(latest);
714
- if (latest.sessionId && latest.sessionDir) {
715
- persistThreadCheckpoint(runtime, thread, "parked");
716
- }
717
- },
718
- });
719
-
720
- // Park/stop/shutdown owns this generation once it cancels the queue
721
- // signal. The newest internal partial is already on thread.lastResult;
722
- // never replace it with the old top-level result or publish stale output.
723
- if (backgroundSignal.aborted || lifecycleInterrupted() || !runtime.sessionActive) return;
724
-
725
- const finalStep = workflowOutcome.steps[workflowOutcome.steps.length - 1]!;
726
- result = {
727
- ...finalStep.result,
728
- runId,
729
- projectCwd: originalCwd,
730
- isolation,
731
- };
732
- thread.lastResult = result;
733
- thread.agentName = result.agent;
734
- thread.task = result.task;
735
- thread.sessionId = result.sessionId;
736
- thread.sessionDir = result.sessionDir;
737
- runtime.retainSession(result);
738
- }
739
633
  // Claim terminal settlement synchronously before the first slow await.
740
634
  // Park therefore either wins while RPC is still active, or is rejected
741
635
  // once settlement owns the generation. Destructive stop may supersede
@@ -749,89 +643,43 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
749
643
  thread.lifecycleOperation === "settle" &&
750
644
  !thread.retired;
751
645
  try {
752
- // For isolated writers this is deliberately after the managed reviewer
753
- // and any needed documentation stage: every child sees the same worktree,
754
- // then one lifecycle owner integrates the complete settled state exactly once.
755
- await thread.finalizeIsolation(generation, result);
756
- if (!ownsSettlement()) return;
757
- if (workflowOutcome && isolation === "worktree") {
758
- for (const step of workflowOutcome.steps) {
759
- step.result.integrationStatus = result.integrationStatus;
760
- step.result.integrationApplied = result.integrationApplied;
761
- step.result.integrationError = result.integrationError;
762
- step.result.integrationWorktreePath = result.integrationWorktreePath;
763
- step.result.integrationPatchPath = result.integrationPatchPath;
764
- }
765
- }
646
+ // For isolated writers the apply runs only after the child settled,
647
+ // so this lifecycle owner integrates the complete settled state
648
+ // exactly once under the repository lane.
649
+ await thread.finalizeIsolation(generation, result);
650
+ if (!ownsSettlement()) return;
766
651
 
767
- const failed = isFailedResult(result);
768
- thread.state = failed ? "failed" : "completed";
769
- // Stamp the terminal monitor state before projecting it. This gives every
770
- // path a fixed endedAt even when the row is removed immediately.
771
- monitor.setStatus(runId, failed ? "failed" : "done");
772
- thread.elapsedMs = monitor.getElapsedMs(runId) ?? thread.elapsedMs;
773
- // Persist before the sessionActive check: a shutdown that won the
774
- // lifecycle race must still leave the settled record on disk.
775
- persistThreadCheckpoint(runtime, thread, failed ? "failed" : "completed");
776
- if (!runtime.sessionActive || !ownsSettlement()) return;
777
-
778
- const modelLevel = failed && isModelLevelFailure(result);
779
- const dispatchFailed = result.dispatchFailed === true;
780
- const ownedController = thread.queueController;
781
- if (runtime.runControllers.get(runId) === ownedController) runtime.runControllers.delete(runId);
782
- thread.queueController = undefined;
783
- finishRun(
784
- runId,
785
- failed ? "failed" : "done",
786
- workflowOutcome || modelLevel || dispatchFailed ? { silent: true } : undefined,
787
- );
788
- runtime.registerRunResult(runId, result);
652
+ const failed = isFailedResult(result);
653
+ thread.state = failed ? "failed" : "completed";
654
+ // Stamp the terminal monitor state before projecting it. This gives every
655
+ // path a fixed endedAt even when the row is removed immediately.
656
+ monitor.setStatus(runId, failed ? "failed" : "done");
657
+ thread.elapsedMs = monitor.getElapsedMs(runId) ?? thread.elapsedMs;
658
+ // Persist before the sessionActive check: a shutdown that won the
659
+ // lifecycle race must still leave the settled record on disk.
660
+ persistThreadCheckpoint(runtime, thread, failed ? "failed" : "completed");
661
+ if (!runtime.sessionActive || !ownsSettlement()) return;
789
662
 
790
- if (workflowOutcome) {
791
- const lastStep = workflowOutcome.steps[workflowOutcome.steps.length - 1]!;
792
- const finalVerdict = lastStep.result.agent === "reviewer"
793
- ? reviewVerdict(getResultOutput(lastStep.result))
794
- : undefined;
795
- // The delivery is the model's only view of the workflow. On success
796
- // the writer's handoff is the actionable detail (overlaid with the
797
- // parent's integration outcome); on failure the terminal result is.
798
- const writerHandoff = !failed && finalVerdict === "pass"
799
- ? {
800
- ...workflowOutcome.steps[0]!.result,
801
- runId: result.runId ?? workflowOutcome.steps[0]!.result.runId,
802
- isolation: result.isolation,
803
- integrationStatus: result.integrationStatus,
804
- integrationApplied: result.integrationApplied,
805
- integrationWorktreePath: result.integrationWorktreePath,
806
- integrationPatchPath: result.integrationPatchPath,
807
- integrationError: result.integrationError,
808
- }
809
- : result;
810
- let block = `${formatManagedWorkflowSummary(workflowOutcome.steps, result)}\n\n${formatCompletionBlock(writerHandoff, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) })}`;
811
- if (finalVerdict === "fail") {
812
- block += `\n\n${reviewFailFollowUpNote()}`;
813
- }
814
- if (modelLevel) block += `\n\n${modelLevelTakeoverNote(result, { runId })}`;
815
- runtime.sendCompletionGroup([{
816
- agent: `managed workflow (${result.agent})`,
817
- block,
818
- triggerTurn: true,
819
- usage: sumUsage(workflowOutcome.steps.map((step) => step.result.usage)),
820
- }]);
821
- runtime.completionBatcher.flush();
822
- return;
823
- }
663
+ const modelLevel = failed && isModelLevelFailure(result);
664
+ const dispatchFailed = result.dispatchFailed === true;
665
+ const ownedController = thread.queueController;
666
+ if (runtime.runControllers.get(runId) === ownedController) runtime.runControllers.delete(runId);
667
+ thread.queueController = undefined;
668
+ finishRun(
669
+ runId,
670
+ failed ? "failed" : "done",
671
+ modelLevel || dispatchFailed ? { silent: true } : undefined,
672
+ );
673
+ runtime.registerRunResult(runId, result);
824
674
 
825
- const completion: CompletionMessageItem = {
826
- agent: result.agent,
827
- block: modelLevel
828
- ? `${formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) })}\n\n${modelLevelTakeoverNote(result, { runId })}`
829
- : result.agent === "reviewer" && reviewVerdict(getResultOutput(result)) === "fail"
830
- ? `${formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) })}\n\n${reviewFailFollowUpNote()}`
831
- : formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) }),
832
- triggerTurn: completionTriggersTurn(result, runConfig.notifyOnReviewPass),
833
- usage: result.usage,
834
- };
675
+ const completion: CompletionMessageItem = {
676
+ agent: result.agent,
677
+ block: modelLevel
678
+ ? `${formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) })}\n\n${modelLevelTakeoverNote(result, { runId })}`
679
+ : formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) }),
680
+ triggerTurn: true,
681
+ usage: result.usage,
682
+ };
835
683
  if (modelLevel) {
836
684
  const detail = result.errorMessage?.trim() || "model unavailable or broken";
837
685
  runCtx.ui.notify(`✗ ${result.agent} dispatch failed: ${detail} — task handed to the main window`, "error");
@@ -911,7 +759,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
911
759
  isolation,
912
760
  exitCode: 1,
913
761
  stopReason: "error",
914
- errorMessage: `Managed workflow dispatch failed: ${errorMessage}`,
762
+ errorMessage: `Subagent dispatch failed: ${errorMessage}`,
915
763
  dispatchFailed: true,
916
764
  }
917
765
  : {
@@ -1299,21 +1147,6 @@ async function discardRestoredRecord(runtime: SubagentRuntime, record: ThreadRec
1299
1147
  await removeThreadRecord(runtime.configPath, record.runId).catch(() => undefined);
1300
1148
  }
1301
1149
 
1302
- /** Whether a settled generation left anything for a gate to review.
1303
- *
1304
- * Only an isolated worktree can answer: it starts from the integration base, so a
1305
- * diff against that base is precisely this generation's own output. A shared
1306
- * checkout is shared with the user and their editor, so nothing in it can be
1307
- * attributed to one run, and undefined keeps the gate. Wrongly skipping a review
1308
- * is the only failure here that costs correctness rather than a run, so an
1309
- * undecidable case always pays for the run. */
1310
- async function producedWorkspaceChanges(
1311
- thread: SubagentThread,
1312
- ): Promise<boolean | undefined> {
1313
- if (!thread.worktree) return undefined;
1314
- return thread.worktree.hasPendingChanges().catch(() => undefined);
1315
- }
1316
-
1317
1150
  /** Project-scoped <projectRoot>/results for a completion's artifacts. */
1318
1151
  export function projectResultsRoot(configPath: string, cwd: string | undefined): string {
1319
1152
  return join(getProjectRoot(configPath, cwd), "results");
@@ -1334,7 +1167,6 @@ function createRestoredThread(
1334
1167
  executionCwd: record.executionCwd,
1335
1168
  ...(record.thinkingLevel ? { thinkingLevel: record.thinkingLevel as ThinkingLevel } : {}),
1336
1169
  isolation: record.isolation,
1337
- ...(record.review ? { review: record.review } : {}),
1338
1170
  worktree,
1339
1171
  state,
1340
1172
  control: new RpcRunControl(record.task, record.generation),