@ferris1225/pi-subagents 4.1.23 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dispatch.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /**
2
- * The `subagent` tool: dispatches the enabled agents (explorer, worker, cleaner,
3
- * documenter, synthesizer, reviewer, plus custom roles) as isolated pi
4
- * child processes, single or parallel. Owns the public dispatch contract,
5
- * per-run status tracking, the managed worker/cleaner reviewer gate, and
6
- * internal step launching. Stable thread generations, final integration, and
2
+ * The `subagent` tool: dispatches the enabled agents (explorer, executor,
3
+ * plus custom roles) as isolated pi
4
+ * child processes, single or parallel. Owns the public dispatch contract and
5
+ * per-run status tracking. Stable thread generations, final integration, and
7
6
  * completion ownership live in thread-lifecycle.ts.
8
7
  */
9
8
 
@@ -15,25 +14,13 @@ import { Type } from "typebox";
15
14
  import { discoverAgents, isWriteCapableAgent, resolveAgentTools, type AgentConfig } from "./agents.ts";
16
15
  import { loadConfig } from "./config.ts";
17
16
  import { formatCompletionBlock, formatUsage, queuedResult } from "./format.ts";
18
- import {
19
- buildFinalReviewBrief,
20
- buildReReviewBrief,
21
- buildReviewerFixBrief,
22
- MAX_REVIEW_FIX_ROUNDS,
23
- type ChainStep,
24
- type ManagedWorkflowOutcome,
25
- type ReviewMode,
26
- } from "./workflow.ts";
27
17
  import {
28
18
  formatTaskSummary,
29
19
  formatToolActivity,
30
20
  monitor,
31
21
  statusIcon,
32
- type RunChainMeta,
33
22
  type RunView,
34
23
  type RunWaitReason,
35
- type WorkflowStage,
36
- type WorkflowStageStatus,
37
24
  } from "./monitor.ts";
38
25
  import type { SubagentRuntime } from "./runtime.ts";
39
26
  import { persistThreadCheckpoint } from "./thread-lifecycle.ts";
@@ -41,7 +28,6 @@ import {
41
28
  getProjectRoot,
42
29
  getResultOutput,
43
30
  isFailedResult,
44
- reviewVerdict,
45
31
  runSingleAgentWithMainFallback,
46
32
  type SingleResult,
47
33
  type SubagentDetails,
@@ -54,7 +40,6 @@ import {
54
40
  runInManagedRepositoryLane,
55
41
  withWorktreeSystemPrompt,
56
42
  type DispatchEnvironment,
57
- type ManagedWorkflowRequest,
58
43
  } from "./thread-lifecycle.ts";
59
44
  import type { IsolationMode } from "./worktree.ts";
60
45
 
@@ -63,7 +48,7 @@ export { isWorktreeCapableAgent, runInManagedRepositoryLane } from "./thread-lif
63
48
  const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
64
49
 
65
50
  const ISOLATION_DESCRIPTION =
66
- "Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including worker, cleaner, and documenter, only)";
51
+ "Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including executor, only)";
67
52
 
68
53
  const IsolationSchema = Type.Optional(
69
54
  StringEnum(["shared", "worktree"] as const, { description: ISOLATION_DESCRIPTION }),
@@ -76,13 +61,6 @@ const WaitSchema = Type.Optional(
76
61
  }),
77
62
  );
78
63
 
79
- const REVIEW_DESCRIPTION =
80
- "Gate intensity for a worker/cleaner task: \"gate\" (default) runs one automatic reviewer after success; \"none\" skips it for mechanical, low-risk edits (typos, comments, doc strings, config value tweaks) that you verify yourself. Keep the default whenever behavior can change.";
81
-
82
- const ReviewSchema = Type.Optional(
83
- StringEnum(["gate", "none"] as const, { description: REVIEW_DESCRIPTION }),
84
- );
85
-
86
64
  const TaskItem = Type.Object({
87
65
  agent: Type.String({ description: "Name of the agent to invoke" }),
88
66
  task: Type.String({
@@ -91,7 +69,6 @@ const TaskItem = Type.Object({
91
69
  }),
92
70
  cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
93
71
  isolation: IsolationSchema,
94
- review: ReviewSchema,
95
72
  });
96
73
 
97
74
  const SubagentParams = Type.Object({
@@ -102,14 +79,13 @@ const SubagentParams = Type.Object({
102
79
  tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
103
80
  cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
104
81
  isolation: IsolationSchema,
105
- review: ReviewSchema,
106
82
  wait: WaitSchema,
107
83
  });
108
84
 
109
85
  /** Roles that default to worktree isolation in parallel dispatches even when
110
86
  * the live catalog cannot be consulted (render-only call sites). Custom
111
87
  * write-capable agents join them via isWriteCapableAgent on the execute path. */
112
- const WORKTREE_DEFAULT_AGENTS = new Set(["worker", "cleaner", "documenter"]);
88
+ const WORKTREE_DEFAULT_AGENTS = new Set(["executor"]);
113
89
 
114
90
  /** Resolve the default isolation for a dispatch. Precedence: an explicit
115
91
  * per-call request, then the role's own frontmatter declaration (`worktree`
@@ -131,27 +107,6 @@ export function defaultIsolationMode(
131
107
  return mode === "parallel" && writeCapable ? "worktree" : "shared";
132
108
  }
133
109
 
134
- function workflowStageStatus(result: SingleResult, relation?: string): WorkflowStageStatus {
135
- if (isFailedResult(result)) return "failed";
136
- if (relation === "review fix") return "done";
137
- if (result.agent !== "reviewer") return "done";
138
- const verdict = reviewVerdict(getResultOutput(result));
139
- if (verdict === "fail") return "changes";
140
- return verdict === "pass" ? "done" : "failed";
141
- }
142
-
143
- /** The runtime-granted write continuation of a failed gate: same reviewer
144
- * role, model, and retained session, but the read-only boundary is lifted for
145
- * this one stage so it applies its own fix instructions. One line only: the
146
- * full fix-stage contract is already in the retained session's prompt. */
147
- function withReviewerFixStageAgent(agent: AgentConfig): AgentConfig {
148
- return {
149
- ...agent,
150
- tools: undefined,
151
- systemPrompt: `${agent.systemPrompt.trimEnd()}\n\nRuntime workflow context: FIX STAGE — your gate just returned REVIEW_FAIL. Your read-only boundary is lifted for this stage only: apply your own fix instructions exactly as specified, verify, and report what changed; never edit during a review and never emit a verdict here.`,
152
- };
153
- }
154
-
155
110
  /** In-turn wait behind dispatch `wait: true` — the escape hatch for one-shot
156
111
  * `pi -p` parents that exit at end of turn: hold the call until every run it
157
112
  * started settles, then hand back their result blocks. Interactive sessions
@@ -240,27 +195,24 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
240
195
  const environmentRef: { current: DispatchEnvironment | undefined } = { current: undefined };
241
196
 
242
197
  // Finished runs leave the active monitor immediately. Their final findings
243
- // are sent as a custom message that starts a follow-up turn. Returns the
244
- // removed row so workflow callers can freeze its exact elapsed time onto
245
- // the stage projection before the row is gone.
198
+ // are sent as a custom message that starts a follow-up turn.
246
199
  const finishRun = (
247
200
  runId: number,
248
201
  status: "done" | "failed",
249
202
  opts?: { silent?: boolean },
250
- ): RunView | undefined => {
203
+ ): void => {
251
204
  monitor.setStatus(runId, status); // stamps endedAt for the elapsed time
252
205
  const run = monitor.removeRun(runId);
253
- if (!run) return undefined; // already finished — stay idempotent
254
- if (opts?.silent || !runtime.sessionActive) return run;
206
+ if (!run) return; // already finished — stay idempotent
207
+ if (opts?.silent || !runtime.sessionActive) return;
255
208
  const icon = status === "done" ? "✓" : "✗";
256
209
  environmentRef.current?.ctx.ui.notify(`${icon} #${run.id} ${monitor.summarize(run)}`, status === "done" ? "info" : "error");
257
- return run;
258
210
  };
259
211
 
260
212
  // Live sub-agent activity → concise one-line status ("thinking",
261
213
  // "read src/index.ts", ...), never a raw args blob. The live handler
262
- // only updates monitor state; the queue task / launchInWorkflow owns
263
- // terminal removal, notification, and downstream workflow decisions.
214
+ // only updates monitor state; the queue task owns terminal removal,
215
+ // notification, and lifecycle decisions.
264
216
  const makeLiveHandler =
265
217
  (runId: number, generation?: number) =>
266
218
  (e: SubagentLiveEvent): void => {
@@ -344,247 +296,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
344
296
  return ` Pacing: ${parts.join(" · ")}. Keep dispatching independent units.`;
345
297
  };
346
298
 
347
- /** Launch one workflow-internal child in a fresh model context. It sees the
348
- * parent's exact repository/worktree state and is registered by its own id,
349
- * but never enters top-level lifecycle policy or completion delivery.
350
- * `stage` continues a retained session (the reviewer fix stage) and/or
351
- * replaces the resolved agent (lifting the reviewer read-only boundary). */
352
- const launchInWorkflow = async (
353
- request: ManagedWorkflowRequest,
354
- agentName: string,
355
- task: string,
356
- meta: RunChainMeta,
357
- stage: {
358
- agentOverride?: AgentConfig;
359
- session?: { sessionId: string; sessionDir: string };
360
- } = {},
361
- ): Promise<{ runId: number; result: SingleResult; elapsedMs?: number }> => {
362
- const discoveredAgent = request.agents.find((candidate) => candidate.name === agentName);
363
- if (!discoveredAgent) {
364
- throw new Error(`Managed workflow requires enabled agent "${agentName}", but discovery did not provide it.`);
365
- }
366
- const boundaryAgent = stage.agentOverride ?? discoveredAgent;
367
- const resolveLiveAgentTools = (candidate: AgentConfig): AgentConfig =>
368
- resolveAgentTools({ ...candidate, tools: boundaryAgent.tools }, runtime.getActiveTools());
369
- const agent = resolveLiveAgentTools(boundaryAgent);
370
- // Workflow policy (agents) stays fixed for the chain, but model/thinking
371
- // routes are re-read per stage so config edits apply to stages that have
372
- // not launched yet.
373
- const stageConfig = await loadConfig(runtime.configPath).catch(() => request.config);
374
- const resolvedRoute = resolveDispatchModelRoute(agent, stageConfig, request.ctx);
375
- const route = request.isolation === "worktree"
376
- ? { ...resolvedRoute, agent: withWorktreeSystemPrompt(resolvedRoute.agent) }
377
- : resolvedRoute;
378
- const thinkingLevel = route.thinkingLevel;
379
- const runId = monitor.addRun(agent.name, task, route.agent.model, thinkingLevel, {
380
- ...meta,
381
- isolation: request.isolation,
382
- ...(request.worktreeId ? { worktreeId: request.worktreeId } : {}),
383
- // Workflow-internal children spawn immediately: they never enter the
384
- // process queue, so they must never be reported as slot-waiting.
385
- waitReason: "starting",
386
- });
387
- const onLive = makeLiveHandler(runId);
388
- const projectRoot = getProjectRoot(runtime.configPath, request.executionCwd);
389
- try {
390
- const result = await runSingleAgentWithMainFallback(
391
- {
392
- defaultCwd: request.executionCwd,
393
- cwd: request.executionCwd,
394
- agent: route.agent,
395
- resolveAgentForAttempt: resolveLiveAgentTools,
396
- agentName,
397
- task,
398
- thinkingLevel,
399
- thinkingLevelForModel: route.thinkingLevelForModel,
400
- signal: request.signal,
401
- onLive,
402
- makeDetails: makeDetails("single", true),
403
- idleTimeoutMs: stageConfig.idleTimeoutSec * 1000,
404
- sessionRoot: join(projectRoot, "sessions"),
405
- scratchRoot: join(projectRoot, "tmp"),
406
- ...(stage.session
407
- ? { sessionId: stage.session.sessionId, sessionDir: stage.session.sessionDir, stdinText: task }
408
- : {}),
409
- },
410
- route.mainFallbackRef,
411
- );
412
- result.runId = runId;
413
- result.projectCwd = request.projectCwd;
414
- result.isolation = request.isolation;
415
- runtime.retainSession(result);
416
- monitor.setModel(runId, result.model, result.modelFallbackFrom);
417
- monitor.setThinking(runId, result.thinking);
418
- const finished = finishRun(runId, isFailedResult(result) ? "failed" : "done", { silent: true });
419
- runtime.registerRunResult(runId, result);
420
- return { runId, result, elapsedMs: finished?.elapsedMs };
421
- } catch (error) {
422
- const finished = finishRun(runId, "failed", { silent: true });
423
- const errorMessage = error instanceof Error ? error.message : String(error);
424
- const crashed: SingleResult = {
425
- ...queuedResult(route.agent, task, thinkingLevel),
426
- runId,
427
- projectCwd: request.projectCwd,
428
- isolation: request.isolation,
429
- exitCode: 1,
430
- stderr: errorMessage,
431
- stopReason: request.signal.aborted ? "aborted" : "error",
432
- errorMessage,
433
- dispatchFailed: true,
434
- };
435
- runtime.registerRunResult(runId, crashed);
436
- return { runId, result: crashed };
437
- }
438
- };
439
-
440
- /** Drop any in-flight internal row. Normal internal settlement already
441
- * removes rows; this is a cancellation/crash guard. */
442
- const removeWorkflowGroup = (groupId: string): void => {
443
- for (const run of [...monitor.getRuns()]) {
444
- if (run.groupId === groupId) monitor.removeRun(run.id);
445
- }
446
- };
447
-
448
- /** Run every downstream role inline under the parent generation's queue
449
- * controller. That gives park/stop/shutdown one lifecycle owner and keeps
450
- * isolated worktrees unintegrated until the final reviewer settles. */
451
- const runManagedWorkflow = async (
452
- request: ManagedWorkflowRequest,
453
- ): Promise<ManagedWorkflowOutcome> => {
454
- const initialStepRunId = monitor.reserveRunId();
455
- const initialStepResult: SingleResult = {
456
- ...request.initialResult,
457
- runId: initialStepRunId,
458
- };
459
- runtime.registerRunResult(initialStepRunId, initialStepResult);
460
- const steps: ChainStep[] = [{
461
- runId: initialStepRunId,
462
- result: initialStepResult,
463
- relation: request.plan.initialRelation,
464
- }];
465
- const enabled = (name: string): boolean =>
466
- request.agents.some((candidate) => candidate.name === name);
467
- const canContinue = (): boolean => runtime.sessionActive && !request.signal.aborted;
468
-
469
- // Keep a live parent-owned projection because settled internal rows are
470
- // intentionally removed. Only real/currently planned stages enter it.
471
- const initialStageRelation = initialStepResult.agent === "worker"
472
- ? "implement"
473
- : initialStepResult.agent === "cleaner"
474
- ? "cleanup"
475
- : "review";
476
- const workflowStages: WorkflowStage[] = [{
477
- agent: initialStepResult.agent,
478
- relation: initialStageRelation,
479
- status: workflowStageStatus(initialStepResult),
480
- // The initial stage is the parent's own run; freeze its telemetry now,
481
- // before the reopened parent row starts counting workflow-wide time.
482
- model: initialStepResult.model,
483
- usage: initialStepResult.usage,
484
- elapsedMs: monitor.getElapsedMs(request.parentRunId),
485
- }];
486
- let reviewStage: WorkflowStage | undefined;
487
- if (enabled("reviewer")) {
488
- reviewStage = { agent: "reviewer", relation: "review", status: "pending" };
489
- workflowStages.push(reviewStage);
490
- }
491
- const publishWorkflowStages = (): void => {
492
- monitor.setWorkflowStages(request.parentRunId, workflowStages);
493
- };
494
- publishWorkflowStages();
495
-
496
- /** Freeze the settled step's telemetry onto its stage: once the child row
497
- * leaves the monitor, this snapshot is the only per-stage record. */
498
- const settleStage = (stage: WorkflowStage, step: { result: SingleResult; elapsedMs?: number }): void => {
499
- stage.model = step.result.model;
500
- stage.usage = step.result.usage;
501
- if (step.elapsedMs !== undefined) stage.elapsedMs = step.elapsedMs;
502
- };
503
-
504
- const launchStep = async (
505
- agentName: string,
506
- task: string,
507
- relation: string,
508
- stage: WorkflowStage,
509
- stageOptions: {
510
- agentOverride?: AgentConfig;
511
- session?: { sessionId: string; sessionDir: string };
512
- } = {},
513
- ): Promise<SingleResult> => {
514
- if (!enabled(agentName)) {
515
- throw new Error(`Managed workflow cannot launch disabled or missing agent "${agentName}".`);
516
- }
517
- stage.status = "active";
518
- publishWorkflowStages();
519
- try {
520
- const step = await launchInWorkflow(request, agentName, task, {
521
- groupId: request.groupId,
522
- relationLabel: relation,
523
- parentRunId: request.parentRunId,
524
- }, stageOptions);
525
- stage.status = workflowStageStatus(step.result, relation);
526
- settleStage(stage, step);
527
- publishWorkflowStages();
528
- request.rememberLatest(step.result);
529
- steps.push({ ...step, relation });
530
- return step.result;
531
- } catch (error) {
532
- stage.status = "failed";
533
- publishWorkflowStages();
534
- throw error;
535
- }
536
- };
537
-
538
- try {
539
- // Park/stop/shutdown may win after the top-level child settles but
540
- // before this continuation starts. Preserve that stable checkpoint and
541
- // never create an already-aborted downstream child.
542
- if (!canContinue()) return { steps };
543
- if (reviewStage) {
544
- const discoveredReviewer = request.agents.find((candidate) => candidate.name === "reviewer")!;
545
- let gateReview = await launchStep(
546
- "reviewer",
547
- buildFinalReviewBrief(initialStepResult),
548
- "final review",
549
- reviewStage,
550
- );
551
- // The failing gate owns its fixes: the same retained
552
- // reviewer session applies its own fix instructions with write access,
553
- // then a converging re-review verifies the fixes. The cap only stops
554
- // pathological burn and hands the still-failing gate to the main agent.
555
- for (let round = 1; round <= MAX_REVIEW_FIX_ROUNDS; round++) {
556
- const gateSession = gateReview.sessionId && gateReview.sessionDir
557
- ? { sessionId: gateReview.sessionId, sessionDir: gateReview.sessionDir }
558
- : undefined;
559
- if (reviewVerdict(getResultOutput(gateReview)) !== "fail" || !gateSession || !canContinue()) break;
560
- const fixStage: WorkflowStage = { agent: "reviewer", relation: "review fix", status: "pending" };
561
- workflowStages.push(fixStage);
562
- publishWorkflowStages();
563
- const fixResult = await launchStep(
564
- "reviewer",
565
- buildReviewerFixBrief(getResultOutput(gateReview)),
566
- "review fix",
567
- fixStage,
568
- { agentOverride: withReviewerFixStageAgent(discoveredReviewer), session: gateSession },
569
- );
570
- if (isFailedResult(fixResult) || !canContinue()) break;
571
- const reReviewStage: WorkflowStage = { agent: "reviewer", relation: "review", status: "pending" };
572
- workflowStages.push(reReviewStage);
573
- publishWorkflowStages();
574
- gateReview = await launchStep(
575
- "reviewer",
576
- buildReReviewBrief(fixResult, round),
577
- round === 1 ? "re-review" : `re-review ${round}`,
578
- reReviewStage,
579
- );
580
- }
581
- }
582
- return { steps };
583
- } finally {
584
- removeWorkflowGroup(request.groupId);
585
- }
586
- };
587
-
588
299
  const startBackground = createBackgroundDispatcher({
589
300
  runtime,
590
301
  getEnvironment: () => {
@@ -596,7 +307,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
596
307
  finishRun,
597
308
  makeLiveHandler,
598
309
  makeDetails,
599
- runManagedWorkflow,
600
310
  });
601
311
  runtime.dispatcher = startBackground;
602
312
 
@@ -607,10 +317,10 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
607
317
  "Dispatch enabled agents as isolated leaf Pi child processes: single {agent, task} or parallel {tasks: [...]}. Dispatching never blocks your turn — runs proceed in the background and each completion resumes you automatically; never poll or restate delivered results.",
608
318
  "Put every genuinely independent unit in one `tasks` array: there is no per-call cap, and runs beyond the machine's free process slots simply wait and start as slots free.",
609
319
  "Parallel write-capable agents default to a detached Git worktree so writers run concurrently; explicit `shared` keeps the caller's checkout and serializes same-repository writers. Worktree setup failure never silently falls back to shared.",
610
- "Successful worker/cleaner runs get one automatic reviewer gate; pass review: \"none\" on a worker/cleaner task to skip it for mechanical, low-risk edits you verify yourself. A REVIEW_FAIL from a gate you dispatched directly returns its findings to you. A configured child-model failure continues the retained session on the current main model.",
320
+ "A configured child-model failure continues the retained session on the current main model.",
611
321
  ].join(" "),
612
322
  promptSnippet:
613
- "Dispatch isolated background agents for recon, implementation, cleanup, docs, or review; never blocks your turn, and REVIEW_FAIL findings return to you to fix.",
323
+ "Dispatch isolated background agents for recon, implementation, cleanup, docs sync, or result merging; never blocks your turn, and completions wake you automatically.",
614
324
  parameters: SubagentParams,
615
325
 
616
326
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -695,7 +405,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
695
405
  catalogAgent ? isWriteCapableAgent(catalogAgent) : undefined,
696
406
  catalogAgent?.isolation,
697
407
  ),
698
- { review: item.review as ReviewMode | undefined },
699
408
  ));
700
409
  }
701
410
  const startedRuns = results.filter((result) => result.exitCode === -1);
@@ -757,7 +466,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
757
466
  singleCatalogAgent ? isWriteCapableAgent(singleCatalogAgent) : undefined,
758
467
  singleCatalogAgent?.isolation,
759
468
  ),
760
- { review: params.review as ReviewMode | undefined },
761
469
  );
762
470
  if (result.exitCode !== -1) {
763
471
  throw new Error(getResultOutput(result));
@@ -783,8 +491,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
783
491
  for (const t of args.tasks.slice(0, 4)) {
784
492
  const preview = formatTaskSummary(t.task, 48);
785
493
  const isolation = defaultIsolationMode("parallel", t.agent, t.isolation) === "worktree" ? " [worktree]" : "";
786
- const gate = t.review === "none" ? " [no gate]" : "";
787
- text += `\n ${theme.fg("accent", t.agent)}${theme.fg("dim", `${isolation}${gate}`)} ${theme.fg("dim", preview)}`;
494
+ text += `\n ${theme.fg("accent", t.agent)}${theme.fg("dim", isolation)} ${theme.fg("dim", preview)}`;
788
495
  }
789
496
  if (args.tasks.length > 4) text += `\n ${theme.fg("dim", `… +${args.tasks.length - 4} more`)}`;
790
497
  return new Text(text, 0, 0);
@@ -792,9 +499,8 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
792
499
  const task: string = args.task ?? "";
793
500
  const preview = formatTaskSummary(task, 60);
794
501
  const isolation = args.isolation === "worktree" ? " [worktree]" : "";
795
- const gate = args.review === "none" ? " [no gate]" : "";
796
502
  return new Text(
797
- `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent ?? "?")}${theme.fg("dim", `${isolation}${gate}`)} ${theme.fg("dim", preview)}`,
503
+ `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent ?? "?")}${theme.fg("dim", `${isolation}`)} ${theme.fg("dim", preview)}`,
798
504
  0,
799
505
  0,
800
506
  );
package/src/durable.ts CHANGED
@@ -76,9 +76,6 @@ export interface ThreadRecord {
76
76
  executionCwd: string;
77
77
  thinkingLevel?: string;
78
78
  isolation: IsolationMode;
79
- /** Persisted only when the dispatch opted out of the automatic gate, so a
80
- * restored resume never surprises the caller with a full review. */
81
- review?: "none";
82
79
  state: "parked" | "completed" | "failed";
83
80
  elapsedMs: number;
84
81
  sessionId?: string;
@@ -171,7 +168,6 @@ function normalizeRecord(value: unknown): ThreadRecord | undefined {
171
168
  executionCwd: typeof raw.executionCwd === "string" && raw.executionCwd ? raw.executionCwd : raw.cwd,
172
169
  ...(typeof raw.thinkingLevel === "string" && raw.thinkingLevel ? { thinkingLevel: raw.thinkingLevel } : {}),
173
170
  isolation: raw.isolation,
174
- ...(raw.review === "none" ? { review: "none" as const } : {}),
175
171
  state: raw.state,
176
172
  elapsedMs: typeof raw.elapsedMs === "number" && Number.isFinite(raw.elapsedMs) ? Math.max(0, raw.elapsedMs) : 0,
177
173
  ...(typeof raw.sessionId === "string" && raw.sessionId ? { sessionId: raw.sessionId } : {}),
@@ -285,7 +281,6 @@ export function threadRecordFromThread(
285
281
  executionCwd: thread.executionCwd,
286
282
  ...(thread.thinkingLevel ? { thinkingLevel: thread.thinkingLevel } : {}),
287
283
  isolation: thread.isolation,
288
- ...(thread.review === "none" ? { review: "none" as const } : {}),
289
284
  state,
290
285
  elapsedMs: thread.elapsedMs,
291
286
  ...(thread.sessionId && thread.sessionDir ? { sessionId: thread.sessionId, sessionDir: thread.sessionDir } : {}),
package/src/format.ts CHANGED
@@ -154,14 +154,6 @@ export function modelLevelTakeoverNote(result: SingleResult, opts?: { runId?: nu
154
154
  return `The sub-agent could not complete this task: ${cause}${retry}.${recovery}`;
155
155
  }
156
156
 
157
- /** Instruction appended whenever a failing gate verdict is delivered: the
158
- * findings return to the main agent, which owns the fix decision — the runtime
159
- * never auto-fixes. Stating it at this exact decision point keeps the main
160
- * agent from relaying the findings to the user and stopping. */
161
- export function reviewFailFollowUpNote(): string {
162
- return "This gate failed and the findings are yours to resolve now: fix them inline or dispatch a worker briefed with these fix instructions, then re-verify the change. Ask the user only before a genuinely destructive or scope-changing fix; do not deliver while a finding stands.";
163
- }
164
-
165
157
  /** Resolve a run-id request to actual ids: an exact numeric match always wins
166
158
  * (so "1" never fans out to 10, 11, …); only when no exact match exists does a
167
159
  * prefix match run, as a convenience for partial ids. Keeps single-digit lookups