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.
- package/dist/agent/bootstrap-poller.d.ts.map +1 -1
- package/dist/agent/bootstrap-poller.js +0 -11
- package/dist/agent/bootstrap-poller.js.map +1 -1
- package/dist/agent/bootstrap.d.ts.map +1 -1
- package/dist/agent/bootstrap.js +0 -42
- package/dist/agent/bootstrap.js.map +1 -1
- package/dist/agent/diff-split.d.ts.map +1 -1
- package/dist/agent/diff-split.js +0 -4
- package/dist/agent/diff-split.js.map +1 -1
- package/dist/agent/image-input.d.ts.map +1 -1
- package/dist/agent/image-input.js +0 -3
- package/dist/agent/image-input.js.map +1 -1
- package/dist/agent/liveness.d.ts.map +1 -1
- package/dist/agent/liveness.js +0 -3
- package/dist/agent/liveness.js.map +1 -1
- package/dist/agent/manager.d.ts +0 -4
- package/dist/agent/manager.d.ts.map +1 -1
- package/dist/agent/manager.js +9 -951
- package/dist/agent/manager.js.map +1 -1
- package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
- package/dist/agent/pane-streamer-manager.js +0 -9
- package/dist/agent/pane-streamer-manager.js.map +1 -1
- package/dist/agent/pane-streamer.d.ts.map +1 -1
- package/dist/agent/pane-streamer.js +2 -27
- package/dist/agent/pane-streamer.js.map +1 -1
- package/dist/agent/pet-input.d.ts.map +1 -1
- package/dist/agent/pet-input.js +0 -3
- package/dist/agent/pet-input.js.map +1 -1
- package/dist/agent/phase-signal-watcher.d.ts +0 -6
- package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
- package/dist/agent/phase-signal-watcher.js +0 -41
- package/dist/agent/phase-signal-watcher.js.map +1 -1
- package/dist/agent/phase-signal.d.ts.map +1 -1
- package/dist/agent/phase-signal.js +1 -18
- package/dist/agent/phase-signal.js.map +1 -1
- package/dist/agent/preflight.d.ts.map +1 -1
- package/dist/agent/preflight.js +0 -12
- package/dist/agent/preflight.js.map +1 -1
- package/dist/agent/prompt.d.ts +0 -4
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +0 -66
- package/dist/agent/prompt.js.map +1 -1
- package/dist/agent/repo-store.d.ts.map +1 -1
- package/dist/agent/repo-store.js +0 -21
- package/dist/agent/repo-store.js.map +1 -1
- package/dist/agent/review-transport.d.ts +0 -1
- package/dist/agent/review-transport.d.ts.map +1 -1
- package/dist/agent/review-transport.js +0 -16
- package/dist/agent/review-transport.js.map +1 -1
- package/dist/agent/runner.d.ts +0 -3
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +0 -40
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tmux-probe-poller.d.ts.map +1 -1
- package/dist/agent/tmux-probe-poller.js +1 -20
- package/dist/agent/tmux-probe-poller.js.map +1 -1
- package/dist/agent/tmux.d.ts +0 -1
- package/dist/agent/tmux.d.ts.map +1 -1
- package/dist/agent/tmux.js +6 -45
- package/dist/agent/tmux.js.map +1 -1
- package/dist/agent/worktree.d.ts.map +1 -1
- package/dist/agent/worktree.js +0 -9
- package/dist/agent/worktree.js.map +1 -1
- package/dist/api/agents.d.ts.map +1 -1
- package/dist/api/agents.js +0 -5
- package/dist/api/agents.js.map +1 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +0 -16
- package/dist/api/config.js.map +1 -1
- package/dist/api/hosts.d.ts.map +1 -1
- package/dist/api/hosts.js +0 -37
- package/dist/api/hosts.js.map +1 -1
- package/dist/api/pets.d.ts.map +1 -1
- package/dist/api/pets.js +0 -5
- package/dist/api/pets.js.map +1 -1
- package/dist/api/probe.d.ts.map +1 -1
- package/dist/api/probe.js +0 -5
- package/dist/api/probe.js.map +1 -1
- package/dist/api/projects.d.ts.map +1 -1
- package/dist/api/projects.js +0 -40
- package/dist/api/projects.js.map +1 -1
- package/dist/api/tasks.d.ts.map +1 -1
- package/dist/api/tasks.js +0 -14
- package/dist/api/tasks.js.map +1 -1
- package/dist/app.d.ts +0 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +0 -14
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +0 -13
- package/dist/cli.js.map +1 -1
- package/dist/config/hot-reload.d.ts.map +1 -1
- package/dist/config/hot-reload.js +0 -3
- package/dist/config/hot-reload.js.map +1 -1
- package/dist/config/loader.d.ts +0 -27
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +0 -56
- package/dist/config/loader.js.map +1 -1
- package/dist/config/normalizer.js +0 -1
- package/dist/config/normalizer.js.map +1 -1
- package/dist/config/validator.d.ts.map +1 -1
- package/dist/config/validator.js +0 -16
- package/dist/config/validator.js.map +1 -1
- package/dist/event/broker.d.ts.map +1 -1
- package/dist/event/broker.js +0 -2
- package/dist/event/broker.js.map +1 -1
- package/dist/event/bus.d.ts.map +1 -1
- package/dist/event/bus.js +0 -2
- package/dist/event/bus.js.map +1 -1
- package/dist/event/handlers.d.ts.map +1 -1
- package/dist/event/handlers.js +2 -150
- package/dist/event/handlers.js.map +1 -1
- package/dist/event/publish.d.ts.map +1 -1
- package/dist/event/publish.js +0 -8
- package/dist/event/publish.js.map +1 -1
- package/dist/event/server-handlers.d.ts.map +1 -1
- package/dist/event/server-handlers.js +0 -80
- package/dist/event/server-handlers.js.map +1 -1
- package/dist/event/ws.d.ts.map +1 -1
- package/dist/event/ws.js +0 -5
- package/dist/event/ws.js.map +1 -1
- package/dist/github/mapper.d.ts.map +1 -1
- package/dist/github/mapper.js +0 -13
- package/dist/github/mapper.js.map +1 -1
- package/dist/github/poller.d.ts.map +1 -1
- package/dist/github/poller.js +0 -15
- package/dist/github/poller.js.map +1 -1
- package/dist/github/resolver.d.ts.map +1 -1
- package/dist/github/resolver.js +0 -1
- package/dist/github/resolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/restart-sentinel.d.ts.map +1 -1
- package/dist/lifecycle/restart-sentinel.js +0 -1
- package/dist/lifecycle/restart-sentinel.js.map +1 -1
- package/dist/lifecycle/restart.d.ts +0 -1
- package/dist/lifecycle/restart.d.ts.map +1 -1
- package/dist/lifecycle/restart.js +0 -3
- package/dist/lifecycle/restart.js.map +1 -1
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +0 -19
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/git-url.d.ts +0 -2
- package/dist/shared/git-url.d.ts.map +1 -1
- package/dist/shared/git-url.js +0 -21
- package/dist/shared/git-url.js.map +1 -1
- package/dist/shared/types.d.ts +0 -54
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/skill/registry.d.ts.map +1 -1
- package/dist/skill/registry.js +3 -25
- package/dist/skill/registry.js.map +1 -1
- package/dist/state/agent-store.d.ts +0 -1
- package/dist/state/agent-store.d.ts.map +1 -1
- package/dist/state/agent-store.js +0 -5
- package/dist/state/agent-store.js.map +1 -1
- package/dist/state/error-record-store.d.ts.map +1 -1
- package/dist/state/error-record-store.js +1 -27
- package/dist/state/error-record-store.js.map +1 -1
- package/dist/state/lock.d.ts.map +1 -1
- package/dist/state/lock.js +1 -6
- package/dist/state/lock.js.map +1 -1
- package/dist/state/pet-store.d.ts.map +1 -1
- package/dist/state/pet-store.js +0 -10
- package/dist/state/pet-store.js.map +1 -1
- package/dist/state/post-approve-store.d.ts.map +1 -1
- package/dist/state/post-approve-store.js +0 -1
- package/dist/state/post-approve-store.js.map +1 -1
- package/dist/state/process-lock.d.ts +0 -2
- package/dist/state/process-lock.d.ts.map +1 -1
- package/dist/state/process-lock.js +0 -5
- package/dist/state/process-lock.js.map +1 -1
- package/dist/state/review-store.d.ts.map +1 -1
- package/dist/state/review-store.js +0 -3
- package/dist/state/review-store.js.map +1 -1
- package/dist/state/snapshot.d.ts.map +1 -1
- package/dist/state/snapshot.js +0 -19
- package/dist/state/snapshot.js.map +1 -1
- package/dist/state/task-store.d.ts.map +1 -1
- package/dist/state/task-store.js +0 -4
- package/dist/state/task-store.js.map +1 -1
- package/dist/terminal/attach.d.ts.map +1 -1
- package/dist/terminal/attach.js +0 -12
- package/dist/terminal/attach.js.map +1 -1
- package/dist/terminal/key-sanitizer.d.ts.map +1 -1
- package/dist/terminal/key-sanitizer.js +0 -2
- package/dist/terminal/key-sanitizer.js.map +1 -1
- package/dist/terminal/stream-ws.d.ts.map +1 -1
- package/dist/terminal/stream-ws.js +0 -5
- package/dist/terminal/stream-ws.js.map +1 -1
- package/dist/timing/backoff.d.ts.map +1 -1
- package/dist/timing/backoff.js.map +1 -1
- package/dist/web/assets/index-BFsxrk2C.js +7 -0
- package/dist/web/assets/{index-CHAb8Mrm.css → index-DvVOHfHm.css} +1 -1
- package/dist/web/assets/{router-D24GsdXZ.js → router-BgSDZqI0.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +2 -2
- package/dist/web/assets/index-B3nBJsTG.js +0 -4
package/dist/event/handlers.js
CHANGED
|
@@ -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, {
|