@quantiya/codevibe-core 2.0.1 → 2.0.3

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.
Files changed (284) hide show
  1. package/dist/__tests__/prompt-parser.test.d.ts +1 -0
  2. package/dist/adapter/registry.d.ts +69 -2
  3. package/dist/appsync/__tests__/appsync-client-event-source-filter.test.d.ts +1 -0
  4. package/dist/appsync/__tests__/appsync-client-task-group.test.d.ts +1 -0
  5. package/dist/appsync/__tests__/cleanup-subscription.test.d.ts +1 -0
  6. package/dist/appsync/__tests__/coerce-awsjson-and-review-summary.test.d.ts +1 -0
  7. package/dist/appsync/__tests__/cp1f-quorum-sdk.test.d.ts +1 -0
  8. package/dist/appsync/__tests__/event-throttle.test.d.ts +1 -0
  9. package/dist/appsync/appsync-client.d.ts +621 -10
  10. package/dist/appsync/event-throttle.d.ts +28 -0
  11. package/dist/appsync/index.d.ts +3 -0
  12. package/dist/appsync/queries.d.ts +19 -0
  13. package/dist/audit-keys/index.d.ts +32 -0
  14. package/dist/auth/auth-service.d.ts +32 -2
  15. package/dist/auth/auth-telemetry.d.ts +19 -1
  16. package/dist/auth/index.d.ts +1 -0
  17. package/dist/companion-mode/__tests__/detect-parallel.test.d.ts +1 -0
  18. package/dist/companion-mode/index.d.ts +4 -3
  19. package/dist/companion-mode/persist-preference.d.ts +1 -1
  20. package/dist/config/config.d.ts +8 -1
  21. package/dist/continuation/__tests__/dirty-state-collector.test.d.ts +1 -0
  22. package/dist/continuation/__tests__/integration.test.d.ts +1 -0
  23. package/dist/continuation/__tests__/packet-reader.test.d.ts +1 -0
  24. package/dist/continuation/__tests__/packet-writer.test.d.ts +1 -0
  25. package/dist/continuation/__tests__/parity.test.d.ts +1 -0
  26. package/dist/continuation/__tests__/status-cli.test.d.ts +1 -0
  27. package/dist/continuation/dirty-state-collector.d.ts +46 -0
  28. package/dist/continuation/index.d.ts +8 -0
  29. package/dist/continuation/packet-reader.d.ts +33 -0
  30. package/dist/continuation/packet-writer.d.ts +86 -0
  31. package/dist/continuation/types.d.ts +239 -0
  32. package/dist/credential-broker/__tests__/broker.test.d.ts +1 -0
  33. package/dist/credential-broker/__tests__/canonical.test.d.ts +1 -0
  34. package/dist/credential-broker/__tests__/scrubber.test.d.ts +1 -0
  35. package/dist/credential-broker/__tests__/upstream-client.test.d.ts +1 -0
  36. package/dist/credential-broker/__tests__/vendor-key-store.test.d.ts +1 -0
  37. package/dist/credential-broker/audit-sink.d.ts +69 -0
  38. package/dist/credential-broker/broker-config.d.ts +39 -0
  39. package/dist/credential-broker/broker-token.d.ts +123 -0
  40. package/dist/credential-broker/broker.d.ts +165 -0
  41. package/dist/credential-broker/canonical.d.ts +119 -0
  42. package/dist/credential-broker/index.d.ts +15 -0
  43. package/dist/credential-broker/scrubber.d.ts +45 -0
  44. package/dist/credential-broker/types.d.ts +141 -0
  45. package/dist/credential-broker/upstream-client.d.ts +20 -0
  46. package/dist/credential-broker/vendor-key-store.d.ts +54 -0
  47. package/dist/index.d.ts +6 -1
  48. package/dist/index.js +427 -124
  49. package/dist/keychain/__tests__/keychain-backend.test.d.ts +1 -0
  50. package/dist/keychain/keychain-backend.d.ts +82 -0
  51. package/dist/local-executor/__tests__/agentic-disposable-repo.test.d.ts +1 -0
  52. package/dist/local-executor/__tests__/agentic-resolve.test.d.ts +1 -0
  53. package/dist/local-executor/__tests__/agentic-resolver-flag.test.d.ts +1 -0
  54. package/dist/local-executor/__tests__/class-a-sign.test.d.ts +1 -0
  55. package/dist/local-executor/__tests__/class1-reconcilers.test.d.ts +1 -0
  56. package/dist/local-executor/__tests__/class2-resolver.test.d.ts +1 -0
  57. package/dist/local-executor/__tests__/confined-model-caller.test.d.ts +1 -0
  58. package/dist/local-executor/__tests__/conflict-classifier.test.d.ts +1 -0
  59. package/dist/local-executor/__tests__/conflict-resolution-durable-store.test.d.ts +1 -0
  60. package/dist/local-executor/__tests__/conflict-resolution-store.test.d.ts +1 -0
  61. package/dist/local-executor/__tests__/cp1f-quorum-consumer.test.d.ts +1 -0
  62. package/dist/local-executor/__tests__/cp7-audit-writer.test.d.ts +1 -0
  63. package/dist/local-executor/__tests__/durable-store.test.d.ts +1 -0
  64. package/dist/local-executor/__tests__/git-lock-retry.test.d.ts +1 -0
  65. package/dist/local-executor/__tests__/implementor-argv.test.d.ts +1 -0
  66. package/dist/local-executor/__tests__/local-executor-teams.test.d.ts +1 -0
  67. package/dist/local-executor/__tests__/merge-conflict-extract.test.d.ts +1 -0
  68. package/dist/local-executor/__tests__/merge-conflict-git.integration.test.d.ts +1 -0
  69. package/dist/local-executor/__tests__/merge-conflict-resolve-agentic.integration.test.d.ts +1 -0
  70. package/dist/local-executor/__tests__/merge-conflict-resolve.integration.test.d.ts +1 -0
  71. package/dist/local-executor/__tests__/merge-journal-store.test.d.ts +1 -0
  72. package/dist/local-executor/__tests__/merge-train-agentic-recovery.integration.test.d.ts +1 -0
  73. package/dist/local-executor/__tests__/merge-train-w3.integration.test.d.ts +1 -0
  74. package/dist/local-executor/__tests__/merge-train-waveb.integration.test.d.ts +1 -0
  75. package/dist/local-executor/__tests__/merge-train.test.d.ts +1 -0
  76. package/dist/local-executor/__tests__/promote-wal-store.test.d.ts +1 -0
  77. package/dist/local-executor/__tests__/revert-reconcile.test.d.ts +1 -0
  78. package/dist/local-executor/__tests__/revert-runner.test.d.ts +1 -0
  79. package/dist/local-executor/__tests__/shared-contract-detector.test.d.ts +1 -0
  80. package/dist/local-executor/__tests__/substrate-engage.test.d.ts +1 -0
  81. package/dist/local-executor/__tests__/team-execution-flag.test.d.ts +1 -0
  82. package/dist/local-executor/__tests__/track-registry.test.d.ts +1 -0
  83. package/dist/local-executor/__tests__/wb2-review-bundle.test.d.ts +1 -0
  84. package/dist/local-executor/__tests__/wb2-substrate-spawn.live.test.d.ts +1 -0
  85. package/dist/local-executor/__tests__/workspace-shadow.test.d.ts +1 -0
  86. package/dist/local-executor/__tests__/worktree-merge.test.d.ts +1 -0
  87. package/dist/local-executor/agentic-disposable-repo.d.ts +52 -0
  88. package/dist/local-executor/agentic-resolve.d.ts +54 -0
  89. package/dist/local-executor/agentic-resolver-flag.d.ts +9 -0
  90. package/dist/local-executor/authority.d.ts +33 -1
  91. package/dist/local-executor/class-a-emit.d.ts +3 -1
  92. package/dist/local-executor/class-a-sign.d.ts +57 -0
  93. package/dist/local-executor/class-b-consumer.d.ts +159 -4
  94. package/dist/local-executor/class-b-verify.d.ts +30 -0
  95. package/dist/local-executor/class1-reconcilers.d.ts +58 -0
  96. package/dist/local-executor/class2-resolver.d.ts +74 -0
  97. package/dist/local-executor/confined-model-caller.d.ts +39 -0
  98. package/dist/local-executor/conflict-classifier.d.ts +21 -0
  99. package/dist/local-executor/conflict-resolution-durable-store.d.ts +61 -0
  100. package/dist/local-executor/conflict-resolution-store.d.ts +210 -0
  101. package/dist/local-executor/conflict-types.d.ts +86 -0
  102. package/dist/local-executor/cp7-audit-writer.d.ts +152 -0
  103. package/dist/local-executor/durable-store.d.ts +334 -0
  104. package/dist/local-executor/git-lock-retry.d.ts +40 -0
  105. package/dist/local-executor/hook-bridge.d.ts +1 -1
  106. package/dist/local-executor/implementor-argv.d.ts +183 -0
  107. package/dist/local-executor/index.d.ts +44 -3
  108. package/dist/local-executor/local-executor-impl.d.ts +645 -4
  109. package/dist/local-executor/merge-conflict-extract.d.ts +34 -0
  110. package/dist/local-executor/merge-conflict-resolve.d.ts +101 -0
  111. package/dist/local-executor/merge-journal-store.d.ts +311 -0
  112. package/dist/local-executor/merge-train.d.ts +713 -0
  113. package/dist/local-executor/promote-wal-store.d.ts +301 -0
  114. package/dist/local-executor/resolution-set-index.d.ts +51 -0
  115. package/dist/local-executor/revert-reconcile.d.ts +89 -0
  116. package/dist/local-executor/revert-runner.d.ts +17 -0
  117. package/dist/local-executor/shared-contract-detector.d.ts +57 -0
  118. package/dist/local-executor/spawn.d.ts +29 -1
  119. package/dist/local-executor/task-group-types.d.ts +346 -0
  120. package/dist/local-executor/team-execution-flag.d.ts +21 -0
  121. package/dist/local-executor/track-registry.d.ts +50 -0
  122. package/dist/local-executor/types.d.ts +238 -4
  123. package/dist/local-executor/wb2-review-bundle.d.ts +59 -0
  124. package/dist/local-executor/workspace-shadow.d.ts +290 -0
  125. package/dist/local-executor/worktree-merge.d.ts +601 -0
  126. package/dist/local-model/__tests__/installer.test.d.ts +1 -0
  127. package/dist/local-model/__tests__/manager.test.d.ts +1 -0
  128. package/dist/local-model/__tests__/ollama.test.d.ts +1 -0
  129. package/dist/local-model/__tests__/runtime.test.d.ts +1 -0
  130. package/dist/local-model/__tests__/state.test.d.ts +1 -0
  131. package/dist/local-model/manager.d.ts +119 -0
  132. package/dist/local-model/ollama.d.ts +27 -0
  133. package/dist/local-model/runtime.d.ts +27 -0
  134. package/dist/local-model/state.d.ts +32 -0
  135. package/dist/orchestration/detect-agents.d.ts +15 -2
  136. package/dist/orchestration/setup-types.d.ts +2 -1
  137. package/dist/orchestration-shell/__tests__/attachments.test.d.ts +1 -0
  138. package/dist/orchestration-shell/__tests__/audit-browser.test.d.ts +1 -0
  139. package/dist/orchestration-shell/__tests__/audited-path-fixes.test.d.ts +1 -0
  140. package/dist/orchestration-shell/__tests__/bracketed-paste.test.d.ts +1 -0
  141. package/dist/orchestration-shell/__tests__/cli-group-halt-wiring.test.d.ts +1 -0
  142. package/dist/orchestration-shell/__tests__/cli-orchestration-session-bootstrap.test.d.ts +1 -0
  143. package/dist/orchestration-shell/__tests__/cli-probe-group-status.test.d.ts +1 -0
  144. package/dist/orchestration-shell/__tests__/cli-quorum-loop-tier.test.d.ts +1 -0
  145. package/dist/orchestration-shell/__tests__/cli-verdict-classifier-wiring.test.d.ts +1 -0
  146. package/dist/orchestration-shell/__tests__/context-store.test.d.ts +1 -0
  147. package/dist/orchestration-shell/__tests__/cp10-585-accept-echo-target.test.d.ts +1 -0
  148. package/dist/orchestration-shell/__tests__/cp10-585-loop.test.d.ts +1 -0
  149. package/dist/orchestration-shell/__tests__/cp10-585-prompts.test.d.ts +1 -0
  150. package/dist/orchestration-shell/__tests__/cp12-w2b-team-loop.test.d.ts +1 -0
  151. package/dist/orchestration-shell/__tests__/cp12-w4-team-recovery.test.d.ts +1 -0
  152. package/dist/orchestration-shell/__tests__/cp1f-quorum-loop.test.d.ts +1 -0
  153. package/dist/orchestration-shell/__tests__/cp606-team-track-user-resolved.test.d.ts +1 -0
  154. package/dist/orchestration-shell/__tests__/cp7-reviewer-substrate.test.d.ts +1 -0
  155. package/dist/orchestration-shell/__tests__/declared-test-runner.test.d.ts +1 -0
  156. package/dist/orchestration-shell/__tests__/familiarize-routing.test.d.ts +1 -0
  157. package/dist/orchestration-shell/__tests__/gate-decision-submit.test.d.ts +1 -0
  158. package/dist/orchestration-shell/__tests__/gate-details-panel.test.d.ts +1 -0
  159. package/dist/orchestration-shell/__tests__/gate-verdict-details-dispatch.test.d.ts +1 -0
  160. package/dist/orchestration-shell/__tests__/group-decision-submit.test.d.ts +1 -0
  161. package/dist/orchestration-shell/__tests__/image-attach-e2e.test.d.ts +1 -0
  162. package/dist/orchestration-shell/__tests__/input-bar-autocomplete.test.d.ts +1 -0
  163. package/dist/orchestration-shell/__tests__/live-region-cap.test.d.ts +1 -0
  164. package/dist/orchestration-shell/__tests__/live-static-order.test.d.ts +1 -0
  165. package/dist/orchestration-shell/__tests__/model-cli.test.d.ts +1 -0
  166. package/dist/orchestration-shell/__tests__/non-gate-prompt-routing.test.d.ts +1 -0
  167. package/dist/orchestration-shell/__tests__/orchestration-app-group-decision-transport.test.d.ts +1 -0
  168. package/dist/orchestration-shell/__tests__/orchestration-app-layout.test.d.ts +1 -0
  169. package/dist/orchestration-shell/__tests__/reissue-team-retained-spec.test.d.ts +1 -0
  170. package/dist/orchestration-shell/__tests__/review-summary-render.test.d.ts +1 -0
  171. package/dist/orchestration-shell/__tests__/reviewer-wizard.test.d.ts +1 -0
  172. package/dist/orchestration-shell/__tests__/reviewers-format.test.d.ts +1 -0
  173. package/dist/orchestration-shell/__tests__/root-discovery.test.d.ts +1 -0
  174. package/dist/orchestration-shell/__tests__/serialize-submissions.test.d.ts +1 -0
  175. package/dist/orchestration-shell/__tests__/slash-suggest.test.d.ts +1 -0
  176. package/dist/orchestration-shell/__tests__/status-format.test.d.ts +1 -0
  177. package/dist/orchestration-shell/__tests__/task-progress.test.d.ts +1 -0
  178. package/dist/orchestration-shell/__tests__/team-decompose-routing.test.d.ts +1 -0
  179. package/dist/orchestration-shell/__tests__/team-decompose-types.test.d.ts +1 -0
  180. package/dist/orchestration-shell/__tests__/team-decompose.test.d.ts +1 -0
  181. package/dist/orchestration-shell/__tests__/team-e2e-smoke.test.d.ts +1 -0
  182. package/dist/orchestration-shell/__tests__/tier2-reviewer-selection.test.d.ts +1 -0
  183. package/dist/orchestration-shell/__tests__/web-browsing.test.d.ts +1 -0
  184. package/dist/orchestration-shell/attachments.d.ts +152 -0
  185. package/dist/orchestration-shell/audit-browser.d.ts +115 -0
  186. package/dist/orchestration-shell/audit-runner.d.ts +59 -0
  187. package/dist/orchestration-shell/bracketed-paste.d.ts +26 -0
  188. package/dist/orchestration-shell/cli.d.ts +336 -1
  189. package/dist/orchestration-shell/cli.js +40580 -4692
  190. package/dist/orchestration-shell/components/AuditLogPanel.d.ts +11 -0
  191. package/dist/orchestration-shell/components/ConversationPane.d.ts +7 -1
  192. package/dist/orchestration-shell/components/GateDetailsPanel.d.ts +57 -0
  193. package/dist/orchestration-shell/components/GatePanelEntry.d.ts +9 -0
  194. package/dist/orchestration-shell/components/GatePromptEntry.d.ts +7 -0
  195. package/dist/orchestration-shell/components/InputBar.d.ts +17 -3
  196. package/dist/orchestration-shell/components/LiveProgressLine.d.ts +8 -0
  197. package/dist/orchestration-shell/components/MultiTrackStatusNode.d.ts +8 -0
  198. package/dist/orchestration-shell/components/OrchestrationApp.d.ts +81 -15
  199. package/dist/orchestration-shell/components/ReviewerSetupWizard.d.ts +24 -0
  200. package/dist/orchestration-shell/components/StatusBar.d.ts +15 -2
  201. package/dist/orchestration-shell/context-store.d.ts +389 -0
  202. package/dist/orchestration-shell/declared-test-runner.d.ts +84 -0
  203. package/dist/orchestration-shell/emit-shell-event.d.ts +1 -1
  204. package/dist/orchestration-shell/gate-decision-submit.d.ts +245 -0
  205. package/dist/orchestration-shell/gate-prompts.d.ts +361 -3
  206. package/dist/orchestration-shell/index.d.ts +613 -1
  207. package/dist/orchestration-shell/ink-runtime.d.ts +21 -1
  208. package/dist/orchestration-shell/live-region-cap.d.ts +70 -0
  209. package/dist/orchestration-shell/live-static-order.d.ts +22 -0
  210. package/dist/orchestration-shell/non-tty-fallback.d.ts +21 -0
  211. package/dist/orchestration-shell/quorum-loop.d.ts +1900 -0
  212. package/dist/orchestration-shell/root-discovery.d.ts +25 -0
  213. package/dist/orchestration-shell/route-browse.d.ts +20 -0
  214. package/dist/orchestration-shell/slash-router.d.ts +20 -1
  215. package/dist/orchestration-shell/slash-routes/continuation.d.ts +71 -0
  216. package/dist/orchestration-shell/slash-routes/reviewers.d.ts +41 -0
  217. package/dist/orchestration-shell/slash-suggest.d.ts +29 -0
  218. package/dist/orchestration-shell/status-format.d.ts +59 -0
  219. package/dist/orchestration-shell/task-progress.d.ts +122 -0
  220. package/dist/orchestration-shell/team-decompose.d.ts +196 -0
  221. package/dist/orchestration-shell/types.d.ts +363 -3
  222. package/dist/orchestration-shell/vendor/ink-parse-keypress.d.ts +42 -0
  223. package/dist/orchestration-shell/web/extract.d.ts +11 -0
  224. package/dist/orchestration-shell/web/fetch.d.ts +15 -0
  225. package/dist/orchestration-shell/web/ip.d.ts +26 -0
  226. package/dist/orchestration-shell/web/sanitize.d.ts +14 -0
  227. package/dist/orchestration-shell/web/search.d.ts +16 -0
  228. package/dist/planner/__tests__/local-gemma.test.d.ts +1 -0
  229. package/dist/planner/index.d.ts +2 -0
  230. package/dist/planner/local-advisory.d.ts +84 -0
  231. package/dist/planner/local-gemma.d.ts +23 -0
  232. package/dist/planner/types.d.ts +16 -6
  233. package/dist/reviewer/__tests__/antigravity-provider.test.d.ts +1 -0
  234. package/dist/reviewer/__tests__/claude-provider-json.test.d.ts +1 -0
  235. package/dist/reviewer/__tests__/cp7-reviewer-sandbox.test.d.ts +1 -0
  236. package/dist/reviewer/__tests__/output-parser-classifier-helpers.test.d.ts +1 -0
  237. package/dist/reviewer/__tests__/verdict-classifier.test.d.ts +1 -0
  238. package/dist/reviewer/index.d.ts +2 -0
  239. package/dist/reviewer/output-parser.d.ts +48 -12
  240. package/dist/reviewer/provider.d.ts +107 -3
  241. package/dist/reviewer/providers/__tests__/failure-classifier.test.d.ts +1 -0
  242. package/dist/reviewer/providers/antigravity.d.ts +49 -0
  243. package/dist/reviewer/providers/claude.d.ts +19 -4
  244. package/dist/reviewer/providers/codex.d.ts +47 -5
  245. package/dist/reviewer/providers/failure-classifier.d.ts +26 -0
  246. package/dist/reviewer/providers/gemini.d.ts +5 -4
  247. package/dist/reviewer/registry.d.ts +5 -5
  248. package/dist/reviewer/subprocess.d.ts +37 -0
  249. package/dist/reviewer/token-usage.d.ts +32 -0
  250. package/dist/reviewer/types.d.ts +5 -5
  251. package/dist/reviewer/verdict-classifier.d.ts +62 -0
  252. package/dist/session/__tests__/prepare-event-timestamp.test.d.ts +1 -0
  253. package/dist/session/index.d.ts +1 -0
  254. package/dist/session/prepare-event-timestamp.d.ts +30 -0
  255. package/dist/substrate/__tests__/docker.test.d.ts +1 -0
  256. package/dist/substrate/__tests__/forwarder.test.d.ts +1 -0
  257. package/dist/substrate/__tests__/git-proxy.test.d.ts +1 -0
  258. package/dist/substrate/__tests__/sandbox-exec.test.d.ts +1 -0
  259. package/dist/substrate/__tests__/substrate-e2e.integration.test.d.ts +1 -0
  260. package/dist/substrate/command-runner.d.ts +19 -0
  261. package/dist/substrate/docker.d.ts +134 -0
  262. package/dist/substrate/errors.d.ts +33 -0
  263. package/dist/substrate/forwarder.d.ts +67 -0
  264. package/dist/substrate/git-proxy.d.ts +35 -0
  265. package/dist/substrate/index.d.ts +12 -0
  266. package/dist/substrate/relay-script.d.ts +27 -0
  267. package/dist/substrate/sandbox-exec.d.ts +116 -0
  268. package/dist/substrate/types.d.ts +218 -0
  269. package/dist/substrate-launch/__tests__/apikey-bootstrap.test.d.ts +1 -0
  270. package/dist/substrate-launch/__tests__/creditless-loop.integration.test.d.ts +1 -0
  271. package/dist/substrate-launch/__tests__/engage-substrate.test.d.ts +1 -0
  272. package/dist/substrate-launch/__tests__/fallback-ladder.test.d.ts +1 -0
  273. package/dist/substrate-launch/__tests__/le-env-scrub.test.d.ts +1 -0
  274. package/dist/substrate-launch/__tests__/sanitized-env.test.d.ts +1 -0
  275. package/dist/substrate-launch/apikey-bootstrap.d.ts +84 -0
  276. package/dist/substrate-launch/engage-substrate.d.ts +102 -0
  277. package/dist/substrate-launch/fallback-ladder.d.ts +49 -0
  278. package/dist/substrate-launch/index.d.ts +12 -0
  279. package/dist/substrate-launch/le-env-scrub.d.ts +77 -0
  280. package/dist/substrate-launch/sanitized-env.d.ts +73 -0
  281. package/dist/types/auth.d.ts +6 -1
  282. package/dist/types/events.d.ts +3 -1
  283. package/dist/types/reviewer.d.ts +6 -6
  284. package/package.json +7 -3
@@ -0,0 +1,1900 @@
1
+ import type { AppSyncClient, TeamTrackRecoveryRow } from '../appsync/appsync-client';
2
+ import type { Session } from '../types';
3
+ import type { LocalExecutor } from '../local-executor';
4
+ import type { ImageAttachment } from './types';
5
+ import { type ImplementorMode } from '../local-executor/implementor-argv';
6
+ import type { ConflictModelCaller } from '../local-executor/class2-resolver';
7
+ import type { ConflictResolverContext } from '../local-executor/merge-conflict-resolve';
8
+ import type { AgentKind } from '../local-executor/types';
9
+ import type { GateDispatchPayload, ReviewerDispatchPayload, ReviseFeedbackPayload } from '../local-executor/types';
10
+ import type { TeamTrackFailureReason } from '../local-executor/task-group-types';
11
+ import { WorkspaceShadow, type ShadowEnv } from '../local-executor/workspace-shadow';
12
+ import type { RevertManifestStore } from '../local-executor/durable-store';
13
+ import { type TeamExecution } from '../local-executor/team-execution-flag';
14
+ import { type RenderedConflict, type TrackIntentDiff } from '../local-executor/wb2-review-bundle';
15
+ import { ReviewerRegistry } from '../reviewer/registry';
16
+ import { type ReviewerFailureReason } from '../reviewer/provider';
17
+ import type { SubstrateHandle } from '../substrate/types';
18
+ import type { VerdictClassifier } from '../reviewer/verdict-classifier';
19
+ import type { AgentKind as ReviewerAgentKind, ReviewerRole } from '../reviewer/types';
20
+ import type { OnProgress } from './task-progress';
21
+ import { runShellCommand } from '../local-executor/verification-gates/shell-runner';
22
+ import type { PostDecisionAction, Tier } from './types';
23
+ import type { ContinuationPacketWriter } from '../continuation/packet-writer';
24
+ import type { ContinuationPacketReader } from '../continuation/packet-reader';
25
+ import type { HandoffReason } from '../continuation/types';
26
+ /**
27
+ * CP-1.f — the clean, user-facing one-liner for a synthesized ESCALATE verdict
28
+ * whose origin is a reviewer subprocess failure. Built ONLY from the closed
29
+ * `ReviewerFailureReason` + the agent display name via the fixed template
30
+ * table. Never derived from raw provider output. Exported for the
31
+ * sanitization test.
32
+ */
33
+ export declare function failureReasonTemplate(agentKind: ReviewerAgentKind, reason: ReviewerFailureReason): string;
34
+ /**
35
+ * CP-1.f implementor-round-failure surfacing (dogfood fix 2026-06-10) — classify
36
+ * a thrown implementor-round error into a SAFE, classified label. NEVER returns
37
+ * the raw error message / stderr (the wire-safety rule shared with progress
38
+ * events + synthesized reviewer reasoning).
39
+ *
40
+ * - `AuthorityError` → the closed-category label (`authority_scope_conflict` →
41
+ * "blocked by the local authority allowlist", etc.).
42
+ * - A spawn/start failure (ENOENT / "spawn" / "not found" on the agent binary)
43
+ * → a generic "the agent could not be started" label.
44
+ * - Anything else → a generic "an unexpected error occurred" label.
45
+ *
46
+ * Exported for the sanitization test (proves no raw message leaks).
47
+ */
48
+ export declare function classifyImplementorRoundFailure(err: unknown): string;
49
+ /**
50
+ * PHASE-CP-10-MIN (#585) §3.6 H-C / L1 — classify an implementor failure as a
51
+ * QUOTA exhaustion (a usage-limit / rate-limit error from the coding-agent CLI).
52
+ * Returns ONLY a boolean (the `quota_exhausted` label); `resetAt` extraction is
53
+ * CP-13's, not #585's (Non-goal §1.3).
54
+ *
55
+ * The discriminator is the `text` (`exitCode` is informational context — a
56
+ * usage-limit exit is typically nonzero, but the stderr text is authoritative).
57
+ * Reads the raw stderr / error message LOCALLY ONLY — never on the wire
58
+ * (wire-safety; only the closed `quota_exhausted` LABEL crosses the wire).
59
+ *
60
+ * Fixtures (L1):
61
+ * - Codex (PINNED — CP-13 §0/§2-D1 live sample): `You've hit your usage limit
62
+ * … try again at <date>` → match on `usage limit` (case-insensitive).
63
+ * - Claude / Gemini: TBD — CP-13 has not captured real samples. Inventing
64
+ * strings risks a FALSE-NEGATIVE classifier that misses a real quota error,
65
+ * so they are left as additive one-liners to fill once real samples land.
66
+ * An UNMATCHED quota error degrades FAIL-SAFE to the existing born-halt
67
+ * `surfaceHalt` (the user just doesn't get auto-continuation; identical to
68
+ * today) — NOT fail-open.
69
+ *
70
+ * Exported for the L1 fixture test (I5).
71
+ */
72
+ export declare function classifyImplementorQuotaFailure(exitCode: number | undefined, text: string): boolean;
73
+ /**
74
+ * Audited-path fix (Fix 1) — build the SAFE, no-plaintext desktop advisory text
75
+ * for a hosted-side PolicyRejection. Uses ONLY the closed `category` classifier
76
+ * label (a hosted-side enum, NOT user prose — already carried on the existing
77
+ * mobile NOTIFICATION) + the closed `recommendedRecovery` enum's recovery hint.
78
+ * The raw `detail` (potential plaintext) is NEVER included. Exported for the
79
+ * sanitization test. An unknown recovery value falls back to the generic
80
+ * `ask_user` hint so the advisory always reads cleanly.
81
+ */
82
+ export declare function policyRejectionAdvisoryText(info: {
83
+ category: string;
84
+ recommendedRecovery: string;
85
+ }): string;
86
+ /**
87
+ * Audited-path fix (Fix 5) — select the implementor `AgentKind` for a START.
88
+ *
89
+ * - An explicit `override` (from `/task --agent codex|claude`) wins, BUT only
90
+ * when that agent was actually detected on this host (else fail-closed to the
91
+ * default — we never spawn a binary the host doesn't have).
92
+ * - Otherwise: CLAUDE when it is among the detected agents (the documented
93
+ * default), else the FIRST detected agent, else CLAUDE as the universal
94
+ * fail-safe (matches the engine's Claude-only fallback when the host detected
95
+ * nothing). The implementor argv allowlist already covers all three agents,
96
+ * so any returned kind is spawnable without widening it.
97
+ *
98
+ * Returns `{ agent, note }` — `note` is a non-null user-facing string ONLY when
99
+ * an explicit override could not be honored (so the shell can surface it); null
100
+ * when the selection was clean. NEVER throws.
101
+ */
102
+ export declare function selectImplementorAgent(detected: readonly AgentKind[], override?: AgentKind | null): {
103
+ agent: AgentKind;
104
+ note: string | null;
105
+ };
106
+ /**
107
+ * CP-14 WB2 §2.4 (LOCK #C14-WB2-TIER2-INDEPENDENT / #C14-MOAT output-review half) — pick the
108
+ * final-tip Tier-2 reviewer agent: the strongest HOST-DETECTED reviewer, best-effort INDEPENDENT
109
+ * of the class-2 resolver agent(s) this gate.
110
+ *
111
+ * - `override` (forward-compat; no desktop surface today) wins, but ONLY when host-detected
112
+ * (never spawn an undetected binary — same posture as {@link selectImplementorAgent}).
113
+ * - else the strongest detected agent (lowest {@link TIER2_REVIEWER_RANK} index) whose kind is
114
+ * NOT in `resolverAgents` (independence). If every detected agent also resolved (single-agent
115
+ * host, or ≥2 resolver agents covering all detected), fall back to the strongest detected
116
+ * overall — a same-agent Tier-2 is still a FRESH adversarial review of the output.
117
+ * - empty detected → `null` → the gate NO-PROMOTEs (the moat's output-review half).
118
+ *
119
+ * Inputs/outputs are LE-UPPERCASE `AgentKind`; the dispatcher bridges the result to the lowercase
120
+ * reviewer wire kind via {@link leAgentToReviewerAgent}. `resolverAgents` is a set of raw strings
121
+ * (the §4.5 record's `implementorAgent`); non-AgentKind values are simply absent from `.has`.
122
+ * Pure; NEVER throws.
123
+ */
124
+ export declare function selectFinalTier2Reviewer(detected: readonly AgentKind[], resolverAgents: ReadonlySet<string>, override?: AgentKind | null): AgentKind | null;
125
+ /**
126
+ * CP-14 WB2 §2.4 fallback (dogfood-pinned 2026-07-03) — the FULL ordered Tier-2
127
+ * candidate list, same selection rules as {@link selectFinalTier2Reviewer}
128
+ * (which is now its head): an explicit host-detected override first, then the
129
+ * independent candidates by rank, then the resolver-agent candidates by rank
130
+ * (a same-agent Tier-2 is still a fresh adversarial review of the output — the
131
+ * documented last resort). The dispatcher walks this list when a candidate
132
+ * fails to SPAWN/confine (host run 16: the top-ranked independent reviewer
133
+ * (Codex) died in-sandbox at boot and the merge REJECTED with two healthy
134
+ * reviewers standing by). A real VERDICT — pass or reject — is authoritative
135
+ * and never falls through. Pure; NEVER throws.
136
+ */
137
+ export declare function selectFinalTier2Candidates(detected: readonly AgentKind[], resolverAgents: ReadonlySet<string>, override?: AgentKind | null): AgentKind[];
138
+ /**
139
+ * CP-7 §8 (Stage-1-resolved) — the narrow result the QuorumLoop consumes from
140
+ * the reviewer substrate engager. Mirrors `SubstrateLaunch.EngageResult` /
141
+ * `LocalExecutorImpl.SubstrateEngageResult` but is declared STRUCTURALLY here
142
+ * (not importing the concrete `engageSubstrate`) so the loop module stays
143
+ * decoupled and tests inject a stub. Two variants:
144
+ * - `substrate` (the moat is ON for this seat — the loop threads
145
+ * `substrateHandle` into `evaluate` + calls `teardown()` when the seat
146
+ * completes, on EVERY path).
147
+ * - `reduced_trust` (the moat is OFF for this seat — A5 coarse host with no
148
+ * runtime / Gemini non-Trusted: the loop runs the reviewer on the LEGACY
149
+ * unsandboxed path + surfaces the reduced-trust badge).
150
+ *
151
+ * THROW semantics (fail-closed): the engager THROWS for a Trusted agent when
152
+ * the broker cannot start (or any non-`reduced_trust` engage failure). The
153
+ * loop treats a throw as FAIL-CLOSED — it does NOT silently fall back to the
154
+ * ambient-cred legacy path (that would defeat the moat); it synthesizes a
155
+ * clean ESCALATE so the gate resolves rather than hangs.
156
+ */
157
+ export type ReviewerSubstrateEngageResult = {
158
+ mode: 'substrate';
159
+ tier: 'docker' | 'sandbox_exec';
160
+ reducedTrust: boolean;
161
+ reducedTrustReason?: string;
162
+ substrateHandle: SubstrateHandle;
163
+ teardown(): Promise<void>;
164
+ } | {
165
+ mode: 'reduced_trust';
166
+ reducedTrust: true;
167
+ reducedTrustReason: string;
168
+ teardown(): Promise<void>;
169
+ };
170
+ /**
171
+ * CP-7 §8 (Stage-1-resolved) — engage the CP-7 trusted-execution substrate +
172
+ * broker for ONE reviewer seat (mirrors the implementor's per-spawn engager).
173
+ * The orchestration shell wires this to `SubstrateLaunch.engageSubstrate`
174
+ * (with `role: 'reviewer'`), translating the reviewer's lowercase `agent`
175
+ * (`claude`/`codex`/`gemini`) into the engager's UPPERCASE `agentKind`.
176
+ *
177
+ * ABSENT on `QuorumLoopDeps` (legacy / companion / tests) → reviewers take the
178
+ * legacy unsandboxed path UNCHANGED (byte-identical non-regression). PRESENT →
179
+ * each Trusted-agent reviewer seat engages the moat (no ambient vendor creds;
180
+ * model call via the broker), with the §6 Q4 GEMINI / reduced-trust handling
181
+ * the engager performs internally (returns `reduced_trust`).
182
+ */
183
+ export type ReviewerSubstrateEngager = (input: {
184
+ taskId: string;
185
+ /** The reviewer's lowercase agent (the loop maps it to UPPERCASE inside the wiring). */
186
+ agent: ReviewerAgentKind;
187
+ /** Canonical host workdir the sandbox mounts (the loop's working dir). */
188
+ workdir: string;
189
+ }) => Promise<ReviewerSubstrateEngageResult>;
190
+ export interface QuorumLoopDeps {
191
+ appsyncClient: AppSyncClient;
192
+ session: Session;
193
+ /** The team-mode LE (used for spawnImplementor + spawnReviewer). */
194
+ localExecutor: LocalExecutor;
195
+ /** Reviewer registry (production: createSubprocessReviewerRegistry()). */
196
+ reviewerRegistry: ReviewerRegistry;
197
+ /**
198
+ * REVIEWER-VERDICT-GEMMA-CLASSIFIER-DESIGN.md (D1/§Wiring) — the local-Gemma
199
+ * verdict classifier, forwarded on the `evaluate` opts at BOTH reviewer sites
200
+ * (per-seat + Wave-B Tier-2) so every reviewer verdict is determined by the
201
+ * fail-closed classifier instead of the regex `parseVerdictOutput`. Under the
202
+ * default `gemma` mode this is ALWAYS present (the real classifier, or a dummy
203
+ * always-ESCALATE classifier when the Gemma runner is null at boot). ABSENT ⇒
204
+ * the providers use the regex parser — reachable ONLY when the operator sets
205
+ * `CODEVIBE_REVIEWER_VERDICT_CLASSIFIER=regex` (a deliberate `undefined`,
206
+ * NEVER an accidental missing-classifier fallback).
207
+ */
208
+ classifyVerdict?: VerdictClassifier;
209
+ /** Resolve the session key for E2E encrypt/decrypt. */
210
+ getSessionKey: (sessionId: string) => Promise<string | null>;
211
+ /** Working dir for spawned subprocesses (canonicalized). */
212
+ workingDir: string;
213
+ /**
214
+ * CP-14 Wave A — the team execution model. `worktree_merge` makes each team
215
+ * track commit to a persistent branch (the merge-before-push spine) instead of
216
+ * the merge-train promote (the only model since the §10 retirement). Omitted keeps the
217
+ * shipped path byte-identical. Resolved from `CODEVIBE_TEAM_EXECUTION` when
218
+ * unset (see {@link resolveTeamExecution}).
219
+ */
220
+ teamExecution?: TeamExecution;
221
+ /**
222
+ * CP-7 Stage-2 r3 (Codex) HIGH — ARM the LE's current/authorized task id so
223
+ * the round-0 (and revise) implementor spawn engages the CP-7 trusted-execution
224
+ * substrate instead of the pre-TaskAuthorized reduced-trust path. `startTask`
225
+ * threads the minted taskId here BEFORE the round-0 spawn drains (and rolls it
226
+ * back to `null` on a START rejection); production wires it to
227
+ * `LocalExecutorImpl.setActiveTaskId`. ABSENT (legacy/tests) → the LE's task id
228
+ * is unchanged (the pre-fix behavior); production MUST wire it so the
229
+ * implementor never spawns unbadged/unsandboxed. The same state
230
+ * (`LocalExecutorImpl.taskId`) drives `engageSubstrateForSpawn` (local-executor-
231
+ * impl.ts:566) to call `substrateEngager` rather than emit a `tier:'none'` badge.
232
+ */
233
+ armLeTaskId?: (taskId: string | null) => void;
234
+ /**
235
+ * CP-7 §8 (Stage-1-resolved) — engage the CP-7 trusted-execution substrate +
236
+ * broker for ONE reviewer seat. When wired (production via cli.ts →
237
+ * `SubstrateLaunch.engageSubstrate` with `role: 'reviewer'`), each Trusted-
238
+ * agent reviewer runs INSIDE the sandbox with no ambient vendor creds and a
239
+ * broker-routed model call — closing the CP-14 §8/§13 reviewer-sandbox gap.
240
+ * ABSENT (legacy / companion / tests) → reviewers take the legacy unsandboxed
241
+ * path BYTE-IDENTICAL (§7 non-regression). Reviewer-sandboxing is ALSO gated
242
+ * behind an opt-in env flag (`CODEVIBE_SANDBOX_REVIEWERS=1`, read in
243
+ * `engageSeatSubstrate` on the `spawnOneSeat` path) that DEFAULTS OFF — so
244
+ * even when this engager is wired,
245
+ * reviewers stay on the legacy path until the broker round-trip is observed
246
+ * green in a configured env (see `reviewerSandboxingEnabled`). The flag is
247
+ * independent of implementor sandboxing (which has its own engager).
248
+ */
249
+ engageReviewerSubstrate?: ReviewerSubstrateEngager;
250
+ /**
251
+ * CP-1.f LOCK RP-6 — this orchestration host's detected coding agents
252
+ * (UPPERCASE `CLAUDE` / `CODEX` / `GEMINI`), computed ONCE by `cli.ts` via
253
+ * `detectInstalledAgents` (the SAME UPPERCASE string form
254
+ * `pushDetectedAgents` sends). Threaded here so `startTask` carries it IN
255
+ * the request and the gate row's availability is stamped race-free from
256
+ * THIS host — closing the cross-device shared-`User.availableAgents`
257
+ * TOCTOU (a legacy companion daemon on another device can no longer union
258
+ * a stale agent back in between the host's write and the engine's read).
259
+ *
260
+ * The `[]` vs ABSENT distinction is load-bearing engine-side: an EMPTY
261
+ * array (`[]` — host detected nothing) is sent VERBATIM (→ Claude-only
262
+ * fail-safe, no Users read). ONLY a fully-absent dep (legacy/tests →
263
+ * `undefined`) omits the field (→ engine Users-table fallback). So
264
+ * production wires this to a (possibly empty) array; it does NOT leave it
265
+ * `undefined`.
266
+ */
267
+ detectedAgents?: string[];
268
+ /**
269
+ * #585 auto-continuation (Max) — the resolved subscription tier (from
270
+ * `resumeOrCreateSession`: env override → getSubscriptionStatus → FREE).
271
+ * MAX → a quota-exhausted continuation handoff AUTO-ACCEPTS on the strongest
272
+ * available agent (no prompt). Pro/Free/unresolved/absent → the existing
273
+ * `continuation_offer_handoff` prompt (fail-closed). Auto-continuation only
274
+ * affects the user's OWN agents, so the resolved tier is sufficient (no
275
+ * separate per-request entitlement fetch).
276
+ */
277
+ tier?: Tier;
278
+ /** Sleep helper (injected for deterministic tests). */
279
+ sleep?: (ms: number) => Promise<void>;
280
+ /**
281
+ * CP-1.f execute-first (§4) — shadow lifecycle environment (state/shadow dir
282
+ * roots, TTL, child-kill hook, sleep). Injected for deterministic tests;
283
+ * production uses the defaults (`~/.codevibe/shadow-state`, `os.tmpdir()`).
284
+ */
285
+ shadowEnv?: ShadowEnv;
286
+ /**
287
+ * CP-1.f execute-first (#EF-1 / #SR-2) — surface a user-visible halt /
288
+ * fail-closed message to the orchestration timeline. Wired by the shell to an
289
+ * `emitShellEvent` NOTIFICATION. ABSENT (tests) → logged only.
290
+ */
291
+ surfaceHalt?: (message: string) => void;
292
+ /**
293
+ * PHASE-589/469 W1 (LOCK #589-E) — flip the desktop's OWN team track to
294
+ * `Failed` + render the group-terminal from LOCAL state. Wired by the TEAM
295
+ * shell to emit a `team_track_terminal` (state `Failed`) shell event through
296
+ * the SAME store-tap chokepoint the MergeGate convergence uses, then evaluate
297
+ * local `all-terminal && any-failed` → render "Team halted — track N
298
+ * failed". TEAM-only — ABSENT on the single-impl path (and in tests) → no-op.
299
+ * The QuorumLoop reports the engine marker (`submitTrackVerificationOutcome`)
300
+ * separately; this callback drives the DESKTOP-side render only.
301
+ */
302
+ onTeamTrackFailed?: (args: {
303
+ taskGroupId: string;
304
+ trackIndex: number;
305
+ reason: TeamTrackFailureReason;
306
+ }) => void;
307
+ /**
308
+ * PHASE-589/469 W2 — flip the desktop's OWN team track to `Revising` while an
309
+ * auto-revise round is in flight. Wired by the TEAM shell to emit a
310
+ * `team_track_terminal` (state `Revising`) shell event through the SAME
311
+ * store-tap chokepoint the convergence + W1-fail render use. Fired ONLY when
312
+ * the engine's `submitTrackVerificationOutcome` marker AUTO-REVISED (the
313
+ * returned `trackState` is NOT `Failed`) — the track stays InFlight and the
314
+ * desktop re-spawns its implementor on the inbound `REVISE_FEEDBACK`. On
315
+ * cap-exhaust the marker returns `Failed` instead and `onTeamTrackFailed`
316
+ * fires (W1's terminal render). TEAM-only — ABSENT on single-impl (and in
317
+ * tests) → no-op.
318
+ */
319
+ onTeamTrackRevising?: (args: {
320
+ taskGroupId: string;
321
+ trackIndex: number;
322
+ }) => void;
323
+ /**
324
+ * #606 (FACE A) — flip the desktop's OWN team track terminal the moment the
325
+ * USER resolves that track's per-track gate (final_approval Approve/Cancel, or
326
+ * the escalation 5-option Accept/Abort). Closes the #469 "track stuck
327
+ * InFlight" gap: the per-track USER decision reached NONE of the render-
328
+ * terminal writers (only `emitGroupConvergence` + the W1 marker path did), so a
329
+ * user-resolved track stayed `InFlight` in the render until the slow engine
330
+ * convergence/sweeper. Emits a `team_track_terminal` (state Passed/Failed)
331
+ * shell event through the SAME `leEmitShellEvent` chokepoint the convergence +
332
+ * W1/W2 callbacks use — but withOUT a `reason` label, so `routeTeamShell
333
+ * EventToStore` does the track flip ONLY (it does NOT take the W1 local group-
334
+ * terminal render branch, which is `reason`-gated; the all-or-nothing GROUP
335
+ * gate is driven authoritatively by the engine's `TaskGroupHalted` emit, #606
336
+ * FACE B). TEAM-only — ABSENT on the single-impl path (and in tests) → no-op.
337
+ */
338
+ onTeamTrackUserResolved?: (args: {
339
+ taskGroupId: string;
340
+ trackIndex: number;
341
+ state: 'Passed' | 'Failed';
342
+ }) => void;
343
+ /**
344
+ * PHASE-589/469 W3.1 (C1) — the durable revert-manifest store. For a TEAM
345
+ * track promote, the pre-image manifest is captured + persisted BEFORE the
346
+ * tree mutates (LOCK #589-I), so a later group reject can revert every applied
347
+ * track. TEAM-only — ABSENT on single-impl (and in tests) → promote runs the
348
+ * legacy no-capture path (byte-identical). Wired by the TEAM shell to a single
349
+ * process-wide `RevertManifestStore`.
350
+ */
351
+ durableStore?: RevertManifestStore;
352
+ /**
353
+ * CP-1.f live progress surfacing — desktop-LOCAL progress phases (shadow
354
+ * created, implementor running + periodic tick, diff captured, submitting,
355
+ * reviewers dispatched, per-seat + verdict progress, revise, promote/discard,
356
+ * waiting-on-user). Wired by the shell to the Ink store (an ephemeral spinner
357
+ * line + persisted milestone lines). ABSENT (tests / non-loop sessions) →
358
+ * no-op. NO backend/wire/schema change: progress NEVER becomes a createEvent.
359
+ *
360
+ * SECURITY INVARIANT: events carry ONLY counts/labels/paths/elapsed — never
361
+ * diff content, file contents, reviewer reasoning, prompts, or any decrypted
362
+ * plaintext (the `TaskProgressEvent` union is the allowlist). The loop wraps
363
+ * every emit so a throwing listener can NEVER break the loop.
364
+ */
365
+ onProgress?: OnProgress;
366
+ /**
367
+ * PHASE-598 — the declared-test EXECUTOR subprocess seam. The per-track
368
+ * declared-`test_surfaces` runner spawns `node <file>` through this primitive
369
+ * (default `runShellCommand`, the SAME primitive the CP-3 verification gates
370
+ * use). Injected so integration tests can stub the executor's `node` runs
371
+ * without a live `node` subprocess. ABSENT → the real `runShellCommand`.
372
+ */
373
+ runShell?: typeof runShellCommand;
374
+ /**
375
+ * PHASE-CP-10-MIN (#585) §3.5 — the continuation packet WRITER (the trigger
376
+ * transport). The `user_requested` (`/continue request`) + `quota_exhausted`
377
+ * (born-halt fork) triggers call `write()` directly (local CONTINUATION.md +
378
+ * the `recordContinuationPacketWritten` mutation → server stamps + emits the
379
+ * handoff prompt). ABSENT (tests / legacy) → the continuation triggers are
380
+ * unavailable (the quota fork degrades to the generic `surfaceHalt`).
381
+ */
382
+ continuationWriter?: ContinuationPacketWriter;
383
+ /**
384
+ * PHASE-CP-10-MIN (#585) §3.5 — the continuation packet READER. The resume
385
+ * (`resumeFromContinuation`) reads the CP-6 `CONTINUATION.md` via this reader,
386
+ * which hash-verifies + backend-audit-row-verifies (refusing a
387
+ * `PacketUnverified` packet — the pre-spawn fidelity gate). ABSENT → resume
388
+ * is unavailable.
389
+ */
390
+ continuationReader?: ContinuationPacketReader;
391
+ /**
392
+ * PHASE-CP-10-MIN (#585) L-A — clock for minting a fresh per-request
393
+ * `expiresAt` (`now + TTL`, millisecond precision). Injected for deterministic
394
+ * tests; production uses `Date.now`.
395
+ */
396
+ clock?: () => number;
397
+ /**
398
+ * PHASE-CP-10-MIN (#585) §3.5 — the continuation packet TTL (ms) used to mint
399
+ * `expiresAt = now + TTL`. ABSENT → the default `CONTINUATION_EXPIRES_TTL_MS`.
400
+ */
401
+ continuationTtlMs?: number;
402
+ }
403
+ /**
404
+ * Map the lowercase wire `AgentKind` (`"claude"`) used by the engine dispatch
405
+ * + the reviewer registry to the UPPERCASE LE `AgentKind` (`"CLAUDE"`) the
406
+ * spawn path uses. Append-only.
407
+ */
408
+ declare function reviewerAgentToLeAgent(agent: ReviewerAgentKind): AgentKind;
409
+ /**
410
+ * Extract the NUMERIC seat-id from the resolved seat-id STRING the engine
411
+ * dispatch carries (`"1::codex"` → 1, bare `"1"` → 1). Returns null when the
412
+ * leading token isn't a non-negative integer.
413
+ */
414
+ export declare function numericSeatId(resolvedSeatId: string): number | null;
415
+ export declare class QuorumLoop {
416
+ private readonly deps;
417
+ private readonly sleep;
418
+ /** The active task this desktop is driving (set at start_task). */
419
+ private activeTaskId;
420
+ private taskEpoch;
421
+ private readonly epochByTaskId;
422
+ private readonly tokensByTaskId;
423
+ private readonly tokensCountedGates;
424
+ /**
425
+ * The implementor brief for the active task (set at start_task). The
426
+ * GATE_DISPATCH consumer reads this to drive the round-0 implementor spawn —
427
+ * the brief is no longer threaded inline through the spawn call (the spawn
428
+ * moved out of `startTask` into the GATE_DISPATCH consumer, §3.C.20 FIX).
429
+ */
430
+ private activeBrief;
431
+ /**
432
+ * IMAGE-ATTACHMENT-DESIGN.md §5 — TASK-SCOPED image attachments (parallel to
433
+ * `activeBrief`/`activeBriefByTask`). The single-impl path uses `activeAttachments`;
434
+ * the team path keys by the child `taskId` in `activeAttachmentsByTask` (armed at
435
+ * `registerTeamTaskBrief`, before its buffered GATE_DISPATCH drains). Re-copied
436
+ * into EACH round's shadow at `runImplementorRound` (round-0, revise, continuation,
437
+ * per track). A LOCAL-file reference (sourcePath + metadata), never bytes/wire.
438
+ */
439
+ private activeAttachments;
440
+ private readonly activeAttachmentsByTask;
441
+ /** IMAGE-ATTACHMENT-DESIGN.md §5 — GROUP-keyed team attachments, armed at launch, fanned per-track at `registerTeamTaskBrief`. */
442
+ private readonly teamAttachmentsByGroup;
443
+ /**
444
+ * CP-12 W2.b (§3.C.2 / §3.C.3 / M5) — per-task brief + agent + trackIndex for
445
+ * the TEAM path. Replaces the single `activeBrief` singleton for multi-task
446
+ * routing: a `GATE_DISPATCH` whose OUTER envelope `task_id` is a known team
447
+ * task resolves `{ brief, agent }` HERE (keyed by the track's child `task_id`),
448
+ * rather than from the single `activeTaskId`/`activeBrief`. Populated by the
449
+ * brief→task_id bridge at `TrackAssigned` (the shell's `launchTrack` looks up
450
+ * the parse-time `briefsByTrackIndex.get(track_index)` and inserts here via the
451
+ * `registerTeamTaskBrief` callback). The single-impl path is UNAFFECTED — its
452
+ * dispatch carries `envelopeTaskId === this.activeTaskId` (the engine stamps the
453
+ * owning task on EVERY dispatch), so `handleGateDispatch`'s
454
+ * `envelopeTaskId !== this.activeTaskId` discriminator falls it through to the
455
+ * single `activeTaskId`/`activeBrief` spawn (byte-identical to today).
456
+ */
457
+ /**
458
+ * CP-12 W2.b (§3.C.2 / §3.C.4) — TRUE once a `/team` has been issued THIS shell
459
+ * session (the shell calls `markTeamSessionActive()` at `/team` parse time).
460
+ * Disambiguates the two cases where a team `GATE_DISPATCH` arrives for a task
461
+ * NOT yet in `activeBriefByTask`:
462
+ * - active (a `/team` ran this session) → an EARLY-PACKET race: the
463
+ * `GATE_DISPATCH` beat its track's `TrackAssigned`; BUFFER + drain on
464
+ * registration (§3.C.2).
465
+ * - NOT active (post-restart: the in-memory brief map is gone, no `/team`
466
+ * re-issued) → FAIL LOUD with the §3.C.4 remediation (never spawn an
467
+ * empty-brief implementor).
468
+ */
469
+ private teamSessionActive;
470
+ /**
471
+ * CP-12 W2.b (§3.C.3 / M4) — the per-track briefs, keyed by the COMPOSITE
472
+ * `teamBriefKey(taskGroupId, trackIndex)`. Captured at `/team` time via
473
+ * `setTeamBriefs(taskGroupId, …)` (after `createTaskGroup` returns the id) AND
474
+ * seeded by recovery PASS 1. The `registerTeamTask` bridge + recovery launch read
475
+ * `getTeamBrief(taskGroupId, trackIndex)` when `TrackAssigned` correlates a track.
476
+ * Desktop-local — NEVER on the wire.
477
+ *
478
+ * CP-12 W4 (#593 Stage-2 r2): keyed by `(taskGroupId, trackIndex)`, NOT bare
479
+ * `trackIndex` — B.2 (`getInFlightTeamTracks`) aggregates rows across ALL
480
+ * in-flight groups for the session, so a bare-index key let two groups that
481
+ * share a `trackIndex` overwrite each other's brief.
482
+ */
483
+ private teamBriefsByKey;
484
+ private readonly activeBriefByTask;
485
+ /**
486
+ * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
487
+ * user-requested changes for a task, accumulated across ALL revise rounds.
488
+ *
489
+ * Each Reject-with-notes the user submits (desktop OR mobile) is delivered by
490
+ * the engine on a single synthetic `seat_id === 'user'` finding inside the
491
+ * `REVISE_FEEDBACK` payload (engine `workflow.rs::open_user_revise_round`).
492
+ * `handleReviseFeedback` records it here via `recordUserNote` BEFORE composing
493
+ * the round's brief — a SINGLE capture point that covers both surfaces (the
494
+ * old `index.ts onTerminalDecision` capture fired only for DESKTOP decisions,
495
+ * silently dropping mobile-originated notes). The revise brief then re-states
496
+ * EVERY accumulated note as a BINDING requirement so a later round can never
497
+ * silently drop an earlier user requirement (the live bug: round-1's JSDoc
498
+ * requirement evaporated by round 2 because the round-2 implementor brief
499
+ * carried ONLY round-1's reviewer findings — no original task, no prior user
500
+ * note). Keyed by taskId so an overlapping terminal signal resolves the right
501
+ * task's notes.
502
+ */
503
+ private readonly userNotesByTask;
504
+ /** Seats with a reviewer subprocess currently running, keyed `gateId::seatId`. */
505
+ private readonly runningSeats;
506
+ /** Seats whose verdict has been submitted, keyed `gateId::seatId`. */
507
+ private readonly submittedSeats;
508
+ /**
509
+ * CP-1.f live progress surfacing — per-gate progress bookkeeping. The
510
+ * single-task loop receives REVIEWER_DISPATCH packets ONE seat at a time
511
+ * (no upfront total on the wire), so we count seats DISPATCHED on this desktop
512
+ * as the "expected" denominator and verdicts SUBMITTED as the numerator. Keyed
513
+ * by gateId → set of seatIds (idempotent under re-delivery / recovery).
514
+ */
515
+ private readonly dispatchedSeatsByGate;
516
+ private readonly verdictedSeatsByGate;
517
+ /** Gates with an implementor round currently running. */
518
+ private readonly inFlightImplementorGates;
519
+ /** Gates whose implementor output has been submitted. */
520
+ private readonly submittedOutputGates;
521
+ /**
522
+ * PHASE-CP-10-MIN (#585) #C10M-16 / Codex-H1 — the per-task ACTIVE
523
+ * implementor context. `runImplementorRound` populates it at spawn (wiring
524
+ * the abort `signal`) + clears it in `finally`. A mid-flight `/continue
525
+ * request` — or the foreign-accept consumer — sets `handoffClaimed = true` +
526
+ * calls `handle.abort()` so the still-running original implementor is
527
+ * suppressed: the post-`await handle.done` path then SKIPS
528
+ * captureDiff/submit/surfaceHalt/round_failed and just `discardShadow`s. The
529
+ * DDB mutual exclusion (#C10M-14) only blocks a late `mark_resolved`; this
530
+ * desktop suppression closes the late-LOCAL-submit race. Keyed by taskId.
531
+ */
532
+ private readonly activeImplementorByTask;
533
+ /**
534
+ * PHASE-CP-10-MIN (#585) §2.3 / #C10M-16 — offers already resumed (the
535
+ * dedupe set; keyed on `offer_id`, the same axis as the audit slot #C10M-4).
536
+ * A LOCAL accept adds the offerId + resumes inline; the `onApplyUserDecision`
537
+ * consumer suppresses an own-echo whose `offer_id` is already in the set and
538
+ * drives the resume only for a FOREIGN (mobile-originated) accept. First-add
539
+ * wins; the second observation is a no-op. `offer_id`-keyed (NOT
540
+ * `gate_id`-only) because a CANCEL→re-request puts two offers on one gate.
541
+ */
542
+ private readonly resumedOffers;
543
+ /**
544
+ * PHASE-CP-10-MIN (#585) #C10M-12 / M6 — agents that have quota-failed for
545
+ * the active task (bounds the quota-substitution loop). Before firing a
546
+ * quota offer, if EVERY distinct configured agent is already in the set →
547
+ * terminal `surfaceHalt` instead of another offer. Keyed by taskId; a desktop
548
+ * restart resets it (acceptable — the user re-decides).
549
+ */
550
+ private readonly quotaFailedAgentsByTask;
551
+ /**
552
+ * #585 auto-continuation (Max) — offerIds minted by the QUOTA auto-fork
553
+ * (`handleQuotaExhaustedFork → requestContinuation`), as opposed to the
554
+ * user-requested `/continue request` path (which mints its OWN distinct
555
+ * offerId). Max auto-continuation MUST fire ONLY for quota-origin offers; a
556
+ * user-requested handoff is the user's explicit choice to pick a target and
557
+ * must NEVER be auto-accepted (Stage-2 Codex+agy HIGH — gate on quota ORIGIN,
558
+ * not prompt KIND). Bounded by the quota loop (≤ one offer per agent per task).
559
+ * A desktop restart empties this → a pre-restart quota offer falls back to the
560
+ * manual prompt (fail-closed; the Max user is prompted instead of auto-resumed).
561
+ */
562
+ private readonly quotaContinuationOffers;
563
+ /** REVISE_FEEDBACK ids already consumed (deterministic dedup). */
564
+ private readonly seenReviseFeedbackIds;
565
+ /**
566
+ * CP-12 W2.b (dogfood team-revise fix, 2026-06-13) — `gateId → taskId` for every
567
+ * dispatched implementor round. A `ReviseFeedback` packet carries ONLY gate ids
568
+ * (`ReviseFeedbackPayload` has no per-track `taskId`/`trackIndex`), so the revise
569
+ * handler has no other way to learn WHICH track (child `task_id`) the revise is
570
+ * for. Without this the team revise round fell back to the single-impl globals
571
+ * (`activeTaskId`/`activeImplementorAgent`/`activeBrief`) — empty/wrong in a team
572
+ * session — and re-spawned the implementor against an empty taskId, throwing
573
+ * "an unexpected error occurred". Populated at EVERY `runImplementorRound`
574
+ * (round-0 + revise, single-impl + team) so `payload.gateId` (the parent gate
575
+ * that produced the REVISE) resolves to the right track. The single-impl path
576
+ * resolves to `activeTaskId` here too (so NO single-vs-team discriminator — the
577
+ * downstream `?? this.activeX` fallbacks keep single-impl byte-identical).
578
+ * Set-only + unbounded, mirroring the sibling `seenReviseFeedbackIds` /
579
+ * `seenGateDispatchRunIds` / `activeBriefByTask` per-round desktop maps (gate
580
+ * cardinality is bounded by those same dedup sets).
581
+ */
582
+ private readonly taskByGateId;
583
+ /**
584
+ * GATE_DISPATCH `gateRunId`s already consumed (idempotent re-delivery dedup).
585
+ * A sweeper / WS re-deliver of the SAME round-0 dispatch must NOT double-spawn
586
+ * the implementor. Symmetric to `seenReviseFeedbackIds` (§3.C.20 FIX). The
587
+ * `runImplementorRound` in-flight/submitted double-guard is the second layer.
588
+ */
589
+ private readonly seenGateDispatchRunIds;
590
+ /**
591
+ * §4.2 (team recovery double-launch fix — dogfood 2026-06-15) — gateRunIds
592
+ * whose track `launchRecoveredTrack` has CLAIMED for recovery, stamped
593
+ * SYNCHRONOUSLY at the dedup point BEFORE the `await recoverLaunch`. Distinct
594
+ * from `seenGateDispatchRunIds`, which `handleTeamGateDispatch` stamps only
595
+ * AFTER its async work — leaving a window where a concurrent B.2 recovery
596
+ * passes the dedup, runs `recoverLaunch` → the `'duplicate'` repair →
597
+ * `registerTeamTaskBrief` a SECOND time → duplicate gate/verdict/applied/
598
+ * convergence emits (the dogfood "Applied 1 file…" ×3 / duplicate final_approval;
599
+ * no data impact, exactly 2 files). Claiming here, before any await, makes the
600
+ * "recover once" dedup actually atomic. Kept SEPARATE from
601
+ * `seenGateDispatchRunIds` so the legitimate round-0 gate dispatch still runs
602
+ * (stamping that set here would suppress the implementor entirely).
603
+ */
604
+ private readonly recoveredGateRunIds;
605
+ /**
606
+ * §4.1 — DURABLE per-track tombstone for a present-but-CORRUPT brief
607
+ * ciphertext (decrypt threw during recovery PASS 1). Blocks BOTH recovery
608
+ * relaunch (PASS 2) AND any future LIVE `TrackAssigned`/drain for that track
609
+ * (§4.3) — a corrupt brief must NEVER spawn an empty-brief implementor on
610
+ * EITHER path. The track times out via the engine sweeper (bounded). Distinct
611
+ * from a `null` `encryptedBrief` (a legit empty brief).
612
+ *
613
+ * CP-12 W4 (#593 Stage-2 r2): keyed by the COMPOSITE `teamBriefKey(taskGroupId,
614
+ * trackIndex)`, NOT bare `trackIndex` — B.2 aggregates across groups, so a
615
+ * tombstone on group A's track 0 must not block group B's (valid) track 0.
616
+ */
617
+ private readonly corruptBriefTracks;
618
+ /**
619
+ * #592 PROVENANCE GUARD (2026-06-26) — the task-group ids THIS desktop
620
+ * ORIGINATED (recorded in `armB1Recovery`, right after a successful
621
+ * `createTaskGroup`). Class B team packets broadcast PER-USER, and B.2 recovery
622
+ * (`recoverTeamTracks`) discovers EVERY in-flight group session-wide — so a
623
+ * SECOND desktop/container on the same account receives + processes this
624
+ * desktop's `TrackAssigned`/`GateDispatch`. WITHOUT this guard a desktop that
625
+ * already owns a group pins a FOREIGN group's base on its OWN workspace and
626
+ * submits an empty round-0 → born-halts the REAL originator's gate (dogfood
627
+ * 2026-06-26, proven: `bold_lewin` killed another run's gate `7681a432`). When
628
+ * NON-EMPTY, recovery + live team dispatch DROP any group not in this set. EMPTY
629
+ * (fresh process / post-crash) → no filter, so legitimate crash-recovery of an
630
+ * own in-flight group is preserved (the rare two-cold-desktops edge is unchanged).
631
+ */
632
+ private readonly ownedGroupIds;
633
+ /**
634
+ * CP-12 W4 (#593 Stage-2 r2) — the composite key for `teamBriefsByKey` +
635
+ * `corruptBriefTracks`. `(taskGroupId, trackIndex)` so two in-flight groups
636
+ * that share a `trackIndex` (B.2 aggregates session-wide) never collide.
637
+ */
638
+ private teamBriefKey;
639
+ /**
640
+ * PHASE-589/469 W3.1 (C1) — the GROUP-WIDE monotonic promote-order counter,
641
+ * keyed by `taskGroupId`. Each team track promote reserves
642
+ * `[base, base + appliedCount)` so the durable manifest's `promoteOrder` is a
643
+ * single increasing sequence ACROSS every track in the group — the reverse of
644
+ * which drives reverse-apply (last-applied reverted first, LOCK #589-I).
645
+ * In-memory: a crash loses the counter, but the durable manifest already
646
+ * persists each entry's `promoteOrder`, so the FIRST reservation per group in
647
+ * this process re-seeds the counter from the manifest's high-water mark
648
+ * (see {@link reservePromoteOrder}), and a recovery sorts off the persisted
649
+ * values.
650
+ */
651
+ private readonly groupPromoteOrder;
652
+ /**
653
+ * PHASE-589/469 (M1) — groups whose `groupPromoteOrder` has already been
654
+ * seeded from the durable manifest's high-water mark IN THIS PROCESS. The
655
+ * seed runs ONCE per group per process (the first `reservePromoteOrder`);
656
+ * thereafter the in-memory counter is authoritative.
657
+ */
658
+ private readonly promoteOrderSeeded;
659
+ /**
660
+ * PHASE-589/469 W3-H1 (Stage-2 Codex r2) — per-GROUP async MUTEX for
661
+ * `reservePromoteOrder`. The seed-then-reserve body is a check-then-`await`-then-
662
+ * act sequence: it marks `promoteOrderSeeded` SYNCHRONOUSLY, then `await`s the
663
+ * durable manifest high-water read BEFORE setting `groupPromoteOrder`. A SECOND
664
+ * same-group reservation entering during that await would see `seeded === true`
665
+ * yet `groupPromoteOrder === undefined`, skip the seed, and reserve `base = 0` —
666
+ * so two same-group tracks could BOTH reserve base 0 (and the slow first reserve
667
+ * then clobbers the second's counter write). Two tracks at the same
668
+ * `(promoteOrder, path)` make `appendPromote` overwrite the older manifest entry
669
+ * (it dedups idempotently on `(promoteOrder, path)`), losing the original
670
+ * pre-image so the all-or-nothing revert can't restore baseline.
671
+ *
672
+ * The shadow promote mutex (`promoteShadowLocked` / {@link runExclusiveShadowOp})
673
+ * is keyed per-TASK, but `promoteOrder` is per-GROUP, so this needs its OWN
674
+ * per-group serialization. Same promise-chain shape as `runExclusiveShadowOp`
675
+ * (predecessor-rejection-isolated, entry GC'd when the tail it stored settles).
676
+ */
677
+ private readonly promoteOrderOpsByGroup;
678
+ /**
679
+ * Serialize one `reservePromoteOrder` body for `taskGroupId` behind any in-flight
680
+ * reservation for the SAME group (mirror of {@link runExclusiveShadowOp}, keyed
681
+ * per-group not per-task). A predecessor's rejection is isolated at the
682
+ * sequencing layer; the map entry is GC'd once the tail we appended is still the
683
+ * current tail (an idle group leaks no entry).
684
+ */
685
+ private runExclusivePromoteOrderOp;
686
+ /**
687
+ * Reserve `count` contiguous group-wide promote-order slots; returns the base.
688
+ *
689
+ * PHASE-589/469 (M1) — on the FIRST reservation for a given `taskGroupId` in
690
+ * this process, seed the in-memory counter from the durable manifest's
691
+ * high-water mark (`max(promoteOrder) + 1`) so a post-crash second track (or a
692
+ * re-promote after the in-memory Map was emptied by a restart) continues ABOVE
693
+ * the persisted orders and NEVER reuses one. Without this, a crash that empties
694
+ * `groupPromoteOrder` would let a recovered second track restart at base 0 and
695
+ * collide with track 0's already-persisted orders — leaving two manifest
696
+ * entries at the same `promoteOrder` for different paths, whose reverse-apply
697
+ * order is then undefined (a shared file could be deleted-before-restored).
698
+ *
699
+ * TEAM-only + best-effort: the seed reads the durable store only when one is
700
+ * wired (team tracks); if the manifest read throws, fall back to the current
701
+ * in-memory base (log a warning, never block the promote).
702
+ *
703
+ * Serialized PER-GROUP ({@link runExclusivePromoteOrderOp}, W3-H1) so the whole
704
+ * seed-then-reserve body is atomic w.r.t. concurrent same-group calls: a second
705
+ * concurrent reservation WAITS, then reads the now-SEEDED counter and reserves
706
+ * the next contiguous slot (distinct, non-overlapping bases — never duplicate 0).
707
+ */
708
+ private reservePromoteOrder;
709
+ private reservePromoteOrderLocked;
710
+ /**
711
+ * §4.3 — the startup ORDERING BARRIER as a REFCOUNT (Stage-2 r1 Codex HIGH).
712
+ * `0` ⇒ no recovery seeding in flight ⇒ barrier UP (live TEAM packets consume
713
+ * normally; single-impl / no-recovery path byte-identical). `> 0` ⇒ at least
714
+ * one recovery op (B.1 / a B.2 attempt sequence) is seeding ⇒ barrier DOWN
715
+ * (live TEAM packets BUFFER). It is a refcount, not a boolean, so the barrier
716
+ * spans the ENTIRE recovery window — incremented SYNCHRONOUSLY at the start of
717
+ * each recovery op (before its first GSI query, so a live packet arriving
718
+ * DURING the query is buffered, not consumed empty) and decremented in a
719
+ * `finally`; the buffered queue is drained ONCE when the count returns to 0.
720
+ * This also makes concurrent B.1+B.2 safe (no per-op toggle race).
721
+ */
722
+ private recoverySeedingDepth;
723
+ /**
724
+ * §4.3 — TEAM Class B packets buffered while the recovery seed barrier is up
725
+ * (`recoverySeedingDepth > 0`). Drained through the normal live handlers AFTER
726
+ * seeding completes (which now read the seeded brief / hit the corrupt
727
+ * tombstone). Single-impl / non-team packets are NEVER buffered here.
728
+ */
729
+ private readonly bufferedTeamPackets;
730
+ /**
731
+ * §4.2 B.1 two-condition latch — TRUE once the Class B subscription has been
732
+ * established (the `onSubscribed` signal). B.1 fires only when BOTH this AND a
733
+ * pending `taskGroupId` are set (covers the common case where the subscription
734
+ * was ALREADY up before `createTaskGroup` returned).
735
+ */
736
+ private subscriptionReady;
737
+ /**
738
+ * §4.2 B.1 two-condition latch — the `taskGroupId` `createTaskGroup` just
739
+ * returned, awaiting the subscription-ready condition. CLEARED immediately on
740
+ * B.1 fire so a later reconnect's `onSubscribed` does not re-run B.1 for an
741
+ * already-handled group (reconnect recovery is B.2's job).
742
+ */
743
+ private pendingB1TaskGroupId;
744
+ /**
745
+ * Stage-2 r2 (Codex) FIX A — START in-flight flag. The engine emits
746
+ * GATE_DISPATCH from INSIDE the `startTask` handler (workflow.rs:3097), BEFORE
747
+ * the mutation response returns, so the live `onClassBPacket` WS can deliver
748
+ * GATE_DISPATCH to the desktop BEFORE `await appsyncClient.startTask(...)`
749
+ * resolves. `startTask` arms the active task + sets this flag BEFORE the
750
+ * await; while it is set, `handleGateDispatch` BUFFERS the packet (into
751
+ * `pendingGateDispatches`) instead of spawning, so we never spawn an
752
+ * implementor for a gate whose START is not yet CONFIRMED. The buffer is
753
+ * DRAINED only after `startTask` resolves successfully (run the spawn then);
754
+ * on `startTask` rejection the active context is rolled back + the buffer
755
+ * discarded (a buffered packet whose START failed never spawns).
756
+ */
757
+ private startTaskInFlight;
758
+ /**
759
+ * Stage-2 r2 (Codex) FIX A — GATE_DISPATCH payloads that arrived while
760
+ * `startTask` was in flight (single-impl early-packet race), OR (CP-12 W2.b,
761
+ * §3.C.2 / agy MEDIUM-2) a TEAM GATE_DISPATCH that arrived BEFORE its track's
762
+ * `TrackAssigned` registered the brief in `activeBriefByTask`.
763
+ *
764
+ * Each entry retains `envelopeTaskId` (the OUTER envelope's `task_id`) so a
765
+ * buffered TEAM dispatch keeps its routing key — the payload carries no
766
+ * `taskId`, and the `TrackAssigned` that registers the brief may land AFTER the
767
+ * `GATE_DISPATCH` under fire-and-forget consumption. The replay-on-registration
768
+ * drain routes each entry by its buffered `envelopeTaskId`
769
+ * (`handleGateDispatch(entry.payload, entry.envelopeTaskId)`). For the
770
+ * single-impl early-packet race the buffered `envelopeTaskId` equals the active
771
+ * task (the engine stamps the owning task on every dispatch), so the re-invoked
772
+ * `handleGateDispatch` sees `envelopeTaskId === this.activeTaskId` and falls
773
+ * through to the single-impl spawn (byte-identical to today).
774
+ */
775
+ private readonly pendingGateDispatches;
776
+ /**
777
+ * CP-1.f execute-first (§4) — the per-task SHADOW workspace. Created BEFORE the
778
+ * round-0 implementor spawn, retained across all revise rounds + the review
779
+ * window, and PROMOTED on `TaskAuthorized` (#PR-1/#PR-3) or DISCARDED on every
780
+ * other terminal. Keyed by taskId so the class-B consumer + terminal handler
781
+ * resolve it without threading it through the packet path. One active task per
782
+ * loop, but a Map keeps the lookup robust across overlapping terminal signals.
783
+ */
784
+ private readonly shadowsByTask;
785
+ /**
786
+ * PHASE-598 (Codex impl-r5 / LOCK #598-11) — the REVIEWED diff snapshot per TEAM
787
+ * task, stored when a round's gate goes live (reviewers dispatched). `promoteShadow`
788
+ * fails-closed if the live shadow drifts from it before promote — the airtight
789
+ * backstop for a declared test that spawns a DETACHED writer (the test runner waits
790
+ * only on the direct child, so such a writer can mutate the shadow AFTER the
791
+ * post-test re-capture passes but BEFORE promote). Presence ⇔ team task: single-impl
792
+ * never stores, so its promote path is unchanged. Cleared on promote/discard.
793
+ */
794
+ private readonly reviewedSnapshotByTask;
795
+ /** Guards concurrent shadow-create for the same task (round-0 + an early re-deliver). */
796
+ private readonly shadowCreateInFlight;
797
+ /**
798
+ * CP-14 Wave A — the ONE pinned base SHA per task group (`worktree_merge`),
799
+ * captured (memoized) at the first track of the group BEFORE any shadow is
800
+ * created, so EVERY track branches from the same base → a deterministic
801
+ * merge-train input (Stage-1 plan-review MED-3). Promise-memoized so concurrent
802
+ * track registrations share a single `git rev-parse HEAD`.
803
+ */
804
+ private readonly groupBaseShaInFlight;
805
+ /**
806
+ * Per-task async MUTEX for shadow LIFECYCLE mutations (restart-race fix,
807
+ * 2026-06-11 Stage-2 Codex r2). A promise-chain serializes EVERY map/stateDir-
808
+ * touching shadow op for a task — `ensureShadow`'s create-or-get section,
809
+ * `discardShadow`, `replaceShadowForRound0`, and `promoteShadow`'s
810
+ * map/stateDir section — so a check-then-act guard's snapshot (mapped vs the
811
+ * captured instance) stays TRUE for the whole critical section. Eliminates the
812
+ * "matched at entry, then replaced while awaiting" class of interleaving (the
813
+ * third found one-at-a-time: orphan double-discard → unmapped window → full-A-
814
+ * discard overlapping a restart replacement deleting B's #PR-4 marker).
815
+ *
816
+ * The chain holds the LAST queued op's settle promise; `runExclusiveShadowOp`
817
+ * appends `fn` to run after it, swallows the predecessor's rejection at the
818
+ * sequencing layer (errors propagate to the OWNING caller, never poison the
819
+ * chain for the next op), and clears the entry once the tail it stored is the
820
+ * one that settled (so an idle task leaks no entry). The serialized region is
821
+ * fs/git-only with NO nested `runExclusiveShadowOp` call (the wrapped methods
822
+ * never call each other), so it cannot self-deadlock.
823
+ */
824
+ private readonly shadowOpsByTask;
825
+ /**
826
+ * Serialize one shadow lifecycle op for `taskId` behind any in-flight op for
827
+ * the same task. The op's result/rejection is returned to ITS caller; a
828
+ * predecessor's rejection is isolated (caught at the sequencing layer) so it
829
+ * never poisons a later op. The map entry is cleared only when the tail we
830
+ * appended is still the current tail (no later op queued behind us).
831
+ */
832
+ private runExclusiveShadowOp;
833
+ /**
834
+ * The last terminal workspace outcome (promote / discard) this loop applied,
835
+ * for the `summarize_current_status` shell answer (Fix 2). RELATIVE file paths
836
+ * only — never any diff content (the user owns the workspace, so paths are
837
+ * permitted per the promote/discard surface exception; content is not). `null`
838
+ * until the first promote/discard lands. Updated ONLY when a promote actually
839
+ * MATERIALIZES files (a re-delivered no-op packet does NOT overwrite it).
840
+ */
841
+ private lastWorkspaceOutcome;
842
+ constructor(deps: QuorumLoopDeps);
843
+ /** The current active task id (for the recovery poll). */
844
+ get activeTask(): string | null;
845
+ /**
846
+ * Audited-path fix (Fix 5) — the host's detected implementor agents
847
+ * (UPPERCASE `AgentKind`), as threaded into the loop at construction. Used by
848
+ * the `/task` deterministic shortcut + the planner `start_task` path to select
849
+ * which implementor to spawn (default CLAUDE when present, else the first
850
+ * detected). Returns a shallow copy so callers can't mutate the loop's record.
851
+ * Empty when the host passed none (a definitive "nothing detected" →
852
+ * `selectImplementorAgent` falls back to CLAUDE).
853
+ */
854
+ /**
855
+ * IMAGE-ATTACHMENT-DESIGN.md §5 (Stage-2 R3 HIGH): expose the loop's durable
856
+ * store so the LIVE team-launch sites can persist the RetainedTeamLaunchSpec
857
+ * (incl. attachments) — otherwise `setTeamLaunchSpec` never runs on a live launch
858
+ * and `recoverTeamTracks`'s restore has nothing to read. Same instance the loop
859
+ * uses for reissue/recovery/promote.
860
+ */
861
+ getDurableStore(): RevertManifestStore | undefined;
862
+ getDetectedAgents(): AgentKind[];
863
+ /**
864
+ * #585 auto-continuation (Max) — pick the agent to AUTO-CONTINUE on when the
865
+ * implementor for `taskId` hit a quota wall, or `null` to fall through to the
866
+ * existing `continuation_offer_handoff` prompt. Returns a target ONLY when:
867
+ * (a) tier === MAX (the "don't bother me" automation tier — mirrors auto-revise), AND
868
+ * (b) ≥1 host-detected agent is NOT already quota-failed for this task.
869
+ * The pick is the STRONGEST such agent (`TIER2_REVIEWER_RANK`), deterministic. The
870
+ * quota-failed set (populated by `handleQuotaExhaustedFork` BEFORE the offer) already
871
+ * contains the just-failed source agent, so the result is never the agent that just
872
+ * hit quota. All detected agents quota-failed → `null` (the existing terminal halt
873
+ * fires). Non-MAX / unresolved / absent tier → `null` (fail-closed to the prompt).
874
+ * Pure read; never throws.
875
+ */
876
+ pickAutoContinuationTarget(taskId: string): AgentKind | null;
877
+ /**
878
+ * #585 auto-continuation (Max) — true iff `offerId` was minted by the QUOTA
879
+ * auto-fork (`handleQuotaExhaustedFork`). Max auto-continuation gates on THIS,
880
+ * not the prompt kind, so a user-requested `/continue request` handoff (a
881
+ * distinct offerId) is NEVER auto-accepted (Stage-2 Codex+agy HIGH). Empty
882
+ * after a desktop restart → returns false → the manual prompt (fail-closed).
883
+ * Pure read; never throws.
884
+ */
885
+ isQuotaContinuationOffer(offerId: string): boolean;
886
+ /**
887
+ * PHASE-590 §3.B — the canonicalized real working dir (the repo). The local
888
+ * team decomposer (`runLocalDecomposer`) spawns a read-only `plan`-mode
889
+ * implementor against THIS dir so it can read the tree before emitting the
890
+ * `WorkItemInput[]` decomposition. Read-only accessor (the decomposer never
891
+ * writes here — `plan` mode).
892
+ */
893
+ getWorkingDir(): string;
894
+ /**
895
+ * Read-only accessor for the last terminal workspace outcome (promote with its
896
+ * RELATIVE file paths, or discard). Used by the shell's
897
+ * `summarize_current_status` answer to name the files last applied. Returns a
898
+ * shallow copy so a caller can't mutate the loop's record. `null` until the
899
+ * first promote/discard.
900
+ */
901
+ getLastWorkspaceOutcome(): {
902
+ kind: 'promoted';
903
+ files: string[];
904
+ } | {
905
+ kind: 'discarded';
906
+ } | null;
907
+ /** The DI callbacks the shell wires into its ClassBConsumer. */
908
+ classBConsumerDeps(): {
909
+ classBSignatureVerifier: (packet: import('../local-executor/types').ClassBInbound) => Promise<boolean>;
910
+ gateDispatch: (payload: GateDispatchPayload, envelopeTaskId?: string) => Promise<void>;
911
+ spawnReviewers: (payload: ReviewerDispatchPayload) => Promise<void>;
912
+ runReviseRound: (payload: ReviseFeedbackPayload) => Promise<void>;
913
+ };
914
+ private cachedSigningKey;
915
+ private verifySignature;
916
+ /**
917
+ * Drive the engine START from a planner `start_task` decision. Mints the
918
+ * taskId, ARMS the active task (taskId/brief/agent), calls the `startTask` SDK
919
+ * mutation, then DRAINS any GATE_DISPATCH buffered during the START window.
920
+ *
921
+ * CP-1.f Wave 3 FIX — the implementor is NOT spawned here. The engine creates
922
+ * the round-0 `reviewer_quorum` gate with a deterministic gate_id and emits a
923
+ * GATE_DISPATCH packet carrying it as `gateRunId`; the `startTask` mutation
924
+ * response carries ONLY the workflow-state LABEL string ("gate_plan_in_flight")
925
+ * — NOT the gate_id. So the desktop spawns the implementor + submits its
926
+ * output against `gateRunId` in `handleGateDispatch`. Using `taskId` as the
927
+ * gate correlation (the prior wiring) is rejected by `submitImplementorOutput`
928
+ * — it needs the engine's gate_id.
929
+ *
930
+ * Stage-2 r2 (Codex) FIX A — the engine emits GATE_DISPATCH from INSIDE the
931
+ * `startTask` handler (workflow.rs:3097), BEFORE its mutation response returns,
932
+ * so the live WS can deliver that round-0 GATE_DISPATCH to the desktop BEFORE
933
+ * the SDK `await` below resolves. To avoid spawning before the START is
934
+ * confirmed (and to avoid the Stage-1 FIX-3 "drop forever" no-op that hangs the
935
+ * loop), we ARM the active context + set `startTaskInFlight` BEFORE the await;
936
+ * an early GATE_DISPATCH is then BUFFERED (not spawned) and DRAINED here once
937
+ * the START resolves successfully. On rejection the armed context is rolled
938
+ * back + the buffer discarded (a buffered packet whose START failed never
939
+ * spawns). Invariant: an early GATE_DISPATCH is never dropped, and the
940
+ * implementor is never spawned until `startTask` succeeds.
941
+ *
942
+ * Best-effort: a backend error is logged + surfaced advisory by the caller
943
+ * (returns the taskId on success, null on failure so the caller can render the
944
+ * error). No `gateId` is returned — it is not known until GATE_DISPATCH.
945
+ */
946
+ startTask(args: {
947
+ decision: import('../planner/types').PlannerDecision;
948
+ brief: string;
949
+ agent: AgentKind;
950
+ /** IMAGE-ATTACHMENT-DESIGN.md §5 — task-scoped image attachments, armed before the drain. */
951
+ attachments?: ImageAttachment[];
952
+ }): Promise<{
953
+ taskId: string;
954
+ } | null>;
955
+ /**
956
+ * CP-1.f Wave 3 FIX — consume a GATE_DISPATCH packet. The engine emits this
957
+ * after `startTask` creates the round-0 `reviewer_quorum` gate; its
958
+ * `gateRunId` is the round-0 gate_id the desktop submits the implementor
959
+ * output against (the `startTask` mutation response does NOT carry it).
960
+ *
961
+ * Only `gateType === "reviewer_quorum"` spawns the implementor (at START
962
+ * `reviewerSeats` is absent — the proposal hasn't arrived; the per-seat
963
+ * fan-out is driven later by REVIEWER_DISPATCH once `submitImplementorOutput`
964
+ * lands). `verification` gates are CP-3 (out of scope for CP-1.f single-task)
965
+ * and `final_approval` surfaces via `onEventCreated` INTERACTIVE_PROMPT (§2),
966
+ * NOT GATE_DISPATCH — both are logged + ignored (no spawn).
967
+ *
968
+ * Idempotent: deduped on `gateRunId` (a sweeper / WS re-deliver must not
969
+ * double-spawn), and `runImplementorRound` is itself double-guarded
970
+ * (in-flight / already-submitted per gate). Best-effort: errors are logged,
971
+ * never thrown upward.
972
+ */
973
+ handleGateDispatch(payload: GateDispatchPayload, envelopeTaskId?: string): Promise<void>;
974
+ /**
975
+ * CP-12 W2.b (§3.C.2 / §3.C.3) — mark a `/team` issued THIS shell session, so
976
+ * a team `GATE_DISPATCH` for a not-yet-registered track is BUFFERED (early
977
+ * race) rather than failed-loud (post-restart). The shell calls this at `/team`
978
+ * parse time (the same parse that captures `briefsByTrackIndex`).
979
+ */
980
+ markTeamSessionActive(): void;
981
+ /**
982
+ * CP-12 W2.b (§3.C.3 / M4) — bind the parse-time per-track briefs to their
983
+ * task group + mark the team session active. The shell calls this from the
984
+ * `/team` command dispatch AFTER `createTaskGroup` returns the `taskGroupId`
985
+ * (the parse-time briefs are keyed by array `trackIndex`; this re-keys them by
986
+ * the composite `(taskGroupId, trackIndex)`). Two synchronous statements after
987
+ * the `createTaskGroup` await, BEFORE `armB1Recovery` — no live `TrackAssigned`
988
+ * can interleave (single-threaded), so the bridge always finds the brief.
989
+ *
990
+ * CP-12 W4 (#593 Stage-2 r2): composite-keyed so two in-flight groups that
991
+ * share a `trackIndex` never overwrite each other's brief. Desktop-local
992
+ * (NEVER on the wire). A subsequent `/team` for a NEW group adds its OWN keys.
993
+ */
994
+ setTeamBriefs(taskGroupId: string, briefsByTrackIndex: Map<number, string>): void;
995
+ /**
996
+ * IMAGE-ATTACHMENT-DESIGN.md §5 — arm the GROUP's image attachments at team
997
+ * launch (alongside `setTeamBriefs`), BEFORE any `TrackAssigned`.
998
+ * `registerTeamTaskBrief` fans them per-track into `activeAttachmentsByTask` before
999
+ * that track's buffered GATE_DISPATCH drains, so the round-0 spawn copies them in.
1000
+ */
1001
+ setTeamAttachments(taskGroupId: string, attachments: ImageAttachment[]): void;
1002
+ /**
1003
+ * CP-12 W2.b (§3.C.3) — read the brief for `(taskGroupId, trackIndex)`. The
1004
+ * `registerTeamTask` bridge (wired in the shell) reads this when `TrackAssigned`
1005
+ * correlates a track to its child `task_id`; recovery PASS 2 reads it for a
1006
+ * relaunched track. Empty string when the user issued no `description` for the
1007
+ * track (a track with no brief runs an empty-brief round-0; the engine's #EF-1
1008
+ * born-halt surfaces it). Composite-keyed (#593 Stage-2 r2) — see `teamBriefKey`.
1009
+ */
1010
+ getTeamBrief(taskGroupId: string, trackIndex: number): string;
1011
+ /**
1012
+ * CP-12 W2.b (§3.C.3 / M5) — the brief→task_id bridge. `launchTrack` calls this
1013
+ * at `TrackAssigned` (the FIRST surface carrying both `track_index` AND the
1014
+ * track's child `task_id`), having resolved the parse-time
1015
+ * `briefsByTrackIndex.get(track_index)` for the brief. Inserts the per-task
1016
+ * brief/agent/scope into `activeBriefByTask`, THEN drains any team
1017
+ * `GATE_DISPATCH` for this `task_id` that arrived BEFORE the track registered
1018
+ * (the §3.C.2 early-packet race). Idempotent: a duplicate `TrackAssigned`
1019
+ * (same generation) re-registers the same entry + the buffered dispatch (if
1020
+ * any) is already drained, so the drain is a no-op.
1021
+ */
1022
+ registerTeamTaskBrief(taskId: string, entry: {
1023
+ brief: string;
1024
+ agent: AgentKind;
1025
+ taskGroupId: string;
1026
+ trackIndex: number;
1027
+ writePaths: string[];
1028
+ autoModifiedFiles: string[];
1029
+ testSurfaces: string[];
1030
+ }): Promise<void>;
1031
+ /**
1032
+ * CP-12 W2.b (§3.C.2) — the TEAM analogue of the single-impl round-0 spawn.
1033
+ * Routes by the OUTER envelope's `task_id` (the payload has none): resolves
1034
+ * `{ brief, agent, write scope }` from `activeBriefByTask`, runs the EXISTING
1035
+ * `runImplementorRound` machinery against the track's child `task_id` + the
1036
+ * scheduler-minted round-0 `gateRunId`, and validates the captured diff against
1037
+ * the track's declared write scope (§3.C.2b option a) before submit.
1038
+ *
1039
+ * - NOT-yet-registered + a `/team` ran this session → BUFFER (early race,
1040
+ * drained by `registerTeamTaskBrief`).
1041
+ * - NOT-yet-registered + NO `/team` this session (post-restart) → FAIL LOUD
1042
+ * (§3.C.4) — never spawn an empty-brief implementor.
1043
+ */
1044
+ private handleTeamGateDispatch;
1045
+ /**
1046
+ * INVARIANT enforcement (live dogfood "Reject and restart" race, 2026-06-11):
1047
+ * before a NEW round-0 spawn, drop any STALE shadow still mapped for this task
1048
+ * so `ensureShadow` re-creates a FRESH one. A restart reuses the SAME taskId;
1049
+ * the rejected attempt's shadow may still be mapped here. Tear it down (its
1050
+ * uuid-suffixed dir is owned solely by that instance, so this never affects a
1051
+ * sibling) and clear the task's accumulated binding user notes — a restart is a
1052
+ * fresh round-0 of the ORIGINAL task, so the rejected attempt's revise-context
1053
+ * notes must not bleed into the restart. Clearing notes HERE (the dispatch side)
1054
+ * AND in the matching-instance `discardShadow` path makes BOTH race orders
1055
+ * converge on "restart starts clean." No-op when nothing is mapped (the normal
1056
+ * start_task → first round-0, where the shadow does not yet exist).
1057
+ */
1058
+ private replaceShadowForRound0;
1059
+ private replaceShadowForRound0Locked;
1060
+ /**
1061
+ * CP-14 P1 — the team execution model this session SIGNALS on
1062
+ * `createTaskGroup` (`executionModel`). Always `worktree_merge` since the
1063
+ * §10 retirement (2026-07-03): overlapping-track dispatch with the
1064
+ * merge-train + the server contract gate as the authority.
1065
+ */
1066
+ teamExecutionModel(): TeamExecution;
1067
+ /**
1068
+ * CP-14 Wave A — the ONE pinned group base SHA (memoized): `git rev-parse HEAD`
1069
+ * in the workspace, captured at the FIRST track of the group, BEFORE any track
1070
+ * shadow/branch is created, so every track roots at the same base (Stage-1
1071
+ * MED-3). Concurrent callers share one rev-parse via the in-flight promise.
1072
+ * Throws if HEAD can't be resolved (a non-git workspace is ineligible upstream).
1073
+ */
1074
+ private ensureGroupBaseSha;
1075
+ /**
1076
+ * CP-1.f execute-first (§4) — ensure the per-task SHADOW exists, creating it
1077
+ * BEFORE the round-0 spawn. Idempotent: round-0 + an early GATE_DISPATCH
1078
+ * re-deliver, and revise rounds, all reuse the SAME shadow (the diff is
1079
+ * cumulative shadow-vs-real across revise rounds, D6). Fail-closed (#SR-2): a
1080
+ * create failure throws so the caller never spawns against a half-built shadow.
1081
+ */
1082
+ private ensureShadow;
1083
+ private ensureShadowLocked;
1084
+ /** The shadow for `taskId` (introspection / the terminal promote/discard handlers). */
1085
+ shadowForTask(taskId: string): WorkspaceShadow | undefined;
1086
+ /**
1087
+ * Spawn the EXECUTE-mode implementor for one round in the task's SHADOW,
1088
+ * capture the shadow-vs-real diff, and submit it as the execute-first
1089
+ * artifact (§E1). Factored so round-0 (start_task) + round-N+1 (revise) share
1090
+ * the path; the shadow is reused across revise rounds (D6). The submit return
1091
+ * carries `workflowState` — on `"halted"`/`"idle"` (empty diff / born-halt)
1092
+ * the halt is surfaced SYNCHRONOUSLY (#EF-1), not via the ≤5min sweeper.
1093
+ *
1094
+ * Double-guarded: a gate already in-flight OR already submitted is a no-op.
1095
+ */
1096
+ runImplementorRound(args: {
1097
+ taskId: string;
1098
+ gateId: string;
1099
+ roundNumber: number;
1100
+ brief: string;
1101
+ agent: AgentKind;
1102
+ sessionKey: string;
1103
+ mode?: ImplementorMode;
1104
+ /**
1105
+ * CP-12 W2.b (§3.C.2b option a / H3) — TEAM only. After `captureDiff` and
1106
+ * BEFORE submit, validate every changed file against the track's declared
1107
+ * write scope (`writePaths ∪ autoModifiedFiles ∪ testSurfaces`); an
1108
+ * out-of-scope write FAILS the round LOUDLY (no submit, no promote).
1109
+ * `testSurfaces` is the track's own-module test files (#590), disjointness-
1110
+ * validated by the engine. Absent for the single-impl path (no per-track
1111
+ * confinement).
1112
+ */
1113
+ validateDiffScope?: {
1114
+ writePaths: string[];
1115
+ autoModifiedFiles: string[];
1116
+ testSurfaces: string[];
1117
+ trackIndex: number;
1118
+ };
1119
+ }): Promise<void>;
1120
+ /**
1121
+ * Extract the agent's informational stdout summary (audit-only; reviewers
1122
+ * never see it — they review the captured diff). Best-effort: a failure to
1123
+ * read the summary is NON-fatal (the diff is the source of truth). For Codex
1124
+ * the clean reply is the `--output-last-message` temp file; for Gemini it is
1125
+ * the JSON envelope's `.response`; Claude `--print` is clean stdout.
1126
+ */
1127
+ private extractAgentSummary;
1128
+ /**
1129
+ * [token counter — Phase 2 · P2-2] Best-effort per-round IMPLEMENTOR token count
1130
+ * (input+output). Mirrors `extractAgentSummary`'s capture split: Codex emits its
1131
+ * `--json` usage on stdout even though the SUMMARY is file-captured, so sum that
1132
+ * JSONL stream; Claude parses the `--output-format json` envelope's `usage`.
1133
+ * Gemini/Antigravity are out of scope (undefined). NEVER throws — a read/parse
1134
+ * failure yields `undefined` (segment simply omitted), never breaks the round.
1135
+ */
1136
+ private extractImplementorTokens;
1137
+ /**
1138
+ * PHASE-589/469 W1+W2 (LOCK #589-E/F/H) — on a TEAM track local hard-fail (any
1139
+ * of the 4 closed reasons: `verification_failure` / `out_of_scope_write` /
1140
+ * `review_integrity_violation` / `promote_failure`), report the engine marker
1141
+ * + render the desktop's OWN track from the AUTHORITATIVE returned state:
1142
+ * (a) report the engine marker (`submitTrackVerificationOutcome`). For a
1143
+ * REVISABLE reason (`verification_failure` / `out_of_scope_write`) thread
1144
+ * the round-N `gateId` so the engine can mint the round-N+1 revise round
1145
+ * (cap permitting). **BEST-EFFORT** — on any failure log a warn and STILL
1146
+ * proceed (the engine lease sweeper backstops); a marker failure must
1147
+ * NEVER crash the loop;
1148
+ * (b) render the desktop's OWN track from the marker's RETURNED `trackState`
1149
+ * (authoritative, no optimistic divergence):
1150
+ * - W2 auto-revise (returned state NOT `Failed`, track stays InFlight)
1151
+ * → `onTeamTrackRevising` (render `Revising`); the engine emits
1152
+ * `REVISE_FEEDBACK` → `handleReviseFeedback` re-spawns the implementor;
1153
+ * - terminal-fail (returned `Failed`: a TERMINAL reason, OR a revisable
1154
+ * reason at cap-exhaust) → `onTeamTrackFailed` (render `Failed` +
1155
+ * group-terminal from LOCAL state);
1156
+ * - a thrown marker (state unknown) → `onTeamTrackFailed` (the honest
1157
+ * "round failed, couldn't drive a revise" render; the lease sweeper
1158
+ * eventually terminal-fails the engine track to match).
1159
+ * TEAM-only — caller guards on `validateDiffScope`; single-impl never calls
1160
+ * this. The `taskGroupId` is resolved from the per-task team registry
1161
+ * (`activeBriefByTask`); if it is absent (a non-team task) this is a no-op.
1162
+ * W2 scope: NO revert, NO engine halt-emit (those are W3).
1163
+ *
1164
+ * `gateId` is the track's CURRENT round-N reviewer_quorum gate; REQUIRED for a
1165
+ * revisable reason (the engine rejects a revisable marker without it). The
1166
+ * TERMINAL-reason callers omit it.
1167
+ */
1168
+ private reportTeamTrackFailure;
1169
+ /**
1170
+ * No-op UX (#1) — report the BENIGN no-change terminal for a TEAM track that
1171
+ * produced an empty diff. Sends the engine the `no_change` outcome (→ the engine
1172
+ * marks the track `TrackState::Passed` via `pass_track_and_schedule`; the
1173
+ * scheduler fires the MergeGate on all-terminal-no-failure — NEVER the all-or-
1174
+ * nothing revert) and flips the desktop's OWN track render out of `InFlight` to
1175
+ * `Passed` via the no-`reason` `onTeamTrackUserResolved` chokepoint (track flip
1176
+ * ONLY; the GROUP terminal stays engine-authoritative). Best-effort: a marker
1177
+ * failure is backstopped by the engine lease sweeper. The PARALLEL of
1178
+ * `reportTeamTrackFailure`, but for the benign no-change outcome (no `gateId` —
1179
+ * `no_change` is non-revisable). TEAM-only.
1180
+ */
1181
+ private reportTeamTrackNoChange;
1182
+ /**
1183
+ * #606 (FACE A) — flip the desktop's OWN team track render-terminal from a
1184
+ * per-track USER decision. Called by the shell's `onTerminalDecision` (the
1185
+ * shared TTY + non-TTY gate-resolution chokepoint) AFTER a successful
1186
+ * `applyUserDecision`, with the task's id + the engine's returned `postAction`.
1187
+ *
1188
+ * Resolves the track via the EXISTING `(taskId → taskGroupId, trackIndex)`
1189
+ * correlation (`activeBriefByTask`, registered at TrackAssigned), maps the
1190
+ * `postAction` to the render-terminal state via the locked
1191
+ * `postActionTeamTrackState` (which MIRRORS the engine's
1192
+ * `fire_deferred_track_terminal` disposition), and fires
1193
+ * `onTeamTrackUserResolved` so the shell emits a `team_track_terminal`
1194
+ * (Passed/Failed) through the SAME chokepoint the W1/W2 callbacks use.
1195
+ *
1196
+ * NO-OP (single-impl byte-identical) when:
1197
+ * - the task is not in `activeBriefByTask` (single-impl / unknown task), or
1198
+ * - its `taskGroupId` is empty (single-impl registration), or
1199
+ * - the `postAction` is non-terminal (`open_new_round` / `restart_proposal` /
1200
+ * `continuation_switch_*` → a new gate follows; the track keeps working).
1201
+ *
1202
+ * Best-effort: a throwing listener can NEVER break the decision path (the
1203
+ * desktop never fails a user's decision on a render-flip emit error; the engine
1204
+ * `TaskGroupHalted` convergence is the authoritative group resolution).
1205
+ */
1206
+ reportTeamTrackUserResolved(taskId: string, postAction: PostDecisionAction): void;
1207
+ /**
1208
+ * CP-1.f execute-first — discard the task's shadow (a non-TaskAuthorized
1209
+ * terminal). Best-effort: a teardown failure is logged + leaves the durable
1210
+ * marker for the next-startup GC (#GC-2 lazy cleanup). Forgets the in-memory
1211
+ * handle so a later promote can't run against a torn-down shadow.
1212
+ *
1213
+ * INSTANCE-PINNED (live dogfood race 2026-06-11, option-4 "Reject and restart"):
1214
+ * a RESTART reuses the SAME taskId. The decision-time discard fires
1215
+ * asynchronously (after the `applyUserDecision` SDK await) while the engine
1216
+ * ALSO emits a fresh round-0 GATE_DISPATCH for the restart — which replaces
1217
+ * the task's map entry with the NEW round's shadow. An instance-BLIND
1218
+ * `shadowsByTask.get(taskId)` here would then discard the NEW shadow out from
1219
+ * under the starting implementor ("Implementor round 0 failed — could not be
1220
+ * started"). So the caller captures the shadow INSTANCE at decision time (BEFORE
1221
+ * the SDK await, before the restart dispatch can win the race) and threads it as
1222
+ * `expected`. When `expected` is given:
1223
+ * • it MATCHES the currently-mapped shadow → discard it as before (this round
1224
+ * is genuinely terminal); clear the notes (terminal, no more revise rounds).
1225
+ * • it does NOT match (a NEWER shadow replaced it — the restart dispatch won)
1226
+ * → the captured instance is ORPHANED: tear down ITS directory directly
1227
+ * (each shadow owns a uuid-suffixed dir, so this never touches the live
1228
+ * sibling), but LEAVE the map + the new shadow + the notes untouched (the
1229
+ * restart's fresh-shadow dispatch already cleared + now owns the notes), and
1230
+ * do NOT print the "discarded — tree unchanged" milestone (a debug log only).
1231
+ *
1232
+ * When `expected` is omitted (the internal born-halt / round-failure callers at
1233
+ * runImplementorRound) the behavior is exactly as before — discard whatever is
1234
+ * currently mapped for the task.
1235
+ */
1236
+ discardShadow(taskId: string, expected?: WorkspaceShadow): Promise<void>;
1237
+ private discardShadowLocked;
1238
+ /**
1239
+ * CP-1.f execute-first (#PR-1/#PR-3) — promote the task's shadow onto the real
1240
+ * tree. Driven ONLY by the `TaskAuthorized` Class-B edge (covers all 3
1241
+ * authorize paths). Idempotent (#PR-4): a second call (packet + recovery
1242
+ * double-fire) is a safe no-op via the durable `promoted` marker. Fail-closed
1243
+ * (#SR-2): conflicts/errors are surfaced to the user, never a silent
1244
+ * half-applied tree.
1245
+ */
1246
+ promoteShadow(taskId: string): Promise<void>;
1247
+ /**
1248
+ * CP-12 W2.b (§3.C.2b (d) / H3-4) — promote-quiescence BARRIER for `taskId`.
1249
+ * Enqueues an EMPTY critical section behind any in-flight shadow lifecycle op
1250
+ * (notably `promoteShadow`) for this task on the SAME per-task
1251
+ * `runExclusiveShadowOp` mutex, so it resolves only once that task's shadow-op
1252
+ * chain has fully drained. `runMergeGate` (on `LocalExecutorImpl`) cannot call
1253
+ * the private `runExclusiveShadowOp` directly, so this PUBLIC delegator is the
1254
+ * surface the team LE dep `awaitTaskShadowQuiescent` wires to — mirroring how
1255
+ * `promoteShadow` (above) is the public delegator over the same private mutex.
1256
+ * It awaits any in-flight promote for the task before MergeGate's integration
1257
+ * check reads the real tree, with NO new locking primitive.
1258
+ */
1259
+ awaitTaskShadowQuiescent(taskId: string): Promise<void>;
1260
+ private promoteShadowLocked;
1261
+ private promoteShadowLockedInner;
1262
+ /**
1263
+ * [live task status timer §3] The promote TAIL — (re)opens the busy window with
1264
+ * `promoting`, applies the reviewed `diff`, then emits either the `promoted`
1265
+ * milestone (success) or returns silently (marker-flip no-op / apply-conflict).
1266
+ * Extracted from `promoteShadowLockedInner` so that method's `try/finally` wraps
1267
+ * EXACTLY the post-`promoting` strand exits — never the pre-`promoting`
1268
+ * no-op/re-delivery/discard returns (which must stay silent). All emits carry the
1269
+ * caller-resolved `originEpoch`.
1270
+ */
1271
+ private promoteApplyLocked;
1272
+ /** #SR-2 — surface a halt/fail-closed message to the user (or log if unwired). */
1273
+ private surfaceHalt;
1274
+ /**
1275
+ * PHASE-598 — the thin wiring that supplies the real fs/shell deps to the pure
1276
+ * `runDeclaredTestSurfaces` executor (unit tests inject mocks into the free
1277
+ * function; integration tests stub the executor's `node` runs via
1278
+ * `QuorumLoopDeps.runShell`). `readdir` is wrapped to `{ withFileTypes: true }`
1279
+ * so the executor sees `Dirent`s (the directory-expansion symlink discriminate).
1280
+ */
1281
+ private runDeclaredTestSurfaces;
1282
+ /**
1283
+ * [live task status timer §3] Origin epoch for an emit context. Resolves from
1284
+ * the ORIGINATING task's id (never `this.taskEpoch` read at emit time — an async
1285
+ * re-delivery may run after a LATER `startTask`). Callers pass their in-scope
1286
+ * taskId (or the `taskByGateId[gateId]` bridge). `?? this.taskEpoch` defends a
1287
+ * post-restart entry (a bounded residual, per design).
1288
+ */
1289
+ private originEpochFor;
1290
+ /**
1291
+ * CP-1.f live progress surfacing — emit ONE desktop-local progress phase.
1292
+ * Failure-safe: a throwing listener is caught + logged, NEVER propagated into
1293
+ * the loop (a render bug must not break the quorum). Synchronous — never
1294
+ * awaited, never blocks the loop. No-op when unwired (tests / non-loop).
1295
+ */
1296
+ private emitProgress;
1297
+ /**
1298
+ * [token counter — Phase 2 · P2-2] Strictly-ADDITIVE per-task token accrual.
1299
+ * `n` is clamped by `finiteToken` (P2-0) so a malformed CLI field contributes 0,
1300
+ * never poisons the accumulator (→ `NaN`/regression). Additive-only means the
1301
+ * reducer's `Math.max` tick-refresh carry is idempotent (a re-delivery never
1302
+ * decreases). No-op for a null taskId or a 0 clamp.
1303
+ */
1304
+ private addTaskTokens;
1305
+ /**
1306
+ * [token counter — Phase 2 · P2-2] The per-task CUMULATIVE total for stamping.
1307
+ * Null-tolerant (MED-1: `seat_update`/`verdicts_progress` resolve taskId via
1308
+ * `taskByGateId[gateId]`, `undefined` after a restart). The `|| undefined`
1309
+ * (falsy-collapse, NOT `??`) is load-bearing: a `0`/absent entry OMITS the
1310
+ * ` · ↓ <tokens>` segment rather than rendering `↓ 0`. Mirrors `originEpochFor`.
1311
+ */
1312
+ private tokensFor;
1313
+ /**
1314
+ * [token counter — Phase 2 · P2-3] Emit a progress phase with BOTH the entry-
1315
+ * cached `originEpoch` AND the LIVE token total injected at CALL time. CRITICAL
1316
+ * (Codex-H3): `tokensFor(taskId)` is read HERE, per emit — NOT cached at function
1317
+ * entry — because `addTaskTokens` (implementor exit, reviewer success) runs BEFORE
1318
+ * the same function's later `diff_captured`/`submitting_diff`/`verdicts_progress`
1319
+ * emits; caching at entry would stamp the OLD total and the last reviewer's tokens
1320
+ * would vanish when `waiting_user` closes the window. Only `originEpoch` is entry-
1321
+ * cached (correct — the epoch is fixed for the task); tokens are read fresh.
1322
+ */
1323
+ private emitProgressForTask;
1324
+ /**
1325
+ * CP-1.f live progress surfacing — cheap, best-effort, time-bounded count of
1326
+ * the files changed so far in the shadow for the per-tick status line. Bounded
1327
+ * to the tracked set; SKIPPED (returns undefined) when the workspace is large
1328
+ * (`TICK_SCAN_MAX_TRACKED`) or the scan exceeds `TICK_SCAN_TIMEOUT_MS`. NEVER
1329
+ * throws + NEVER blocks the loop (the tick fire-and-forgets it). Returns ONLY
1330
+ * a count — never any file content or path payload.
1331
+ */
1332
+ private tickFileChangeCount;
1333
+ /**
1334
+ * Submit the execute-first artifact with NotFound retry-with-backoff, and
1335
+ * RETURN the engine's `{ gateId, workflowState }` so the caller can read the
1336
+ * SYNCHRONOUS born-halt label (#EF-1 / agy M4). On exhausted retries the last
1337
+ * error propagates (the caller surfaces it fail-closed).
1338
+ */
1339
+ private submitImplementorOutputWithRetry;
1340
+ private handleReviewerDispatch;
1341
+ /**
1342
+ * §3.C.21 — spawn + run ONE reviewer seat, then submit its verdict.
1343
+ * Double-guarded (§3.C.21b LOCK): a seat already running-in-memory OR
1344
+ * already verdict-submitted is a no-op, so a live packet + a recovery
1345
+ * re-spawn never double-run the same seat. On a reviewer error/timeout, a
1346
+ * synthesized ESCALATE verdict is submitted (#C1F-8) so the gate never hangs.
1347
+ */
1348
+ spawnOneSeat(args: {
1349
+ gateId: string;
1350
+ seatId: number;
1351
+ role: ReviewerRole;
1352
+ agentKind: ReviewerAgentKind;
1353
+ }): Promise<void>;
1354
+ /**
1355
+ * Audited-path fix (Fix 9) — run `reviewerRegistry.evaluate` under the
1356
+ * DEFENSE-IN-DEPTH per-seat wall-clock guard. Returns the verdict if `evaluate`
1357
+ * settles within `SEAT_EVALUATE_TIMEOUT_MS`; throws `SeatTimeoutError` if the
1358
+ * guard fires first. The abandoned `evaluate` promise is given a no-op `.catch`
1359
+ * so a late rejection (the subprocess `runReviewer` self-kill at 5 min throws a
1360
+ * `timeout` ReviewerError) does NOT surface as an unhandled rejection. The guard
1361
+ * never touches any process — `runReviewer` owns + kills its own child.
1362
+ */
1363
+ private evaluateSeatWithTimeout;
1364
+ /**
1365
+ * CP-7 §8 (Stage-1-resolved) — engage the trusted-execution substrate +
1366
+ * broker for ONE reviewer seat (mirrors the implementor's per-spawn engage).
1367
+ * Returns `{ handle?, teardown }`:
1368
+ * - `handle` PRESENT → sandboxed; the seat's model call routes through the
1369
+ * broker (no ambient vendor creds reach the reviewer subprocess).
1370
+ * - `handle` ABSENT → the seat runs the LEGACY unsandboxed path.
1371
+ * - THROWS `ReviewerErrorClass` (spawn_failed) → FAIL-CLOSED: a Trusted-
1372
+ * agent broker-engage failure. The caller turns this into a clean
1373
+ * synthesized ESCALATE — it does NOT fall back to the ambient-cred legacy
1374
+ * path (that defeats the moat).
1375
+ *
1376
+ * TRI-STATE (decided here, mirroring the implementor LE):
1377
+ * 1. No engager wired OR `CODEVIBE_SANDBOX_REVIEWERS` not opted in (DEFAULT
1378
+ * OFF; opt in with `=1`/`=true`) → LEGACY (byte-identical non-regression).
1379
+ * No badge.
1380
+ * 1b. No resolvable taskId (cannot scope a per-task broker) → LEGACY + a
1381
+ * reduced-trust badge (Stage-2 r1 Codex LOW — a trust-downgrade must be
1382
+ * VISIBLE, never a silent ambient-cred fallback; see
1383
+ * `surfaceReviewerReducedTrust` at the no-taskId branch below).
1384
+ * 2. GEMINI (non-Trusted, §6 Q4) → LEGACY + reduced-trust badge. We do NOT
1385
+ * call the engager for Gemini (it would throw the non-Trusted error);
1386
+ * pre-checking the agent kind avoids string-matching that throw and keeps
1387
+ * a Gemini seat RUNNING (just unsandboxed + badged), never escalated.
1388
+ * 3. Engager returns `reduced_trust` (no runtime / coarse host) → LEGACY +
1389
+ * reduced-trust badge.
1390
+ * 4. Engager returns `substrate` → SANDBOXED. A `reducedTrust` (A5 coarse)
1391
+ * result still runs sandboxed but surfaces the coarse badge.
1392
+ * 5. Engager THROWS for a Trusted agent (broker-start failure, etc.) →
1393
+ * FAIL-CLOSED (re-thrown as ReviewerErrorClass spawn_failed).
1394
+ *
1395
+ * `teardown()` is ALWAYS safe to call (a no-op on the legacy path); the caller
1396
+ * invokes it in a `finally` so a sandbox never leaks.
1397
+ */
1398
+ private engageSeatSubstrate;
1399
+ /**
1400
+ * CP-7 §8 (Stage-1-resolved) — surface the PROMINENT reduced-trust badge for a
1401
+ * reviewer seat (mirrors the implementor's badge, scoped to the loop's
1402
+ * available NOTIFICATION channel). The moat being OFF/coarse for a reviewer is
1403
+ * VISIBLE + auditable, never silent: a user-visible NOTIFICATION (via
1404
+ * `surfaceHalt`, the loop's NOTIFICATION surface) + a WARN log. Best-effort —
1405
+ * unlike the implementor's `tier:'none'` path this is NOT delivery-gated,
1406
+ * because a reviewer reduced-trust badge does not change whether the seat
1407
+ * RUNS (it always runs; the badge only informs the user).
1408
+ */
1409
+ private surfaceReviewerReducedTrust;
1410
+ /**
1411
+ * Wave B §2.1 confinement TRI-STATE for a merge-gate model/reviewer invocation
1412
+ * (LOCK #C14-MOAT). Path A: the CP-7 substrate/broker (opted in + a Trusted agent
1413
+ * + engage succeeds → a handle; no ambient vendor cred in the child). Path B: a
1414
+ * trusted-container boundary (legacy spawn confined by the OUTER container — the
1415
+ * dogfood/E2E path). Path C: neither → NOT confined. A path-A engage that
1416
+ * throws/reduced-trusts falls through to B/C (both safe: B confined, C refuses),
1417
+ * so a broker hiccup never silently runs unconfined.
1418
+ */
1419
+ private engageWaveBConfinement;
1420
+ /**
1421
+ * Wave B §2.1 — build the CONFINED class-2 model caller for a merge gate, or
1422
+ * `null` on path C (no confinement → the gate runs without a resolver; conflicts
1423
+ * halt to the human gate; no unconfined LLM resolver ever runs — LOCK #C14-MOAT).
1424
+ * The model call reuses the proven reviewer subprocess (`runReviewer`, `claude
1425
+ * --print`, read-only allowlist), routed through the substrate broker on path A
1426
+ * or the trusted-container legacy spawn on path B.
1427
+ */
1428
+ makeConflictModelCaller(args: {
1429
+ ownerTaskId: string;
1430
+ }): Promise<{
1431
+ caller: ConflictModelCaller;
1432
+ teardown: () => Promise<void>;
1433
+ } | null>;
1434
+ /**
1435
+ * CP-14 WB2 §2.1/§2.2/§2.3 — build the AGENTIC class-2 resolver, or `null` on
1436
+ * path C (no confinement). When wired, a slot's class-2 conflicts are resolved
1437
+ * by re-dispatching the INCOMING track's implementor (read+write) inside a
1438
+ * disposable repo, then fd-safe selective-importing ONLY the conflicted files.
1439
+ * SECURITY-CRITICAL — fail-closed on BOTH layers:
1440
+ *
1441
+ * 1. CONSTRUCTION-time (mirrors `makeConflictModelCaller` returning null): probe
1442
+ * `engageWaveBConfinement` ONCE against the loop workdir; path C → return
1443
+ * `null` → `resolveAgentic` is NOT set on the context → a fresh class-2
1444
+ * conflict takes the legacy `caller` path (which on path C is the
1445
+ * CONFINEMENT-UNAVAILABLE caller → human-gate). Byte-identical to today.
1446
+ * 2. DISPATCH-time (Stage-2 Codex HIGH — confinement BOUND TO THE ACTUAL SPAWN):
1447
+ * `dispatchImplementor` spawns with `requireConfined: true`, so
1448
+ * `LocalExecutor.spawnImplementor` → `engageSubstrateForSpawn` THROWS
1449
+ * `SpawnConfinementUnavailable` on EVERY non-substrate-engaged outcome (no
1450
+ * engager / pre-task / `reduced_trust`) instead of returning the legacy
1451
+ * unsandboxed args. The closure maps that throw → `AgenticDispatchUnconfinedError`
1452
+ * → `resolveAgenticSlot` → a clean `human_gate`. NEVER an unconfined spawn.
1453
+ *
1454
+ * The confining substrate is established by `spawnImplementor` itself
1455
+ * (`engageSubstrateForSpawn`, keyed on the real `taskId` + `workingDir =
1456
+ * realpath(disposableRepo)`), which mounts ONLY the disposable repo. There is NO
1457
+ * separate per-resolve probe (the prior "prove then tear down then spawn" design
1458
+ * proved confinement on a handle the LE discarded — Codex correctly flagged that the
1459
+ * separate engage could still yield `reduced_trust`; the `requireConfined` spawn IS
1460
+ * now the sole, correctly-bound dispatch-time gate). The construction probe (layer 1)
1461
+ * is only the coarse wire-vs-legacy decision. On a path-B host with no FS-scoping
1462
+ * substrate (the dogfood — bwrap can't namespace a nested container) WB2 fail-closes
1463
+ * here; a successful resolve requires a real path-A substrate (production).
1464
+ */
1465
+ makeAgenticResolver(args: {
1466
+ ownerTaskId: string;
1467
+ }): Promise<{
1468
+ resolveAgentic: NonNullable<ConflictResolverContext['resolveAgentic']>;
1469
+ teardown: () => Promise<void>;
1470
+ } | null>;
1471
+ /**
1472
+ * Wave B §2.3 (LOCK #C14-FINAL-TIER2 / #C14-MOAT) — dispatch the mandatory
1473
+ * final-tip Tier-2 reviewer over the integration diff (base → tip), CONFINED via
1474
+ * the §2.1 tri-state. Returns `confined: false` on path C (the gate fails closed,
1475
+ * NO-PROMOTE). A non-APPROVE verdict OR a reviewer error → `reject` (fail-safe: an
1476
+ * LLM byte never promotes on an unclear Tier-2). Reuses the per-gate reviewer
1477
+ * registry + the read-only reviewer allowlist over the resolved review surface.
1478
+ */
1479
+ dispatchFinalTier2Review(args: {
1480
+ ownerTaskId: string;
1481
+ repoDir: string;
1482
+ baseSha: string;
1483
+ integrationTip: string;
1484
+ /** CP-14 WB2 §2.4 — the class-2 resolver agent(s) this gate (for independent selection). */
1485
+ resolverAgents?: ReadonlySet<string>;
1486
+ /** Forward-compat desktop override (no surface today; honored only when host-detected). */
1487
+ tier2ReviewerOverride?: AgentKind | null;
1488
+ /**
1489
+ * CP-14 WB2 §2.6 (additive) — the per-conflict 3-way CONTENT + per-track INTENT
1490
+ * diffs to fold into the reviewer prompt. ALL OPTIONAL: absent ⇒ today's bare
1491
+ * base→tip diff (graceful degrade). The aggregate bundle is byte-bounded with
1492
+ * fail-closed overflow (over `WAVE_B_TIER2_MAX_BUNDLE_BYTES` → reject/NO-PROMOTE).
1493
+ */
1494
+ conflictBundle?: readonly RenderedConflict[];
1495
+ trackIntentDiffs?: readonly TrackIntentDiff[];
1496
+ }): Promise<{
1497
+ verdict: 'pass' | 'reject';
1498
+ confined: boolean;
1499
+ reason?: string;
1500
+ }>;
1501
+ /**
1502
+ * Synthesize an ESCALATE verdict for a seat that could NOT produce a real
1503
+ * verdict, so the gate resolves instead of hanging (#C1F-8).
1504
+ *
1505
+ * CP-1.f sanitization invariant (LOAD-BEARING): the wire `reasoning` is a
1506
+ * CLEAN, classified one-liner from the fixed template table — NEVER raw
1507
+ * stderr / stdout / the raw `spawn_failed.reason`. The `synthesized ESCALATE
1508
+ * (#C1F-8): …` audit prefix lives ONLY in logs (the caller logs it), never
1509
+ * on the wire.
1510
+ *
1511
+ * @param reasoning the final clean wire `reasoning` (already a fixed template).
1512
+ */
1513
+ private synthesizeEscalate;
1514
+ private submitVerdict;
1515
+ /**
1516
+ * On WS (re)connect/startup, poll for the seats assigned to this desktop for
1517
+ * any in_review gate of the active task and spawn any seat NEITHER already
1518
+ * running-in-memory NOR already verdict-submitted (double-guard). Without
1519
+ * this, a REVIEWER_DISPATCH dropped during a WS gap hangs the gate forever.
1520
+ */
1521
+ recoverInReviewAssignments(): Promise<void>;
1522
+ /**
1523
+ * CP-1.f execute-first (#GC-1 / #PR-4) — on session-start/WS-resync, GC
1524
+ * orphaned shadow dirs (un-signalled auto-halts leave NO terminal edge, so
1525
+ * their temp dirs would leak) while NEVER deleting an authorized-but-pending
1526
+ * shadow. The active task's shadow is KEPT (it may still promote/discard); a
1527
+ * `promoted`-or-expired orphan is reaped.
1528
+ *
1529
+ * Promote-on-recovery (#PR-4) of a dropped `TaskAuthorized`: the durable
1530
+ * `pending`→`promoted` marker makes promote idempotent, so a re-delivered
1531
+ * `TaskAuthorized` packet after reconnect promotes safely (the live edge is
1532
+ * the recovery path). A `getTaskState` query for an eager promote of an
1533
+ * authorized-but-pending shadow whose packet was permanently lost is the only
1534
+ * residual — until then the GC reaper KEEPS such a shadow (it is never reaped
1535
+ * while `pending` and un-expired), so the approved code is never lost; a
1536
+ * re-delivered packet (or the next live edge) promotes it.
1537
+ */
1538
+ recoverShadows(): Promise<void>;
1539
+ /**
1540
+ * §4.3 — true while a team recovery is seeding briefs (the startup barrier is
1541
+ * down). The Class B consume path checks this to BUFFER a live TEAM packet
1542
+ * instead of consuming it before PASS-1 seeding finishes. Single-impl /
1543
+ * non-team packets are unaffected (the barrier is team-scoped — the caller
1544
+ * only buffers TEAM packets).
1545
+ */
1546
+ isRecoverySeeding(): boolean;
1547
+ /**
1548
+ * §4.3 (Stage-2 r1 Codex MED) — true if `(taskGroupId, trackIndex)` carries a
1549
+ * corrupt-brief TOMBSTONE. The LE consults this at the TOP of `launchTrack`
1550
+ * (before any register / spawn / lease-renewing progress emit) so a tombstoned
1551
+ * track is refused on the LIVE path as early as on the recovery path — never
1552
+ * spawning an empty-brief implementor or mutating registry state for it. The
1553
+ * engine sweeper times the un-launched track out (bounded).
1554
+ *
1555
+ * CP-12 W4 (#593 Stage-2 r2): keyed by `(taskGroupId, trackIndex)` so a corrupt
1556
+ * brief in group A never blocks a different group B's track with the same index.
1557
+ */
1558
+ isTrackTombstoned(taskGroupId: string, trackIndex: number): boolean;
1559
+ /**
1560
+ * #592 PROVENANCE GUARD (live path) — true if `taskGroupId` is a team group this
1561
+ * desktop did NOT originate AND this desktop owns ≥1 group. Wired to the LE's
1562
+ * `isForeignTeamGroup` team-dep, consulted at the TOP of `launchTrack` (right
1563
+ * after the tombstone gate) so a foreign group's LIVE `TrackAssigned` is refused
1564
+ * before any spawn — the symmetric counterpart to the recovery-path filter in
1565
+ * `recoverTeamTracks` / `launchRecoveredTrack`. Returns FALSE when the owned-set
1566
+ * is empty (fresh process / post-crash) so own-group launch + crash-recovery are
1567
+ * unaffected. See `ownedGroupIds`.
1568
+ */
1569
+ isForeignTeamGroup(taskGroupId: string): boolean;
1570
+ /**
1571
+ * §4.3 — buffer a live TEAM Class B packet while the recovery seed barrier is
1572
+ * down. Returns true if buffered (the caller must NOT consume it live). The
1573
+ * caller (`index.ts` onPacket / the LE consume path) only routes TEAM packets
1574
+ * here; single-impl / non-team packets bypass this entirely.
1575
+ */
1576
+ bufferTeamPacketDuringSeeding(kind: 'trackAssigned' | 'gateDispatch', payload: unknown, envelopeTaskId?: string): boolean;
1577
+ /**
1578
+ * §4.3 — drain the buffered live TEAM packets through the normal live handlers
1579
+ * AFTER the recovery seed barrier lifts (`recoverySeedingDepth === 0`). Each
1580
+ * handler now reads the SEEDED brief (or hits the corrupt tombstone and refuses
1581
+ * to launch). `seenGateDispatchRunIds` dedups a packet both recovery and the
1582
+ * drained-live path would drive. Best-effort — a per-packet throw is logged.
1583
+ */
1584
+ private drainBufferedTeamPackets;
1585
+ /**
1586
+ * §4.3 (Stage-2 r1 Codex HIGH) — run a recovery op UNDER the refcount barrier.
1587
+ * Increments `recoverySeedingDepth` SYNCHRONOUSLY (before `fn`'s first GSI
1588
+ * query, so a live TEAM packet arriving during that query is buffered, not
1589
+ * consumed with an empty brief), then decrements in `finally`; the buffered
1590
+ * queue drains ONCE when the count returns to 0 (so concurrent B.1+B.2 share
1591
+ * one barrier window and drain exactly once after BOTH finish). `fn` is
1592
+ * already fail-open internally; a throw still releases the barrier + drains.
1593
+ */
1594
+ private withRecoveryBarrier;
1595
+ /**
1596
+ * §4.2 / §4.3 (Stage-2 r2 Stage-1 M1) — run the `/team` CREATE + brief-bind under
1597
+ * the SAME refcount seed barrier the recovery path uses. The parse-time briefs
1598
+ * can only be bound by `(taskGroupId, trackIndex)` AFTER `createTaskGroup`
1599
+ * returns the group id; the engine emits the per-track `TrackAssigned`/round-0
1600
+ * `GateDispatch` BEFORE that response, so when the Class B subscription is
1601
+ * already up a live packet can be delivered DURING the create round-trip. Running
1602
+ * the whole create+bind under this barrier makes such an in-window packet BUFFER
1603
+ * (`isRecoverySeeding()` true → `bufferTeamPacketDuringSeeding`) instead of
1604
+ * registering with an empty brief; the barrier releases + drains AFTER the briefs
1605
+ * are bound, so the buffered packet re-consumes with the correct brief. The
1606
+ * `seenGateDispatchRunIds` dedup makes the subsequent B.1 a no-op for an
1607
+ * already-launched track. Generic — returns the fn's value (the new group id).
1608
+ */
1609
+ runTeamCreateUnderSeedBarrier<T>(fn: () => Promise<T>): Promise<T>;
1610
+ /**
1611
+ * §4.2 B.1 — arm the B.1 two-condition latch with the just-created
1612
+ * `taskGroupId`. If the subscription is already established, fire B.1
1613
+ * immediately (the common case where the subscription was up before
1614
+ * `createTaskGroup` returned).
1615
+ */
1616
+ armB1Recovery(taskGroupId: string): void;
1617
+ /**
1618
+ * §4.2 — the subscription-establishment signal (`onSubscribed`). Sets the
1619
+ * `subscriptionReady` latch (firing a pending B.1) and ALWAYS runs B.2
1620
+ * discovery. Fires on the INITIAL connect (`isReconnect=false`, the restart
1621
+ * case B.2 exists for) AND a reconnect (`isReconnect=true`). B.1 and B.2 each
1622
+ * take the refcount barrier (`withRecoveryBarrier`) which is incremented
1623
+ * SYNCHRONOUSLY here (before this returns), so any live TEAM packet processed
1624
+ * after this point buffers until BOTH recovery ops finish seeding.
1625
+ */
1626
+ onSubscriptionReady(isReconnect: boolean): void;
1627
+ /**
1628
+ * §4.2 B.1 — strong-read the just-created group's in-flight tracks and recover
1629
+ * them UNDER the barrier. Capture + CLEAR `pendingB1TaskGroupId` immediately
1630
+ * so a later reconnect's `onSubscribed` does not re-run B.1 for an
1631
+ * already-handled group. Fail-open: a query throw warns + returns (the barrier
1632
+ * still releases + drains via `withRecoveryBarrier`'s `finally`).
1633
+ */
1634
+ private fireB1;
1635
+ /**
1636
+ * §4.2 B.2 — discovery on subscription establishment, with a bounded GSI-lag
1637
+ * retry. Each attempt queries the GSI then IMMEDIATELY recovers that attempt's
1638
+ * rows (NOT accumulate-then-recover — `relaunch` is decided from the lease live
1639
+ * at query time, so recovering each attempt's rows immediately avoids a
1640
+ * near-expiring lease lapsing during backoff). Keeps querying across the
1641
+ * bounded window to catch GSI-lagged groups (does NOT stop on first non-empty).
1642
+ * Idempotent via `seenGateDispatchRunIds`. Retry-skip optimization (§4.2): on a
1643
+ * RECONNECT where no team work ran this session AND the first query is empty,
1644
+ * skip the remaining retries (no GSI lag possible). On the INITIAL connect the
1645
+ * full retry ALWAYS runs (a restarted process has `teamSessionActive`
1646
+ * stale-false yet may have orphaned team work). Fail-open per attempt.
1647
+ */
1648
+ runB2Recovery(isReconnect: boolean): Promise<void>;
1649
+ private runB2RecoveryAttempts;
1650
+ /**
1651
+ * §4.1 — the TWO-PASS recovery core. PASS 1 decrypts + seeds EVERY brief for
1652
+ * all non-terminal tracks (the SEED set, incl. `Pending`) BEFORE any launch, so
1653
+ * the live launch plumbing (bridge → getTeamBrief → registerTeamTaskBrief →
1654
+ * drain), any buffered-dispatch drain, AND a LATER live dispatch of a
1655
+ * capacity-held `Pending` track all read the CORRECT brief — no empty window.
1656
+ * PASS 2 relaunches ONLY the relaunch subset. The ordering BARRIER (§4.3) is
1657
+ * owned by the CALLER (`fireB1` / `runB2Recovery` via `withRecoveryBarrier`),
1658
+ * which holds it across the WHOLE recovery window + drains once at the end —
1659
+ * so this method just seeds + relaunches (it is always invoked under a barrier
1660
+ * that is already down, and may run multiple times within one barrier window).
1661
+ */
1662
+ recoverTeamTracks(rows: TeamTrackRecoveryRow[]): Promise<void>;
1663
+ /**
1664
+ * §4.1 PASS 2 (one row) — validate → recoverLaunchTrack → repair-on-duplicate →
1665
+ * drive the round-0 gate. Honors the corrupt-brief tombstone + the
1666
+ * `seenGateDispatchRunIds` dedup + the #592 provenance guard. Called ONLY from
1667
+ * `recoverTeamTracks` PASS 2 (the recovery dispatch chokepoint); the buffered
1668
+ * live-TrackAssigned drain (§4.3) routes through `consumeClassB` →
1669
+ * `LocalExecutorImpl.launchTrack` (NOT here), where the LE's `isForeignTeamGroup`
1670
+ * gate is the live-path counterpart (Stage-1 r1 HIGH / Stage-2 advisory). Pre: the
1671
+ * brief is already seeded (PASS 1) or legitimately empty.
1672
+ */
1673
+ private launchRecoveredTrack;
1674
+ /**
1675
+ * §4.1 — reconstruct the round-0 `GateDispatchPayload` from a recovery row.
1676
+ * Round-0 is SEAT-LESS (the proposal hasn't arrived, no reviewer seats yet), so
1677
+ * `verificationChecks` / `reviewerSeats` / `finalApprovalSurface` are absent —
1678
+ * matching the live round-0 emit (`start_ports.rs` sets `reviewer_seats: None`).
1679
+ */
1680
+ private reconstructRound0GateDispatch;
1681
+ private handleReviseFeedback;
1682
+ /** The implementor agent the loop spawns (set at start_task). */
1683
+ private activeImplementorAgent;
1684
+ setActiveImplementorAgent(agent: AgentKind): void;
1685
+ /**
1686
+ * #585 #C10M-9 — resolve the active task's request context for `/continue
1687
+ * request` (the entrypoint wires this to the slash route's
1688
+ * `getActiveRequestContext`). Returns `null` when no implementor round is
1689
+ * active (no gate to hand off from). The `gateId`/`roundNumber` come from the
1690
+ * ACTIVE implementor registry (the round currently running); the brief +
1691
+ * source agent come from the loop's active state.
1692
+ */
1693
+ getActiveContinuationRequestContext(): {
1694
+ taskId: string;
1695
+ gateId: string;
1696
+ roundNumber: number;
1697
+ brief: string;
1698
+ sourceAgent: AgentKind;
1699
+ } | null;
1700
+ /**
1701
+ * #585 L-A — mint a fresh `expiresAt = now + TTL` at millisecond precision
1702
+ * (RFC-3339). Fresh per request so a CANCEL→re-request produces a distinct
1703
+ * hash preimage → distinct `packet_hash` → the FRESH-write path re-fires the
1704
+ * prompt (#C10M-12). NEW behavior #585 adds (no production minting site
1705
+ * existed; `expiresAt` is a caller-supplied `PacketWriteInput` field).
1706
+ */
1707
+ private mintExpiresAt;
1708
+ /**
1709
+ * #585 L2 — resolve the text used to classify a quota failure. In TEST mode
1710
+ * ONLY (`NODE_ENV === 'test'`), the force-quota env var (assembled from
1711
+ * fragments below, see the inline comment) injects a synthetic usage-limit
1712
+ * stderr for the named agent so the `continuation-handoff-quota` E2E scenario
1713
+ * is reproducible in CI (the real quota error is not naturally reproducible).
1714
+ * The force-quota literal is asserted ABSENT from the published esbuild bundle
1715
+ * by the `verify-no-test-hooks` build step (§5.5 / dimension-8
1716
+ * prod-unreachability) — hence the fragment-assembled key + the verbatim
1717
+ * literal NEVER appearing in this file. The branch is `NODE_ENV==='test'`-gated
1718
+ * so it is never evaluated in prod regardless.
1719
+ */
1720
+ /**
1721
+ * #585 §5.5 / L2 — the SINGLE source of the force-quota test gate. Returns true
1722
+ * when (a) `NODE_ENV === 'test'` AND (b) the fragment-assembled force-quota env
1723
+ * var names `agent`. The env-var KEY is assembled from fragments so the
1724
+ * force-quota literal NEVER appears verbatim in the published bundle (the
1725
+ * `verify-no-test-hooks` CI grep asserts its absence);
1726
+ * the whole gate is ALSO `NODE_ENV==='test'`-gated so it is never evaluated in
1727
+ * prod. Drives BOTH the synthetic quota stderr (`resolveQuotaClassificationText`)
1728
+ * AND the forced empty-diff born-halt (`runImplementorRound` capture site) — the
1729
+ * two halves that make the `continuation-handoff-quota` E2E reproducible (a real
1730
+ * model will not deterministically exit with NO diff, which the quota fork
1731
+ * requires).
1732
+ */
1733
+ private isForcedQuotaAgent;
1734
+ /**
1735
+ * Test seam — force the ROUND-0 implementor agent (overriding the session
1736
+ * default). `NODE_ENV==='test'`-gated; the env key is fragment-assembled so the
1737
+ * literal never appears in a published bundle (`verify-no-test-hooks` greps for
1738
+ * it). Makes the `codex-implementor-edits` E2E deterministic: a Codex
1739
+ * implementor exercises `codex exec` end-to-end (the substrate-aware sandbox
1740
+ * rewrite) where the session default would otherwise pick Claude. Returns the
1741
+ * forced agent, or null when unset / in prod.
1742
+ */
1743
+ private resolveForcedImplementorAgent;
1744
+ private resolveQuotaClassificationText;
1745
+ /**
1746
+ * #585 §3.5 / §3.6 — the `quota_exhausted` fork. The implementor halted on a
1747
+ * quota error (born-halt or, defensively, a throw). Bound-check the
1748
+ * quota-substitution loop (#C10M-12), discard the shadow (the real tree was
1749
+ * never touched), emit the `continuation_offered` milestone (NOT
1750
+ * `round_failed`, L-B), and fire `writer.write()` with
1751
+ * `blockedReason:'quota_exhausted'` + the dead round's gateId + a fresh ms
1752
+ * `expiresAt` — the server stamps the gate + emits the handoff prompt.
1753
+ */
1754
+ private handleQuotaExhaustedFork;
1755
+ /**
1756
+ * #585 §3.5 / #C10M-3/9/16 — the trigger transport. Builds a
1757
+ * `PacketWriteInput` (fresh ULID `offerId`, the trigger's `blockedReason`,
1758
+ * the round's `gateId`, a fresh ms `expiresAt`) and calls
1759
+ * `ContinuationPacketWriter.write()` DIRECTLY (local CONTINUATION.md + the
1760
+ * already-wired `recordContinuationPacketWritten` mutation → the server
1761
+ * stamps `handoff_pending` + emits the prompt). For the `user_requested`
1762
+ * trigger (`abortActive: true`) it FIRST claims the handoff + aborts the
1763
+ * running implementor (Codex-H1/#C10M-16) so the original implementor is
1764
+ * suppressed. Returns the minted `offerId` (or null on failure).
1765
+ */
1766
+ requestContinuation(input: {
1767
+ taskId: string;
1768
+ gateId: string;
1769
+ sourceAgent: AgentKind;
1770
+ blockedReason: HandoffReason;
1771
+ brief: string;
1772
+ abortActive: boolean;
1773
+ targetAgent?: AgentKind;
1774
+ onOfferMinted?: (offerId: string) => void;
1775
+ }): Promise<{
1776
+ offerId: string;
1777
+ } | null>;
1778
+ /**
1779
+ * #585 #C10M-16 — claim the handoff for the active task: set
1780
+ * `handoffClaimed = true` + abort the running implementor's spawn so the
1781
+ * post-`await handle.done` path suppresses the late capture/submit/surfaceHalt.
1782
+ * Idempotent; a no-op when no implementor is active for the task.
1783
+ */
1784
+ private claimHandoffForActiveTask;
1785
+ /**
1786
+ * #585 §2.3 / #C10M-16 — the `onApplyUserDecision` consumer for the
1787
+ * orchestration shell. Routes a `continuation_switch_accepted` action to
1788
+ * `resumeFromContinuation`, deduped against the local-accept path via
1789
+ * `resumedOffers` keyed on `action.offer_id`. A LOCAL accept already added
1790
+ * its `offer_id` (so its own echo is SUPPRESSED here); a FOREIGN
1791
+ * (mobile-originated) accept drives EXACTLY ONE resume. A `declined` echo
1792
+ * clears the prompt state (handled by the reducer via the SUBMIT path; here
1793
+ * it is a no-op for the resume side). Wired by the shell to the
1794
+ * `AppSyncClient.subscribeToApplyUserDecision` callback.
1795
+ */
1796
+ onApplyUserDecisionEcho(event: {
1797
+ taskId: string;
1798
+ gateId: string;
1799
+ decision: string;
1800
+ action: PostDecisionAction;
1801
+ }): Promise<void>;
1802
+ /**
1803
+ * #585 §3.5 / #C10M-11/13 — resume the task with the user's chosen agent.
1804
+ * The SERVER has ALREADY minted the `nextGateId` row + advanced to
1805
+ * `GatePlanInFlight{nextGateId}` (agy-H1) — the desktop does NOT mint a gate;
1806
+ * it spawns into the server-created gate. Reads the CP-6 `CONTINUATION.md`
1807
+ * (hash + backend-audit verified — refuses a `PacketUnverified` packet, the
1808
+ * pre-spawn fidelity gate), builds the resume brief from the packet's
1809
+ * WORK-CONTEXT fields ONLY (NEVER `packet.targetAgent` — the user's choice is
1810
+ * authoritative, #C10M-13), `setActiveImplementorAgent(targetAgent)`, and
1811
+ * re-enters `runImplementorRound` with the SERVER-supplied `nextGateId`/
1812
+ * `nextRound`. Idempotent on the deterministic `nextGateId` via the
1813
+ * `inFlightImplementorGates`/`submittedOutputGates` double-guard.
1814
+ */
1815
+ resumeFromContinuation(input: {
1816
+ taskId: string;
1817
+ targetAgent: AgentKind;
1818
+ nextGateId: string;
1819
+ nextRound: number;
1820
+ }): Promise<boolean>;
1821
+ /**
1822
+ * #585 #C10M-13 — build the resume brief from the packet's WORK-CONTEXT
1823
+ * fields ONLY. MUST NOT include `packet.targetAgent` or any agent-name
1824
+ * directive (so it can never contradict the user's chosen agent). Pure +
1825
+ * deterministic.
1826
+ */
1827
+ private buildResumeBrief;
1828
+ /**
1829
+ * #585 #C10M-16 — record that a LOCAL accept (typed-digit or `/continue
1830
+ * accept`) drove `resumeFromContinuation` inline, so the `onApplyUserDecision`
1831
+ * own-echo for that SAME offer is suppressed (exactly ONE resume per offer).
1832
+ * Called by the local-accept path BEFORE driving the resume.
1833
+ */
1834
+ markOfferResumed(offerId: string): void;
1835
+ /** @internal — test introspection for the active-implementor registry. */
1836
+ get _activeImplementorForTests(): ReadonlyMap<string, {
1837
+ gateId: string;
1838
+ handoffClaimed: boolean;
1839
+ }>;
1840
+ /** @internal — test introspection for the resume dedupe set. */
1841
+ get _resumedOffersForTests(): ReadonlySet<string>;
1842
+ /**
1843
+ * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — record a
1844
+ * BINDING user-requested change for a task. Called from `handleReviseFeedback`
1845
+ * when the `REVISE_FEEDBACK` payload carries the engine's synthetic
1846
+ * `seat_id === 'user'` finding (a Reject-with-notes, desktop OR mobile); its
1847
+ * `findings` text becomes a binding requirement re-stated in EVERY subsequent
1848
+ * revise round so no round can silently drop an earlier user requirement.
1849
+ *
1850
+ * Empty / whitespace-only notes are ignored (a defensive guard — the engine
1851
+ * already fails closed on empty notes upstream). Idempotent against an exact
1852
+ * consecutive repeat (a re-delivered packet must not duplicate the binding
1853
+ * requirement). Public so tests can drive the capture directly.
1854
+ */
1855
+ recordUserNote(taskId: string, note: string): void;
1856
+ /**
1857
+ * CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — build the
1858
+ * CUMULATIVE round-N+1 revise brief from the original task brief
1859
+ * (`activeBrief`, set at start_task), every BINDING user note recorded for
1860
+ * this task, and the current round's reviewer findings. The SAME brief is
1861
+ * used as the implementor stdin AND the execute-first `task_brief` rendered
1862
+ * into the reviewer prompts, so reviewers and the implementor share one
1863
+ * source of truth (the REAL requested task) and no round drops an earlier
1864
+ * user requirement.
1865
+ */
1866
+ private buildReviseBrief;
1867
+ /** @internal — test introspection. */
1868
+ get _runningSeatsForTests(): ReadonlySet<string>;
1869
+ /** @internal — test introspection. */
1870
+ get _submittedSeatsForTests(): ReadonlySet<string>;
1871
+ /** @internal — test seam to set the active task without start_task. */
1872
+ _setActiveTaskForTests(taskId: string, brief?: string): void;
1873
+ /** @internal — read a task's accumulated binding user notes. */
1874
+ _userNotesForTests(taskId: string): readonly string[];
1875
+ /** @internal — read the consumed REVISE_FEEDBACK dedup ids (Stage-2 r1 guard). */
1876
+ get _seenReviseFeedbackIdsForTests(): ReadonlySet<string>;
1877
+ /** @internal — read the live in-memory shadow handle for a task (or undefined). */
1878
+ _getShadowForTests(taskId: string): WorkspaceShadow | undefined;
1879
+ /** @internal — re-arm a (recovered) shadow handle so a double-fire hits the marker short-circuit. */
1880
+ _armShadowForTests(taskId: string, shadow: WorkspaceShadow): void;
1881
+ /**
1882
+ * @internal — drop a task's shadow-map entry WITHOUT touching its dir/marker.
1883
+ * Reproduces the transient UNMAPPED window inside `replaceShadowForRound0`:
1884
+ * the map entry is `delete`d before the (slow) stale teardown awaits and before
1885
+ * the replacement shadow B is created, so `discardShadow` can observe
1886
+ * `mapped === undefined`.
1887
+ */
1888
+ _unmapShadowForTests(taskId: string): void;
1889
+ /**
1890
+ * @internal — NEGATIVE-CONTROL seams (restart-race mutex, 2026-06-11 Codex r2):
1891
+ * invoke the UNLOCKED `*Locked` bodies directly, BYPASSING `runExclusiveShadowOp`.
1892
+ * Used ONLY by the mutex regression test to prove that without serialization a
1893
+ * matched discard overlapping a restart replacement destroys the #PR-4 marker.
1894
+ * Production code MUST always go through the locked public wrappers.
1895
+ */
1896
+ _discardShadowUnlockedForTests(taskId: string, expected?: WorkspaceShadow): Promise<void>;
1897
+ _replaceShadowForRound0UnlockedForTests(taskId: string): Promise<void>;
1898
+ _ensureShadowUnlockedForTests(taskId: string): Promise<WorkspaceShadow>;
1899
+ }
1900
+ export { reviewerAgentToLeAgent };