baxian 1.2.24 → 1.2.26

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 (199) hide show
  1. package/dist/agent/bootstrap-poller.d.ts.map +1 -1
  2. package/dist/agent/bootstrap-poller.js +0 -11
  3. package/dist/agent/bootstrap-poller.js.map +1 -1
  4. package/dist/agent/bootstrap.d.ts.map +1 -1
  5. package/dist/agent/bootstrap.js +0 -42
  6. package/dist/agent/bootstrap.js.map +1 -1
  7. package/dist/agent/diff-split.d.ts.map +1 -1
  8. package/dist/agent/diff-split.js +0 -4
  9. package/dist/agent/diff-split.js.map +1 -1
  10. package/dist/agent/image-input.d.ts.map +1 -1
  11. package/dist/agent/image-input.js +0 -3
  12. package/dist/agent/image-input.js.map +1 -1
  13. package/dist/agent/liveness.d.ts.map +1 -1
  14. package/dist/agent/liveness.js +0 -3
  15. package/dist/agent/liveness.js.map +1 -1
  16. package/dist/agent/manager.d.ts +0 -4
  17. package/dist/agent/manager.d.ts.map +1 -1
  18. package/dist/agent/manager.js +9 -951
  19. package/dist/agent/manager.js.map +1 -1
  20. package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
  21. package/dist/agent/pane-streamer-manager.js +0 -9
  22. package/dist/agent/pane-streamer-manager.js.map +1 -1
  23. package/dist/agent/pane-streamer.d.ts.map +1 -1
  24. package/dist/agent/pane-streamer.js +2 -27
  25. package/dist/agent/pane-streamer.js.map +1 -1
  26. package/dist/agent/pet-input.d.ts.map +1 -1
  27. package/dist/agent/pet-input.js +0 -3
  28. package/dist/agent/pet-input.js.map +1 -1
  29. package/dist/agent/phase-signal-watcher.d.ts +0 -6
  30. package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
  31. package/dist/agent/phase-signal-watcher.js +0 -41
  32. package/dist/agent/phase-signal-watcher.js.map +1 -1
  33. package/dist/agent/phase-signal.d.ts.map +1 -1
  34. package/dist/agent/phase-signal.js +1 -18
  35. package/dist/agent/phase-signal.js.map +1 -1
  36. package/dist/agent/preflight.d.ts.map +1 -1
  37. package/dist/agent/preflight.js +0 -12
  38. package/dist/agent/preflight.js.map +1 -1
  39. package/dist/agent/prompt.d.ts +0 -4
  40. package/dist/agent/prompt.d.ts.map +1 -1
  41. package/dist/agent/prompt.js +0 -66
  42. package/dist/agent/prompt.js.map +1 -1
  43. package/dist/agent/repo-store.d.ts.map +1 -1
  44. package/dist/agent/repo-store.js +0 -21
  45. package/dist/agent/repo-store.js.map +1 -1
  46. package/dist/agent/review-transport.d.ts +0 -1
  47. package/dist/agent/review-transport.d.ts.map +1 -1
  48. package/dist/agent/review-transport.js +0 -16
  49. package/dist/agent/review-transport.js.map +1 -1
  50. package/dist/agent/runner.d.ts +0 -3
  51. package/dist/agent/runner.d.ts.map +1 -1
  52. package/dist/agent/runner.js +0 -40
  53. package/dist/agent/runner.js.map +1 -1
  54. package/dist/agent/tmux-probe-poller.d.ts.map +1 -1
  55. package/dist/agent/tmux-probe-poller.js +1 -20
  56. package/dist/agent/tmux-probe-poller.js.map +1 -1
  57. package/dist/agent/tmux.d.ts +0 -1
  58. package/dist/agent/tmux.d.ts.map +1 -1
  59. package/dist/agent/tmux.js +6 -45
  60. package/dist/agent/tmux.js.map +1 -1
  61. package/dist/agent/worktree.d.ts.map +1 -1
  62. package/dist/agent/worktree.js +0 -9
  63. package/dist/agent/worktree.js.map +1 -1
  64. package/dist/api/agents.d.ts.map +1 -1
  65. package/dist/api/agents.js +0 -5
  66. package/dist/api/agents.js.map +1 -1
  67. package/dist/api/config.d.ts.map +1 -1
  68. package/dist/api/config.js +0 -16
  69. package/dist/api/config.js.map +1 -1
  70. package/dist/api/hosts.d.ts.map +1 -1
  71. package/dist/api/hosts.js +0 -37
  72. package/dist/api/hosts.js.map +1 -1
  73. package/dist/api/pets.d.ts.map +1 -1
  74. package/dist/api/pets.js +0 -5
  75. package/dist/api/pets.js.map +1 -1
  76. package/dist/api/probe.d.ts.map +1 -1
  77. package/dist/api/probe.js +0 -5
  78. package/dist/api/probe.js.map +1 -1
  79. package/dist/api/projects.d.ts.map +1 -1
  80. package/dist/api/projects.js +0 -40
  81. package/dist/api/projects.js.map +1 -1
  82. package/dist/api/tasks.d.ts.map +1 -1
  83. package/dist/api/tasks.js +0 -14
  84. package/dist/api/tasks.js.map +1 -1
  85. package/dist/app.d.ts +0 -3
  86. package/dist/app.d.ts.map +1 -1
  87. package/dist/app.js +0 -14
  88. package/dist/app.js.map +1 -1
  89. package/dist/cli.d.ts.map +1 -1
  90. package/dist/cli.js +0 -13
  91. package/dist/cli.js.map +1 -1
  92. package/dist/config/hot-reload.d.ts.map +1 -1
  93. package/dist/config/hot-reload.js +0 -3
  94. package/dist/config/hot-reload.js.map +1 -1
  95. package/dist/config/loader.d.ts +0 -27
  96. package/dist/config/loader.d.ts.map +1 -1
  97. package/dist/config/loader.js +0 -56
  98. package/dist/config/loader.js.map +1 -1
  99. package/dist/config/normalizer.js +0 -1
  100. package/dist/config/normalizer.js.map +1 -1
  101. package/dist/config/validator.d.ts.map +1 -1
  102. package/dist/config/validator.js +0 -16
  103. package/dist/config/validator.js.map +1 -1
  104. package/dist/event/broker.d.ts.map +1 -1
  105. package/dist/event/broker.js +0 -2
  106. package/dist/event/broker.js.map +1 -1
  107. package/dist/event/bus.d.ts.map +1 -1
  108. package/dist/event/bus.js +0 -2
  109. package/dist/event/bus.js.map +1 -1
  110. package/dist/event/handlers.d.ts.map +1 -1
  111. package/dist/event/handlers.js +2 -150
  112. package/dist/event/handlers.js.map +1 -1
  113. package/dist/event/publish.d.ts.map +1 -1
  114. package/dist/event/publish.js +0 -8
  115. package/dist/event/publish.js.map +1 -1
  116. package/dist/event/server-handlers.d.ts.map +1 -1
  117. package/dist/event/server-handlers.js +0 -80
  118. package/dist/event/server-handlers.js.map +1 -1
  119. package/dist/event/ws.d.ts.map +1 -1
  120. package/dist/event/ws.js +0 -5
  121. package/dist/event/ws.js.map +1 -1
  122. package/dist/github/mapper.d.ts.map +1 -1
  123. package/dist/github/mapper.js +0 -13
  124. package/dist/github/mapper.js.map +1 -1
  125. package/dist/github/poller.d.ts.map +1 -1
  126. package/dist/github/poller.js +0 -15
  127. package/dist/github/poller.js.map +1 -1
  128. package/dist/github/resolver.d.ts.map +1 -1
  129. package/dist/github/resolver.js +0 -1
  130. package/dist/github/resolver.js.map +1 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +0 -26
  133. package/dist/index.js.map +1 -1
  134. package/dist/lifecycle/restart-sentinel.d.ts.map +1 -1
  135. package/dist/lifecycle/restart-sentinel.js +0 -1
  136. package/dist/lifecycle/restart-sentinel.js.map +1 -1
  137. package/dist/lifecycle/restart.d.ts +0 -1
  138. package/dist/lifecycle/restart.d.ts.map +1 -1
  139. package/dist/lifecycle/restart.js +0 -3
  140. package/dist/lifecycle/restart.js.map +1 -1
  141. package/dist/shared/constants.d.ts.map +1 -1
  142. package/dist/shared/constants.js +0 -19
  143. package/dist/shared/constants.js.map +1 -1
  144. package/dist/shared/git-url.d.ts +0 -2
  145. package/dist/shared/git-url.d.ts.map +1 -1
  146. package/dist/shared/git-url.js +0 -21
  147. package/dist/shared/git-url.js.map +1 -1
  148. package/dist/shared/types.d.ts +0 -54
  149. package/dist/shared/types.d.ts.map +1 -1
  150. package/dist/shared/types.js.map +1 -1
  151. package/dist/skill/registry.d.ts.map +1 -1
  152. package/dist/skill/registry.js +3 -25
  153. package/dist/skill/registry.js.map +1 -1
  154. package/dist/state/agent-store.d.ts +0 -1
  155. package/dist/state/agent-store.d.ts.map +1 -1
  156. package/dist/state/agent-store.js +0 -5
  157. package/dist/state/agent-store.js.map +1 -1
  158. package/dist/state/error-record-store.d.ts.map +1 -1
  159. package/dist/state/error-record-store.js +1 -27
  160. package/dist/state/error-record-store.js.map +1 -1
  161. package/dist/state/lock.d.ts.map +1 -1
  162. package/dist/state/lock.js +1 -6
  163. package/dist/state/lock.js.map +1 -1
  164. package/dist/state/pet-store.d.ts.map +1 -1
  165. package/dist/state/pet-store.js +0 -10
  166. package/dist/state/pet-store.js.map +1 -1
  167. package/dist/state/post-approve-store.d.ts.map +1 -1
  168. package/dist/state/post-approve-store.js +0 -1
  169. package/dist/state/post-approve-store.js.map +1 -1
  170. package/dist/state/process-lock.d.ts +0 -2
  171. package/dist/state/process-lock.d.ts.map +1 -1
  172. package/dist/state/process-lock.js +0 -5
  173. package/dist/state/process-lock.js.map +1 -1
  174. package/dist/state/review-store.d.ts.map +1 -1
  175. package/dist/state/review-store.js +0 -3
  176. package/dist/state/review-store.js.map +1 -1
  177. package/dist/state/snapshot.d.ts.map +1 -1
  178. package/dist/state/snapshot.js +0 -19
  179. package/dist/state/snapshot.js.map +1 -1
  180. package/dist/state/task-store.d.ts.map +1 -1
  181. package/dist/state/task-store.js +0 -4
  182. package/dist/state/task-store.js.map +1 -1
  183. package/dist/terminal/attach.d.ts.map +1 -1
  184. package/dist/terminal/attach.js +0 -12
  185. package/dist/terminal/attach.js.map +1 -1
  186. package/dist/terminal/key-sanitizer.d.ts.map +1 -1
  187. package/dist/terminal/key-sanitizer.js +0 -2
  188. package/dist/terminal/key-sanitizer.js.map +1 -1
  189. package/dist/terminal/stream-ws.d.ts.map +1 -1
  190. package/dist/terminal/stream-ws.js +0 -5
  191. package/dist/terminal/stream-ws.js.map +1 -1
  192. package/dist/timing/backoff.d.ts.map +1 -1
  193. package/dist/timing/backoff.js.map +1 -1
  194. package/dist/web/assets/index-BFsxrk2C.js +7 -0
  195. package/dist/web/assets/{index-CHAb8Mrm.css → index-DvVOHfHm.css} +1 -1
  196. package/dist/web/assets/{router-D24GsdXZ.js → router-BgSDZqI0.js} +1 -1
  197. package/dist/web/index.html +3 -3
  198. package/package.json +2 -2
  199. package/dist/web/assets/index-B3nBJsTG.js +0 -4
@@ -6,8 +6,6 @@ const HEAD_SHA_RE = /^[0-9a-f]{40}$/i;
6
6
  function validHeadSha(value) {
7
7
  return typeof value === 'string' && HEAD_SHA_RE.test(value) ? value : undefined;
8
8
  }
9
- // Re-establish the develop-phase watcher after a handler-side rejection so the same
10
- // task can consume a corrected emit (same token) without a server restart.
11
9
  async function reArmDevelopWatcher(manager, task, agentId, opts = {}) {
12
10
  const kinds = task.phase === 'code' ? ['pr-created'] : ['spec-done', 'pr-created'];
13
11
  await manager.setupPhaseSignal(task.id, agentId, kinds, { skipSnapshot: opts.skipSnapshot ?? true });
@@ -49,9 +47,6 @@ async function resolveAuthoritativeHead(manager, task, opts = {}) {
49
47
  return { headSha: undefined, source: 'unknown', fetchError };
50
48
  }
51
49
  }
52
- // pr-fixed verification needs a LIVE head (no stale fallback). GitHub has brief
53
- // read-after-write lag + transient failures, so retry a few times, then throw so
54
- // the handler fails closed instead of mis-reading a no-op.
55
50
  async function fetchVerifiedHeadSha(manager, taskId) {
56
51
  let lastErr;
57
52
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -66,13 +61,7 @@ async function fetchVerifiedHeadSha(manager, taskId) {
66
61
  }
67
62
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
68
63
  }
69
- // Budget = initial APPROVE dispatch + N redispatches; sized to converge a ~10-finding PR.
70
64
  export const POST_APPROVE_REDISPATCH_CAP = 10;
71
- // Clock-skew tolerance for the verdict freshness gate. GitHub `submitted_at` is
72
- // second-granularity and its clock drifts from baxian's ms-precision
73
- // `reviewDispatchedAt`; a false-reject is costly (poller cursor marks the review
74
- // processed and won't retry → task strands), while real superseded passes are
75
- // seconds-to-minutes stale, so a few seconds of slack is safe.
76
65
  export const VERDICT_FRESHNESS_SKEW_MS = 5000;
77
66
  async function dispatchDevPostApproveCheck(bus, manager, task, approvedHeadSha, opts = {}) {
78
67
  if (!approvedHeadSha) {
@@ -163,8 +152,6 @@ async function handlePrMergeReady(bus, manager, event) {
163
152
  });
164
153
  }
165
154
  const verdictAgentId = event.data.verdictAgentId;
166
- // PhaseSignalWatcher unified `data.token`; old PostApproveSignalWatcher used
167
- // `data.signalToken` — reading the wrong field strands approved tasks.
168
155
  const signalToken = event.data.token;
169
156
  const completion = await manager.getPostApproveCompletion(taskNow.id);
170
157
  if (taskNow.status !== 'approved'
@@ -204,8 +191,6 @@ async function handlePrMergeReady(bus, manager, event) {
204
191
  await dispatchDevPostApproveCheck(bus, manager, freshTask, freshCompletion.approvedHeadSha, { redispatchCount: nextCount });
205
192
  return;
206
193
  }
207
- // merge:'auto' decides what confirm executes; persist the approved head so
208
- // confirm's merge guard catches a push inside the gate window.
209
194
  const readied = await manager.transitionTaskStatus(freshTask.id, 'merge-ready', { fromStatus: ['approved'] }, { latestHeadSha: freshCompletion.approvedHeadSha });
210
195
  if (readied) {
211
196
  await manager.clearPostApproveCompletionIfMatches(freshTask.id, signalToken);
@@ -240,7 +225,6 @@ async function handlePrFeedback(bus, manager, event) {
240
225
  });
241
226
  return;
242
227
  }
243
- // Don't Ctrl-C dev mid-pass on its own webhook echo — coalesce via pendingRedispatch.
244
228
  const devState = await manager.getAgentState(taskNow.agentId);
245
229
  if (devState?.taskId === taskNow.id) {
246
230
  if (!completion.pendingRedispatch) {
@@ -276,8 +260,6 @@ async function handlePrCodePush(bus, manager, event) {
276
260
  const eventPrUrl = event.data.prUrl;
277
261
  const eventKind = event.data.kind;
278
262
  const eventHeadSha = validHeadSha(event.data.headSha);
279
- // Only `push` freshens `latestHeadSha` — legacy events (kind=undefined) use headSha
280
- // for the review anchor only, not for the staleness fallback cache.
281
263
  const prPatch = {
282
264
  ...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
283
265
  ...(eventPrUrl !== undefined ? { prUrl: eventPrUrl } : {}),
@@ -292,13 +274,11 @@ async function handlePrCodePush(bus, manager, event) {
292
274
  `deferring catch-up`);
293
275
  return;
294
276
  }
295
- // Anchor at dispatch time — must NOT shift if a subsequent push lands mid-review.
296
277
  const anchorAtDispatch = eventHeadSha ?? validHeadSha(taskBeforeTransition.latestHeadSha);
297
278
  const result = await manager.transitionTaskStatus(taskId, 'review', { fromStatus: ['in_progress', 'fixing', 'review', 'approved', 'merge-ready'] }, {
298
279
  ...prPatch,
299
280
  ...(anchorAtDispatch ? { reviewHeadAnchorSha: anchorAtDispatch } : {}),
300
281
  reviewDispatchedAt: new Date().toISOString(),
301
- // Rotate token atomically so an old QA's late verdict is rejected by the gate.
302
282
  signalToken: createSignalToken(),
303
283
  });
304
284
  if (!result)
@@ -359,15 +339,11 @@ async function handlePrCodePush(bus, manager, event) {
359
339
  });
360
340
  return;
361
341
  }
362
- // Persist qaAgentId BEFORE setting up so a pane-fallback verdict's review.submitted
363
- // handler can read it for the release path.
364
342
  await manager.updateTask(transitioned.id, { qaAgentId: qa.id });
365
343
  const { armed } = await manager.rotateAndSetupPhaseSignal(transitioned.id, qa.id, ['pr-approved', 'pr-changes-requested']);
366
344
  if (!armed) {
367
345
  console.warn(`[EventHandler] pr.updated verdict watcher failed to arm for task=${transitioned.id} (${qaPhase}); rolling back recheck dispatch`);
368
346
  if (previousStatus === 'in_progress' || previousStatus === 'fixing') {
369
- // Full rollback: restore status+token+anchor and re-arm so the dev's already-emitted
370
- // signal isn't stranded by the token rotation.
371
347
  await manager.rollbackVerdictArmFailure(transitioned.id, {
372
348
  status: previousStatus,
373
349
  signalToken: taskBeforeTransition.signalToken,
@@ -376,8 +352,6 @@ async function handlePrCodePush(bus, manager, event) {
376
352
  });
377
353
  }
378
354
  else {
379
- // approved/review: don't restore status (approved with cleared completion is unsafe;
380
- // review was already current). Leave in review for operator follow-up.
381
355
  await manager.updateTask(transitioned.id, { qaAgentId: undefined });
382
356
  }
383
357
  await manager.releaseAgentForTask(qa.id, transitioned.id, 'idle').catch(err => {
@@ -406,7 +380,6 @@ async function handlePrCodePush(bus, manager, event) {
406
380
  await manager.failTaskForDispatchError(transitioned.id, qaPhase, qa.id, dispatchErr);
407
381
  }
408
382
  else if (dispatchErr instanceof EnsureSessionError && dispatchErr.partial.handled) {
409
- // handleDialogPendingFromRuntime already marked QA Held + fail task + release partners
410
383
  }
411
384
  else {
412
385
  await manager.updateTask(transitioned.id, { qaAgentId: undefined });
@@ -433,8 +406,6 @@ async function handlePrCodePush(bus, manager, event) {
433
406
  }
434
407
  return;
435
408
  }
436
- // Bump round for first review / approved re-review only; fixing/review→review recheck
437
- // of the in-flight pass must NOT bump.
438
409
  if (previousStatus === 'in_progress' || previousStatus === 'approved' || previousStatus === 'merge-ready') {
439
410
  await manager.bumpReviewRoundIfStillAt(transitioned.id, expectedRound);
440
411
  }
@@ -468,14 +439,10 @@ async function handleReviewApproval(bus, manager, task, reviewedHeadSha, current
468
439
  });
469
440
  return;
470
441
  }
471
- const result = await manager.transitionTaskStatus(task.id, 'approved', { fromStatus: ['review'] },
472
- // reviewRound 0 = first pass entered via catch-up (deferred bump). Count it now.
473
- task.reviewRound === 0 ? { ...prPatch, reviewRound: 1 } : prPatch);
442
+ const result = await manager.transitionTaskStatus(task.id, 'approved', { fromStatus: ['review'] }, task.reviewRound === 0 ? { ...prPatch, reviewRound: 1 } : prPatch);
474
443
  if (!result)
475
444
  return;
476
445
  const { task: transitioned } = result;
477
- // Verdict consumed → tear down the fallback verdict watcher (poller path leaves
478
- // it set-up-but-unfired; pane path already removed its entry — this is a no-op).
479
446
  manager.stopPhaseSignalWatcher(transitioned.id);
480
447
  if (transitioned.qaAgentId) {
481
448
  await manager.releaseAgentForTask(transitioned.qaAgentId, transitioned.id, 'idle', { allowAwaitingHuman: true })
@@ -507,8 +474,6 @@ async function handleReviewRequestChanges(bus, manager, task, reviewedHeadSha, c
507
474
  const reviewedRound = task.reviewRound === 0 ? 1 : task.reviewRound;
508
475
  const nextRound = reviewedRound + 1;
509
476
  if (task.status === 'approved' && nextRound <= manager.getConfig().review.rounds) {
510
- // Post-approve check may still be running — clearing completion blocks auto-merge;
511
- // fix dispatch deferred to avoid prompt collision with the in-flight check.
512
477
  const devState = await manager.getAgentState(task.agentId);
513
478
  const postApproveActive = await manager.getPostApproveCompletion(task.id);
514
479
  if (devState?.taskId === task.id && postApproveActive) {
@@ -646,13 +611,6 @@ export function registerEventHandlers(bus, manager) {
646
611
  return;
647
612
  if (await isServerModeTask(manager, event.taskId))
648
613
  return;
649
- // pane-signal pr.created carries data.prNumber (extracted from the agent's
650
- // [bx:pr-created:<num>:<token>] signal) but no prUrl / headSha. The
651
- // transition only needs prNumber to wire task→PR; prUrl is derivable from
652
- // repo+number; headSha lands via the next pr.updated push event.
653
- //
654
- // spec phase 由 server 评审链(server.spec.* handlers)驱动;poller 不应越过它派 QA review,
655
- // 否则 QA 在 spec-review 槽位上跑 pr-review 流程,gh pr review --approve 会误触 post-approve。
656
614
  {
657
615
  const taskNow = await manager.getTask(event.taskId);
658
616
  if (taskNow?.phase === 'spec') {
@@ -715,8 +673,6 @@ export function registerEventHandlers(bus, manager) {
715
673
  }
716
674
  }
717
675
  const createdHeadSha = validHeadSha(event.data.headSha) ?? paneVerifiedHeadSha;
718
- // Snapshot pre-transition fields so an arm failure can be rolled back atomically (restore
719
- // status/token/anchor instead of stranding the task in review with no QA — see armed gate below).
720
676
  const taskBeforeTransition = await manager.getTask(event.taskId);
721
677
  const result = await manager.transitionTaskStatus(event.taskId, 'review', { fromStatus: ['in_progress', 'fixing'] }, {
722
678
  ...(event.data.prNumber !== undefined ? { prNumber: event.data.prNumber } : {}),
@@ -746,8 +702,6 @@ export function registerEventHandlers(bus, manager) {
746
702
  console.log(`[EventHandler] pr.created reconciled branch for task ${event.taskId}: ` +
747
703
  `${taskBeforeTransition?.branch} → ${reconciledBranch}`);
748
704
  }
749
- // Round captured before any verdict can land (watcher armed only after this) — the
750
- // count-once token for bumpReviewRoundIfStillAt on the dispatch success path.
751
705
  const expectedRound = transitioned.reviewRound;
752
706
  const qa = manager.findQaPartner(event.agentId);
753
707
  if (!qa) {
@@ -759,7 +713,6 @@ export function registerEventHandlers(bus, manager) {
759
713
  }
760
714
  return;
761
715
  }
762
- // Acquire before startSession so the lock window covers the task binding write.
763
716
  const acquired = await manager.acquireAgentForTask(qa.id, transitioned.id, 'review');
764
717
  if (!acquired) {
765
718
  await emitIntervention(bus, transitioned.projectId, transitioned.agentId, transitioned.id, {
@@ -768,21 +721,9 @@ export function registerEventHandlers(bus, manager) {
768
721
  });
769
722
  return;
770
723
  }
771
- // Persist qaAgentId BEFORE setting up so a pane-fallback verdict's review.submitted
772
- // handler can read task.qaAgentId for the release path.
773
724
  await manager.updateTask(transitioned.id, { qaAgentId: qa.id });
774
- // Poller is authoritative for the verdict (native `gh pr review` state, with
775
- // commit_id + submitted_at). Set up the verdict watcher as a FALLBACK: when dev
776
- // and qa share a GitHub identity, `gh pr review` is rejected (422 — can't
777
- // review your own PR) and leaves no GitHub state to poll, so QA echoes the
778
- // verdict as a pane signal instead. A distinct-identity task never fires this
779
- // watcher; the review.submitted handler tears it down when the poller verdict lands.
780
725
  const { armed } = await manager.rotateAndSetupPhaseSignal(transitioned.id, qa.id, ['pr-approved', 'pr-changes-requested']);
781
726
  if (!armed) {
782
- // Verdict watcher didn't arm. For a same-identity task the poller can't supply a verdict
783
- // (422), so dispatching now would deadlock with no consumer. Atomically restore the
784
- // pre-transition state (status/token/anchor) + re-arm the develop watcher so the dev's
785
- // already-emitted pr-created is re-consumed (self-heal), then release QA + intervention.
786
727
  console.warn(`[EventHandler] pr.created verdict watcher failed to arm for task=${transitioned.id}; rolling back review dispatch`);
787
728
  await manager.rollbackVerdictArmFailure(transitioned.id, {
788
729
  status: previousStatus,
@@ -816,11 +757,8 @@ export function registerEventHandlers(bus, manager) {
816
757
  await manager.failTaskForDispatchError(transitioned.id, 'review', qa.id, dispatchErr);
817
758
  }
818
759
  else if (dispatchErr instanceof EnsureSessionError && dispatchErr.partial.handled) {
819
- // handleDialogPendingFromRuntime 已标 QA Held + fail task + release partners;这里
820
- // 再调 release 会因 boundTask terminal 让 shouldReleaseHeldBinding 放行 → 解锁仍卡 dialog 的 pane。
821
760
  }
822
761
  else {
823
- // Rollback the pre-set up qaAgentId so the task doesn't keep a binding to a QA we never started.
824
762
  await manager.updateTask(transitioned.id, { qaAgentId: undefined });
825
763
  await manager.releaseAgentForTask(qa.id, transitioned.id, 'idle')
826
764
  .catch(err => {
@@ -834,17 +772,11 @@ export function registerEventHandlers(bus, manager) {
834
772
  }
835
773
  return;
836
774
  }
837
- // QA review pass started → count it (1-based Round). Bump on the success path only,
838
- // so a no-QA / acquire / startSession failure above never inflates the round. The
839
- // count-once token no-ops if a same-identity verdict already counted this pass.
840
775
  if (previousStatus === 'in_progress') {
841
776
  await manager.bumpReviewRoundIfStillAt(transitioned.id, expectedRound);
842
777
  }
843
778
  const ok = await manager.markAgentWaiting(event.agentId, transitioned.id);
844
779
  if (!ok) {
845
- // QA review prompt 已粘进 pane 在跑;裸 release 让下一 review 派来同一 pane 会污染 outcome。
846
- // 标 awaiting_human 让 operator 决定如何收尾(取消该 task 走 cancelTask,或等 QA 跑完再 Resume)。
847
- // expectedTaskId: 防止迟到的 mark 撞 outcome 已被接受 + QA release+reassign 后的新 binding。
848
780
  await manager.markAwaitingHuman(qa.id, 'dev-wait-gate-failed-after-qa-started', `QA review for task ${transitioned.id} started but dev wait-gate failed; QA prompt may still be running, needs operator decision.`, { expectedTaskId: transitioned.id }).catch(err => {
849
781
  console.error(`[EventHandler] pr.created markAwaitingHuman(QA=${qa.id}) after dev-wait-gate-fail:`, err);
850
782
  });
@@ -853,13 +785,9 @@ export function registerEventHandlers(bus, manager) {
853
785
  bus.on('pr.updated', async (event) => {
854
786
  if (!event.taskId || !event.agentId)
855
787
  return;
856
- // Server tasks review via the exchange protocol — a poller-observed sync on
857
- // the published PR must not drag them into legacy QA review.
858
- // pr.merged stays open: external merges of a ready PR finish through it.
859
788
  if (await isServerModeTask(manager, event.taskId))
860
789
  return;
861
790
  const eventKind = event.data.kind;
862
- // spec phase 由 server 评审链驱动;pr-merge-ready 是 dev 内部状态推进,不受限。
863
791
  if (eventKind !== 'pr-merge-ready') {
864
792
  const taskNow = await manager.getTask(event.taskId);
865
793
  if (taskNow?.phase === 'spec') {
@@ -882,18 +810,11 @@ export function registerEventHandlers(bus, manager) {
882
810
  ...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
883
811
  ...(eventPrUrl !== undefined ? { prUrl: eventPrUrl } : {}),
884
812
  };
885
- const result = await manager.transitionTaskStatus(event.taskId, 'merged',
886
- // max_rounds included so manual mark-complete (and an externally-merged
887
- // max_rounds PR the poller detects) transitions to merged + runs cleanup.
888
- // ready included for server-mode afterDone:'pr' tasks whose managed PR is
889
- // merged directly on GitHub instead of via baxian's Confirm.
890
- { fromStatus: ['in_progress', 'fixing', 'review', 'approved', 'merge-ready', 'ready', 'max_rounds'] }, prPatch);
813
+ const result = await manager.transitionTaskStatus(event.taskId, 'merged', { fromStatus: ['in_progress', 'fixing', 'review', 'approved', 'merge-ready', 'ready', 'max_rounds'] }, prPatch);
891
814
  if (!result)
892
815
  return;
893
816
  const { task: transitioned } = result;
894
817
  if (transitioned.qaAgentId) {
895
- // Keep QA bound (non-dispatchable) until its branch cleanup + /clear finish, then release.
896
- // dispatchPostMergeCleanup owns the worktree removal → branch delete → /clear → release.
897
818
  if (transitioned.prNumber && transitioned.branch) {
898
819
  await manager.dispatchPostMergeCleanup(transitioned.qaAgentId, {
899
820
  taskId: transitioned.id,
@@ -901,7 +822,6 @@ export function registerEventHandlers(bus, manager) {
901
822
  }).catch(err => console.warn(`[EventHandler] pr.merged dispatchPostMergeCleanup(QA=${transitioned.qaAgentId}) failed:`, err));
902
823
  }
903
824
  else {
904
- // Nothing to clean up or compact — release QA immediately so it frees up.
905
825
  try {
906
826
  await manager.releaseAgentForTask(transitioned.qaAgentId, transitioned.id, 'idle');
907
827
  }
@@ -926,11 +846,6 @@ export function registerEventHandlers(bus, manager) {
926
846
  let task = await manager.getTask(event.taskId);
927
847
  if (!task)
928
848
  return;
929
- // Terminal-task escape: release QA on manual reviews of merged/cancelled/failed/max_rounds
930
- // tasks; transitions below return null on terminal so QA would otherwise stay glued.
931
- // 注:spec phase 任务也可能终态(spec-review max_rounds 只改 status 不清 phase)。terminal
932
- // escape 必须在 spec gate 之前,否则 spec terminal 状态的残留 QA 绑定无法靠这条 manual
933
- // review 兜底释放。
934
849
  {
935
850
  const terminalQaId = task.qaAgentId;
936
851
  if (terminalQaId && TASK_TERMINAL_STATUS_SET.has(task.status)) {
@@ -951,26 +866,14 @@ export function registerEventHandlers(bus, manager) {
951
866
  }
952
867
  }
953
868
  }
954
- // spec phase(非 terminal)由 server 评审链(server.spec.review.submitted)驱动 verdict;
955
- // GitHub PR review 不应改 task.status,早退避免误推 approved + 派 dev post-approve。
956
- // terminal 状态已在前面兜底释放,此处只屏蔽 active spec 路径。
957
869
  if (task.phase === 'spec') {
958
870
  console.warn(`[EventHandler] review.submitted (action=${action}) ignored for task ${task.id}: task in spec phase`);
959
871
  return;
960
872
  }
961
- // Freshness gate: reject a verdict whose GitHub submit time precedes the
962
- // current review pass's dispatch. Such a verdict belongs to a SUPERSEDED
963
- // pass — dev force-pushed mid-review, the server re-dispatched a fresh pass,
964
- // and the prior pass's late verdict is now landing. GitHub attributes a
965
- // review to its submit-time head, so commit_id can't catch this; only baxian
966
- // knows when it dispatched the active pass. Skipped when either timestamp is
967
- // absent (can't prove staleness → allow).
968
873
  const verdictSubmittedAt = typeof event.data.submittedAt === 'string' ? event.data.submittedAt : undefined;
969
874
  if (verdictSubmittedAt && task.reviewDispatchedAt) {
970
875
  const submittedMs = Date.parse(verdictSubmittedAt);
971
876
  const dispatchedMs = Date.parse(task.reviewDispatchedAt);
972
- // Reject only when clearly older than dispatch (beyond the skew budget) — a
973
- // same-second fresh verdict must not be killed by clock granularity/drift.
974
877
  if (Number.isFinite(submittedMs) && Number.isFinite(dispatchedMs)
975
878
  && submittedMs < dispatchedMs - VERDICT_FRESHNESS_SKEW_MS) {
976
879
  await emitIntervention(bus, task.projectId, task.agentId, task.id, {
@@ -982,18 +885,6 @@ export function registerEventHandlers(bus, manager) {
982
885
  return;
983
886
  }
984
887
  }
985
- // Per-pass identity gate. signalToken rotates on every (re)dispatch, so a verdict
986
- // whose pass token != the task's current token belongs to a SUPERSEDED pass. The
987
- // token arrives two ways and BOTH must be checked:
988
- // - poller verdict: `reviewPassToken` (QA stamps it into the gh review body,
989
- // <!-- baxian:pr-approved:TOKEN -->; mapper/poller extract it).
990
- // - pane fallback verdict: `data.token` (the signal the watcher matched).
991
- // Checking only the poller stamp leaves a hole: an old QA's late
992
- // `[bx:pr-approved:<old-token>]`, fired by a not-yet-replaced watcher after a
993
- // redispatch whose new-QA dispatch failed, would bypass the rotation. That pane
994
- // case is worse — it has no headSha, so the handler would otherwise bind it to the
995
- // new anchor head the old QA never reviewed. Verify-if-present: a human review (no
996
- // stamp / no token) is not rejected.
997
888
  const verdictPassToken = (typeof event.data.reviewPassToken === 'string' ? event.data.reviewPassToken : undefined)
998
889
  ?? (typeof event.data.token === 'string' ? event.data.token : undefined);
999
890
  if (verdictPassToken && task.signalToken && verdictPassToken !== task.signalToken) {
@@ -1012,23 +903,11 @@ export function registerEventHandlers(bus, manager) {
1012
903
  ...(eventPrNumber !== undefined && eventPrNumber !== task.prNumber ? { prNumber: eventPrNumber } : {}),
1013
904
  ...(eventPrUrl !== undefined && eventPrUrl !== task.prUrl ? { prUrl: eventPrUrl } : {}),
1014
905
  };
1015
- // Poller verdicts carry `headSha` (the review's GitHub commit_id) and
1016
- // `currentHeadSha` (the PR's live head). A pane-fallback verdict (same-identity
1017
- // 422 case) carries neither — the agent doesn't observe SHAs — so it applies to
1018
- // the head pinned at dispatch into `task.reviewHeadAnchorSha`. Using
1019
- // `latestHeadSha` would let a mid-review push silently re-anchor the approval to
1020
- // a commit QA never saw; the anchor is immutable per review round.
1021
906
  const isPaneSignal = event.data.source === 'pane-signal';
1022
907
  const reviewedHeadSha = validHeadSha(event.data.headSha)
1023
908
  ?? (isPaneSignal ? validHeadSha(task.reviewHeadAnchorSha) : undefined);
1024
909
  const currentHeadSha = validHeadSha(event.data.currentHeadSha)
1025
910
  ?? (isPaneSignal ? validHeadSha(task.reviewHeadAnchorSha) : undefined);
1026
- // A late verdict can arrive while the task is still in_progress/fixing (the
1027
- // pr.created/pr.updated that should have moved it to review was missed). Catch up
1028
- // to review WITHOUT bumping here: the first-review count is derived from persisted
1029
- // state below (reviewRound === 0 ⇒ first pass) and applied only when the verdict is
1030
- // accepted. So a stale verdict that returns early leaves reviewRound 0, and the
1031
- // NEXT valid verdict — which no longer re-enters this catch-up — still counts it.
1032
911
  if ((task.status === 'in_progress' || task.status === 'fixing') && eventPrNumber !== undefined) {
1033
912
  const catchup = await manager.transitionTaskStatus(event.taskId, 'review', { fromStatus: ['in_progress', 'fixing'] }, prPatch);
1034
913
  if (catchup) {
@@ -1053,10 +932,6 @@ export function registerEventHandlers(bus, manager) {
1053
932
  return handleReviewRequestChanges(bus, manager, task, reviewedHeadSha, currentHeadSha, prPatch);
1054
933
  }
1055
934
  });
1056
- // Dev emitted pr-fixed: it claims the fixing round is done. Verify on GitHub
1057
- // before advancing (option C) — a new commit OR a reply to the findings means
1058
- // real work; neither means a no-op claim. Every GitHub read
1059
- // fails closed: a verification we can't complete must NOT be read as "no-op".
1060
935
  bus.on('pr.fix.submitted', async (event) => {
1061
936
  if (!event.taskId || !event.agentId)
1062
937
  return;
@@ -1064,17 +939,10 @@ export function registerEventHandlers(bus, manager) {
1064
939
  if (!task || task.reviewMode === 'server' || task.status !== 'fixing' || task.phase === 'spec')
1065
940
  return;
1066
941
  const { projectId, agentId } = task;
1067
- // The watcher is one-shot and already consumed this pr-fixed. For paths that
1068
- // leave the task in `fixing`, re-establish (skipSnapshot so we don't re-fire on the
1069
- // same scrollback signal) so the dev can retry pr-fixed after addressing the
1070
- // note — otherwise a corrected retry has no watcher and the task strands.
1071
942
  const stayFixing = async (data) => {
1072
943
  await emitIntervention(bus, projectId, agentId, task.id, data);
1073
944
  await manager.setupPhaseSignal(task.id, agentId, 'pr-fixed', { skipSnapshot: true });
1074
945
  };
1075
- // Anti-stale, fail-closed: a pr-fixed with a missing OR mismatched token
1076
- // (superseded pass) must not advance. No re-establish — a superseded pass means a
1077
- // fresh dispatch already set up its own watcher.
1078
946
  const token = typeof event.data.token === 'string' ? event.data.token : undefined;
1079
947
  if (!token || !task.signalToken || token !== task.signalToken) {
1080
948
  await emitIntervention(bus, projectId, agentId, task.id, {
@@ -1084,9 +952,6 @@ export function registerEventHandlers(bus, manager) {
1084
952
  });
1085
953
  return;
1086
954
  }
1087
- // pr-fixed's premise is "read the real GitHub state", so the head MUST come
1088
- // from a live fetch (resolveAuthoritativeHead's stale fallback could read the
1089
- // pre-push anchor). Fail closed on fetch failure rather than guessing no-op.
1090
955
  let headSha;
1091
956
  try {
1092
957
  headSha = await fetchVerifiedHeadSha(manager, task.id);
@@ -1098,19 +963,12 @@ export function registerEventHandlers(bus, manager) {
1098
963
  });
1099
964
  return;
1100
965
  }
1101
- // Without a review anchor we can't tell a pushed fix from a no-op — fail closed
1102
- // rather than mis-read the missing anchor as "no new commit".
1103
966
  if (!task.reviewHeadAnchorSha) {
1104
967
  await stayFixing({ phase: 'fix-verify-no-anchor', headSha });
1105
968
  return;
1106
969
  }
1107
- // New commit → the poller's pr.updated(push) is the authoritative advance;
1108
- // re-dispatching here too would double-trigger the QA recheck. Defer to it.
1109
970
  if (headSha !== task.reviewHeadAnchorSha)
1110
971
  return;
1111
- // No new commit: did the dev do anything since THIS fix round started? Use
1112
- // fixDispatchedAt (not reviewDispatchedAt) so QA/human comments left during the
1113
- // prior review don't count as dev activity. Fail closed on a fetch error.
1114
972
  const since = task.fixDispatchedAt ?? task.reviewDispatchedAt;
1115
973
  let hasReplies;
1116
974
  try {
@@ -1130,8 +988,6 @@ export function registerEventHandlers(bus, manager) {
1130
988
  });
1131
989
  return;
1132
990
  }
1133
- // Replies but no new commit (all "Won't fix"): recheck the same head + the
1134
- // replies. Reuse the push path for identical round/anchor/token semantics.
1135
991
  await bus.emit({
1136
992
  id: '',
1137
993
  type: 'pr.updated',
@@ -1147,10 +1003,6 @@ export function registerEventHandlers(bus, manager) {
1147
1003
  source: 'pr-fixed',
1148
1004
  },
1149
1005
  });
1150
- // If that synthetic advance failed downstream — the push handler rolls a failed
1151
- // QA dispatch back from review to fixing — the one-shot pr-fixed watcher is
1152
- // already consumed and (unlike a real push) there is no poller event to retry
1153
- // it. Surface it rather than leaving the task silently stuck in fixing.
1154
1006
  const afterAdvance = await manager.getTask(task.id);
1155
1007
  if (afterAdvance?.status === 'fixing') {
1156
1008
  await emitIntervention(bus, projectId, agentId, task.id, {