baxian 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/branch.d.ts +0 -1
- package/dist/agent/branch.d.ts.map +1 -1
- package/dist/agent/branch.js.map +1 -1
- package/dist/agent/dispatch-reconciler.d.ts.map +1 -1
- package/dist/agent/dispatch-reconciler.js +10 -5
- package/dist/agent/dispatch-reconciler.js.map +1 -1
- package/dist/agent/manager.d.ts +42 -13
- package/dist/agent/manager.d.ts.map +1 -1
- package/dist/agent/manager.js +568 -165
- package/dist/agent/manager.js.map +1 -1
- package/dist/agent/net-exec.d.ts +3 -0
- package/dist/agent/net-exec.d.ts.map +1 -1
- package/dist/agent/net-exec.js +6 -0
- package/dist/agent/net-exec.js.map +1 -1
- package/dist/agent/pane-streamer-manager.d.ts +6 -6
- package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
- package/dist/agent/pane-streamer-manager.js.map +1 -1
- package/dist/agent/pane-streamer.d.ts +2 -2
- package/dist/agent/pane-streamer.d.ts.map +1 -1
- package/dist/agent/pane-streamer.js +46 -36
- package/dist/agent/pane-streamer.js.map +1 -1
- package/dist/agent/phase-signal-watcher.d.ts +15 -1
- package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
- package/dist/agent/phase-signal-watcher.js +113 -6
- package/dist/agent/phase-signal-watcher.js.map +1 -1
- package/dist/agent/phase-signal.d.ts +7 -0
- package/dist/agent/phase-signal.d.ts.map +1 -1
- package/dist/agent/phase-signal.js +14 -5
- package/dist/agent/phase-signal.js.map +1 -1
- package/dist/agent/prompt.d.ts +0 -5
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +5 -18
- package/dist/agent/prompt.js.map +1 -1
- package/dist/agent/repo-store.d.ts +4 -1
- package/dist/agent/repo-store.d.ts.map +1 -1
- package/dist/agent/repo-store.js +33 -7
- package/dist/agent/repo-store.js.map +1 -1
- package/dist/agent/root-agent-runtime.d.ts +80 -0
- package/dist/agent/root-agent-runtime.d.ts.map +1 -0
- package/dist/agent/root-agent-runtime.js +635 -0
- package/dist/agent/root-agent-runtime.js.map +1 -0
- package/dist/agent/root-recovery-coordinator.d.ts +125 -0
- package/dist/agent/root-recovery-coordinator.d.ts.map +1 -0
- package/dist/agent/root-recovery-coordinator.js +1579 -0
- package/dist/agent/root-recovery-coordinator.js.map +1 -0
- package/dist/agent/runner.d.ts +1 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +103 -7
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tmux-probe-poller.d.ts +1 -0
- package/dist/agent/tmux-probe-poller.d.ts.map +1 -1
- package/dist/agent/tmux-probe-poller.js +16 -10
- package/dist/agent/tmux-probe-poller.js.map +1 -1
- package/dist/agent/tmux.d.ts +3 -0
- package/dist/agent/tmux.d.ts.map +1 -1
- package/dist/agent/tmux.js +11 -5
- package/dist/agent/tmux.js.map +1 -1
- package/dist/api/agents.d.ts.map +1 -1
- package/dist/api/agents.js +42 -1
- package/dist/api/agents.js.map +1 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +77 -7
- package/dist/api/config.js.map +1 -1
- package/dist/api/hosts.d.ts.map +1 -1
- package/dist/api/hosts.js +68 -13
- package/dist/api/hosts.js.map +1 -1
- package/dist/api/projects.d.ts.map +1 -1
- package/dist/api/projects.js +23 -7
- package/dist/api/projects.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +6 -0
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +49 -7
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +22 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/normalizer.d.ts.map +1 -1
- package/dist/config/normalizer.js +18 -0
- package/dist/config/normalizer.js.map +1 -1
- package/dist/config/validator.d.ts.map +1 -1
- package/dist/config/validator.js +194 -23
- package/dist/config/validator.js.map +1 -1
- package/dist/event/handlers.d.ts.map +1 -1
- package/dist/event/handlers.js +0 -3
- package/dist/event/handlers.js.map +1 -1
- package/dist/event/server-handlers.d.ts.map +1 -1
- package/dist/event/server-handlers.js +201 -128
- package/dist/event/server-handlers.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -1
- package/dist/platform/platform-poller.d.ts.map +1 -1
- package/dist/platform/platform-poller.js +1 -5
- package/dist/platform/platform-poller.js.map +1 -1
- package/dist/shared/constants.d.ts +7 -1
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +5 -0
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/types.d.ts +25 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +18 -0
- package/dist/shared/types.js.map +1 -1
- package/dist/skills/baxian-pr-feedback/SKILL.md +2 -4
- package/dist/skills/baxian-pr-recheck/SKILL.md +2 -0
- package/dist/skills/baxian-pr-review/SKILL.md +2 -0
- package/dist/skills/baxian-research/SKILL.md +1 -1
- package/dist/skills/baxian-server-feedback/SKILL.md +1 -1
- package/dist/skills/baxian-server-review/SKILL.md +2 -2
- package/dist/skills/baxian-signals/SKILL.md +6 -0
- package/dist/state/init.d.ts.map +1 -1
- package/dist/state/init.js +1 -0
- package/dist/state/init.js.map +1 -1
- package/dist/state/lock.d.ts +6 -0
- package/dist/state/lock.d.ts.map +1 -1
- package/dist/state/lock.js +8 -0
- package/dist/state/lock.js.map +1 -1
- package/dist/state/root-recovery-store.d.ts +107 -0
- package/dist/state/root-recovery-store.d.ts.map +1 -0
- package/dist/state/root-recovery-store.js +400 -0
- package/dist/state/root-recovery-store.js.map +1 -0
- package/dist/state/task-store.d.ts.map +1 -1
- package/dist/state/task-store.js +7 -3
- package/dist/state/task-store.js.map +1 -1
- package/dist/terminal/attach.d.ts +3 -3
- package/dist/terminal/attach.d.ts.map +1 -1
- package/dist/terminal/attach.js.map +1 -1
- package/dist/terminal/stream-ws.d.ts.map +1 -1
- package/dist/terminal/stream-ws.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { isSpecStagePhase } from '../shared/index.js';
|
|
1
|
+
import { isSpecStagePhase, taskGenerationGuard } from '../shared/index.js';
|
|
2
2
|
import { ReviewExchangeError, } from '../agent/review-transport.js';
|
|
3
|
+
import { settlePermitOf } from '../agent/phase-signal-watcher.js';
|
|
3
4
|
import { computeBackoffMs } from '../timing/backoff.js';
|
|
4
5
|
import { reviewFindingsDigest, serverResponseFailureSignature, } from '../state/review-store.js';
|
|
5
6
|
const LEGACY_DIFF_LARGE_THRESHOLD = 2000;
|
|
@@ -150,7 +151,7 @@ async function gate(bus, manager, event, expect) {
|
|
|
150
151
|
});
|
|
151
152
|
return null;
|
|
152
153
|
}
|
|
153
|
-
return { task };
|
|
154
|
+
return { task, expectedTask: taskGenerationGuard(task) };
|
|
154
155
|
}
|
|
155
156
|
export function registerServerEventHandlers(bus, manager) {
|
|
156
157
|
const configured = manager.getReviewStore();
|
|
@@ -179,11 +180,15 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
179
180
|
.catch(err => console.error(`[ServerHandler] max_rounds clear agentId(${task.id}) failed:`, err));
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
|
-
async function putVerdictRound(task, agentId, kind, data) {
|
|
183
|
+
async function putVerdictRound(task, agentId, kind, data, opts = {}) {
|
|
183
184
|
let lastError;
|
|
184
185
|
for (let attempt = 1; attempt <= SERVER_HANDLER_ATTEMPTS; attempt++) {
|
|
185
186
|
try {
|
|
186
|
-
|
|
187
|
+
const stored = opts.expectedTask
|
|
188
|
+
? await manager.putReviewRoundIfCurrent(task.id, data, opts.expectedTask)
|
|
189
|
+
: (await reviewStore.putRound(task.id, data.phase, data), true);
|
|
190
|
+
if (!stored)
|
|
191
|
+
return false;
|
|
187
192
|
return true;
|
|
188
193
|
}
|
|
189
194
|
catch (err) {
|
|
@@ -203,9 +208,9 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
203
208
|
return holdConsumedSignal(task, agentId, kind, 'verdict-store-failed', `server-${data.phase}-verdict-store-failed`, {
|
|
204
209
|
round: data.round,
|
|
205
210
|
error: lastError instanceof Error ? lastError.message : String(lastError),
|
|
206
|
-
});
|
|
211
|
+
}, opts.onSideEffect);
|
|
207
212
|
}
|
|
208
|
-
async function holdConsumedSignal(task, agentId, kind, reason, failurePhase, data = {}) {
|
|
213
|
+
async function holdConsumedSignal(task, agentId, kind, reason, failurePhase, data = {}, onSideEffect) {
|
|
209
214
|
if (!SERVER_SIGNAL_KINDS.has(kind)) {
|
|
210
215
|
throw new Error(`unsupported consumed server signal kind: ${kind}`);
|
|
211
216
|
}
|
|
@@ -214,6 +219,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
214
219
|
const held = await manager.holdConsumedServerSignal(task, agentId, kind, { phase, round, reason, failurePhase });
|
|
215
220
|
if (!held)
|
|
216
221
|
return false;
|
|
222
|
+
onSideEffect?.();
|
|
217
223
|
await emitIntervention(bus, held, {
|
|
218
224
|
phase: failurePhase,
|
|
219
225
|
...data,
|
|
@@ -239,17 +245,27 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
239
245
|
await holdConsumedSignal(task, agentId, kind, 'handler-failed', `server-${kind.startsWith('spec-') ? 'spec' : 'code'}-handler-failed`, { error: err instanceof Error ? err.message : String(err) });
|
|
240
246
|
}
|
|
241
247
|
}
|
|
242
|
-
async function deleteFindingsOrHold(task, qa, kind) {
|
|
248
|
+
async function deleteFindingsOrHold(task, qa, kind, expectedTask) {
|
|
243
249
|
try {
|
|
244
|
-
await
|
|
245
|
-
return true;
|
|
250
|
+
return await manager.deleteReviewExchangeIfCurrent(task.id, qa, 'findings', expectedTask);
|
|
246
251
|
}
|
|
247
252
|
catch (err) {
|
|
248
253
|
await holdConsumedSignal(task, qa.id, kind, 'handler-failed', `server-${kind.startsWith('spec-') ? 'spec' : 'code'}-findings-cleanup-failed`, { error: err instanceof Error ? err.message : String(err) });
|
|
249
254
|
return false;
|
|
250
255
|
}
|
|
251
256
|
}
|
|
252
|
-
async function
|
|
257
|
+
async function deleteResponseOrHold(task, dev, kind, opts) {
|
|
258
|
+
try {
|
|
259
|
+
const deleted = await manager.deleteReviewExchangeIfCurrent(task.id, dev, 'response', opts.expectedTask ?? taskGenerationGuard(task));
|
|
260
|
+
if (deleted)
|
|
261
|
+
opts.onSideEffect?.();
|
|
262
|
+
return deleted;
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
return holdConsumedSignal(task, dev.id, kind, 'handler-failed', `server-${kind.startsWith('spec-') ? 'spec' : 'code'}-response-cleanup-failed`, { error: err instanceof Error ? err.message : String(err) }, opts.onSideEffect);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function rejectServerResponse(task, dev, roundData, failure, onSideEffect) {
|
|
253
269
|
const phase = roundData.phase;
|
|
254
270
|
const signalKind = `${phase}-fixed`;
|
|
255
271
|
const sourceToken = task.signalToken;
|
|
@@ -283,6 +299,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
283
299
|
});
|
|
284
300
|
if (!claimed?.signalToken)
|
|
285
301
|
return false;
|
|
302
|
+
onSideEffect?.();
|
|
286
303
|
let recorded;
|
|
287
304
|
try {
|
|
288
305
|
recorded = await reviewStore.recordServerResponseFailure(task.id, phase, roundData.round, {
|
|
@@ -351,7 +368,9 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
351
368
|
return false;
|
|
352
369
|
}
|
|
353
370
|
try {
|
|
354
|
-
await
|
|
371
|
+
const deleted = await manager.deleteReviewExchangeIfCurrent(task.id, dev, 'response', taskGenerationGuard(beforeCleanup), currentRecovery);
|
|
372
|
+
if (!deleted)
|
|
373
|
+
return false;
|
|
355
374
|
}
|
|
356
375
|
catch (err) {
|
|
357
376
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -387,24 +406,30 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
387
406
|
const recorded = manager.getAgentConfig(task.qaAgentId);
|
|
388
407
|
return recorded?.role === 'qa' ? recorded : undefined;
|
|
389
408
|
}
|
|
390
|
-
async function pauseForUnavailableQa(task, entryKind, phase) {
|
|
409
|
+
async function pauseForUnavailableQa(task, entryKind, phase, onSideEffect) {
|
|
391
410
|
if (!task.qaAgentId || resolveActiveQa(task))
|
|
392
411
|
return false;
|
|
393
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', phase, { qaAgentId: task.qaAgentId });
|
|
412
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', phase, { qaAgentId: task.qaAgentId }, onSideEffect);
|
|
394
413
|
return true;
|
|
395
414
|
}
|
|
396
|
-
async function autoApproveCode(task, entryKind = 'code-done') {
|
|
397
|
-
const afterDone = await manager.commitServerAfterDone(task.id);
|
|
415
|
+
async function autoApproveCode(task, expectedTask, entryKind = 'code-done', onSideEffect) {
|
|
416
|
+
const afterDone = await manager.commitServerAfterDone(task.id, expectedTask);
|
|
417
|
+
onSideEffect?.();
|
|
398
418
|
if (afterDone === null) {
|
|
399
|
-
const ready = await manager.transitionTaskStatus(task.id, 'ready', {
|
|
419
|
+
const ready = await manager.transitionTaskStatus(task.id, 'ready', {
|
|
420
|
+
fromStatus: ['in_progress', 'fixing'],
|
|
421
|
+
...(expectedTask !== undefined ? { expectTask: expectedTask } : {}),
|
|
422
|
+
});
|
|
423
|
+
if (ready)
|
|
424
|
+
onSideEffect?.();
|
|
400
425
|
if (!ready) {
|
|
401
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-transition-failed');
|
|
426
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-transition-failed', {}, onSideEffect);
|
|
402
427
|
}
|
|
403
428
|
return;
|
|
404
429
|
}
|
|
405
430
|
const dev = manager.getAgentConfig(task.agentId);
|
|
406
431
|
if (!dev) {
|
|
407
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'fix-no-dev-agent', 'server-code-auto-approve-no-dev-agent');
|
|
432
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'fix-no-dev-agent', 'server-code-auto-approve-no-dev-agent', {}, onSideEffect);
|
|
408
433
|
return;
|
|
409
434
|
}
|
|
410
435
|
await manager.refreshWorkdirCacheFor(task.agentId);
|
|
@@ -413,28 +438,35 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
413
438
|
reviewHeadAnchorSha = await transport().readHeadSha(dev);
|
|
414
439
|
}
|
|
415
440
|
catch (err) {
|
|
416
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-head-capture-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
441
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-head-capture-failed', { error: err instanceof Error ? err.message : String(err) }, onSideEffect);
|
|
417
442
|
return;
|
|
418
443
|
}
|
|
419
|
-
const approved = await manager.transitionTaskStatus(task.id, 'approved', {
|
|
444
|
+
const approved = await manager.transitionTaskStatus(task.id, 'approved', {
|
|
445
|
+
fromStatus: ['in_progress', 'fixing'],
|
|
446
|
+
...(expectedTask !== undefined ? { expectTask: expectedTask } : {}),
|
|
447
|
+
}, { reviewHeadAnchorSha });
|
|
448
|
+
if (approved)
|
|
449
|
+
onSideEffect?.();
|
|
420
450
|
if (!approved) {
|
|
421
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-transition-failed');
|
|
451
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-transition-failed', {}, onSideEffect);
|
|
422
452
|
return;
|
|
423
453
|
}
|
|
424
454
|
const dispatched = await manager.dispatchServerAfterDone(task.id, afterDone);
|
|
425
455
|
if (!dispatched) {
|
|
426
|
-
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-publish-dispatch-failed');
|
|
456
|
+
await holdConsumedSignal(task, task.agentId, entryKind, 'handler-failed', 'server-code-auto-approve-publish-dispatch-failed', {}, onSideEffect);
|
|
427
457
|
}
|
|
428
458
|
}
|
|
429
459
|
function specApprovalIsHuman(task) {
|
|
430
460
|
return task.researchAgentId !== undefined
|
|
431
461
|
|| manager.getProjectConfig(task.projectId)?.specApproval === 'human';
|
|
432
462
|
}
|
|
433
|
-
async function advanceApprovedSpec(task, failurePhase) {
|
|
463
|
+
async function advanceApprovedSpec(task, failurePhase, expectedTask) {
|
|
434
464
|
try {
|
|
435
465
|
const result = specApprovalIsHuman(task)
|
|
436
|
-
? await manager.parkTaskAtSpecReady(task.id
|
|
437
|
-
|
|
466
|
+
? await manager.parkTaskAtSpecReady(task.id, {
|
|
467
|
+
...(expectedTask !== undefined ? { expectedTask } : {}),
|
|
468
|
+
})
|
|
469
|
+
: await manager.transitionToCodePhase(task.id, expectedTask);
|
|
438
470
|
if (!result) {
|
|
439
471
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'spec-reviewed', 'handler-failed', failurePhase);
|
|
440
472
|
}
|
|
@@ -443,17 +475,19 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
443
475
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'spec-reviewed', 'handler-failed', failurePhase, { error: err instanceof Error ? err.message : String(err) });
|
|
444
476
|
}
|
|
445
477
|
}
|
|
446
|
-
async function autoApproveSpec(task) {
|
|
478
|
+
async function autoApproveSpec(task, expectedTask, onSideEffect) {
|
|
447
479
|
try {
|
|
448
|
-
const result = await manager.transitionToCodePhase(task.id);
|
|
480
|
+
const result = await manager.transitionToCodePhase(task.id, expectedTask);
|
|
481
|
+
if (result)
|
|
482
|
+
onSideEffect?.();
|
|
449
483
|
if (!result) {
|
|
450
|
-
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-auto-approve-transition-failed');
|
|
484
|
+
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-auto-approve-transition-failed', {}, onSideEffect);
|
|
451
485
|
return false;
|
|
452
486
|
}
|
|
453
487
|
return true;
|
|
454
488
|
}
|
|
455
489
|
catch (err) {
|
|
456
|
-
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-auto-approve-transition-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
490
|
+
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-auto-approve-transition-failed', { error: err instanceof Error ? err.message : String(err) }, onSideEffect);
|
|
457
491
|
return false;
|
|
458
492
|
}
|
|
459
493
|
}
|
|
@@ -461,14 +495,18 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
461
495
|
const dev = manager.getAgentConfig(task.agentId);
|
|
462
496
|
if (!dev) {
|
|
463
497
|
const kind = task.status === 'fixing' ? 'code-fixed' : 'code-done';
|
|
464
|
-
return holdConsumedSignal(task, task.agentId, kind, 'fix-no-dev-agent', 'server-code-review-no-dev-agent');
|
|
498
|
+
return holdConsumedSignal(task, task.agentId, kind, 'fix-no-dev-agent', 'server-code-review-no-dev-agent', {}, opts.onSideEffect);
|
|
465
499
|
}
|
|
466
500
|
const cap = manager.getConfig().review.rounds + (task.maxRoundsContinues ?? 0);
|
|
467
501
|
const nextRound = opts.bumpRound === false ? task.reviewRound : task.reviewRound + 1;
|
|
468
502
|
if (nextRound > cap) {
|
|
469
|
-
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
503
|
+
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
504
|
+
fromStatus: ['in_progress', 'fixing'],
|
|
505
|
+
...(opts.expectedTask !== undefined ? { expectTask: opts.expectedTask } : {}),
|
|
506
|
+
});
|
|
470
507
|
if (!capResult)
|
|
471
508
|
return false;
|
|
509
|
+
opts.onSideEffect?.();
|
|
472
510
|
const paused = capResult.task;
|
|
473
511
|
if (paused.qaAgentId)
|
|
474
512
|
await releaseAtCap(paused, paused.qaAgentId, 'qaAgentId');
|
|
@@ -509,7 +547,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
509
547
|
}
|
|
510
548
|
catch (err) {
|
|
511
549
|
const kind = task.status === 'fixing' ? 'code-fixed' : 'code-done';
|
|
512
|
-
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-content-read-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
550
|
+
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-content-read-failed', { error: err instanceof Error ? err.message : String(err) }, opts.onSideEffect);
|
|
513
551
|
}
|
|
514
552
|
try {
|
|
515
553
|
const violation = await manager.findLineageViolation(task.id, content.baseSha);
|
|
@@ -520,12 +558,12 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
520
558
|
offendingBranch: violation.branch,
|
|
521
559
|
offendingSha: violation.sha,
|
|
522
560
|
note: 'The task branch embeds another active task\'s commits — reviewing it would leak foreign work into this task. Have the dev rebase onto origin/HEAD and re-emit the signal.',
|
|
523
|
-
});
|
|
561
|
+
}, opts.onSideEffect);
|
|
524
562
|
}
|
|
525
563
|
}
|
|
526
564
|
catch (err) {
|
|
527
565
|
const kind = task.status === 'fixing' ? 'code-fixed' : 'code-done';
|
|
528
|
-
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-lineage-check-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
566
|
+
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-lineage-check-failed', { error: err instanceof Error ? err.message : String(err) }, opts.onSideEffect);
|
|
529
567
|
}
|
|
530
568
|
}
|
|
531
569
|
const round = {
|
|
@@ -538,7 +576,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
538
576
|
...(content.headTree ? { headTree: content.headTree } : {}),
|
|
539
577
|
startedAt: new Date().toISOString(),
|
|
540
578
|
};
|
|
541
|
-
const putEntryRound = (data) => putVerdictRound(task, task.agentId, task.status === 'fixing' ? 'code-fixed' : 'code-done', data);
|
|
579
|
+
const putEntryRound = (data) => putVerdictRound(task, task.agentId, task.status === 'fixing' ? 'code-fixed' : 'code-done', data, opts);
|
|
542
580
|
const resolveRecheckInterdiff = async () => {
|
|
543
581
|
if (!opts.recheck)
|
|
544
582
|
return undefined;
|
|
@@ -558,7 +596,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
558
596
|
const dispatched = await manager.dispatchServerReviewToQa(task.id, {
|
|
559
597
|
phase: 'code',
|
|
560
598
|
recheck: opts.recheck,
|
|
561
|
-
...(opts.bumpRound
|
|
599
|
+
...(opts.bumpRound !== undefined ? { bumpRound: opts.bumpRound } : {}),
|
|
562
600
|
content: content.content,
|
|
563
601
|
reviewHeadAnchorSha,
|
|
564
602
|
...(content.baseSha ? { baseSha: content.baseSha } : {}),
|
|
@@ -568,11 +606,14 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
568
606
|
...(opts.priorFindingsJson ? { priorFindingsJson: opts.priorFindingsJson } : {}),
|
|
569
607
|
...(opts.priorResponseJson ? { priorResponseJson: opts.priorResponseJson } : {}),
|
|
570
608
|
callerOwnsConsumedSignalFailure: true,
|
|
609
|
+
...(opts.settlePermit !== undefined ? { settlePermit: opts.settlePermit } : {}),
|
|
610
|
+
...(opts.expectedTask !== undefined ? { expectedTask: opts.expectedTask } : {}),
|
|
611
|
+
...(opts.onSideEffect !== undefined ? { onSideEffect: opts.onSideEffect } : {}),
|
|
571
612
|
});
|
|
572
613
|
if (dispatched)
|
|
573
614
|
return true;
|
|
574
615
|
const kind = task.status === 'fixing' ? 'code-fixed' : 'code-done';
|
|
575
|
-
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-review-dispatch-failed');
|
|
616
|
+
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-code-review-dispatch-failed', {}, opts.onSideEffect);
|
|
576
617
|
}
|
|
577
618
|
async function dispatchLegacyBatch(taskId, batches, index, diffstat, opts) {
|
|
578
619
|
const text = batches[index].map(f => f.text).join('\n');
|
|
@@ -586,6 +627,8 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
586
627
|
...(opts.priorFindingsJson ? { priorFindingsJson: opts.priorFindingsJson } : {}),
|
|
587
628
|
...(opts.priorResponseJson ? { priorResponseJson: opts.priorResponseJson } : {}),
|
|
588
629
|
callerOwnsConsumedSignalFailure: true,
|
|
630
|
+
...(opts.settlePermit !== undefined ? { settlePermit: opts.settlePermit } : {}),
|
|
631
|
+
...(opts.expectedTask !== undefined ? { expectedTask: opts.expectedTask } : {}),
|
|
589
632
|
});
|
|
590
633
|
return dispatched !== null;
|
|
591
634
|
}
|
|
@@ -596,22 +639,22 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
596
639
|
const gated = await gate(bus, manager, event, { status: 'in_progress', phase: 'code', requireServerMode: true });
|
|
597
640
|
if (!gated)
|
|
598
641
|
return;
|
|
599
|
-
const { task } = gated;
|
|
642
|
+
const { task, expectedTask } = gated;
|
|
600
643
|
await runConsumedSignal(task, task.agentId, 'code-done', async () => {
|
|
601
644
|
if (await pauseForUnavailableQa(task, 'code-done', 'server-code-qa-unavailable'))
|
|
602
645
|
return;
|
|
603
646
|
if (!task.qaAgentId) {
|
|
604
|
-
await autoApproveCode(task);
|
|
647
|
+
await autoApproveCode(task, expectedTask);
|
|
605
648
|
return;
|
|
606
649
|
}
|
|
607
|
-
await prepareAndDispatchCodeReview(task, { recheck: false });
|
|
650
|
+
await prepareAndDispatchCodeReview(task, { recheck: false, expectedTask, settlePermit: settlePermitOf(event) });
|
|
608
651
|
});
|
|
609
652
|
});
|
|
610
653
|
bus.on('server.code.review.submitted', async (event) => {
|
|
611
654
|
const gated = await gate(bus, manager, event, { status: 'review', phase: 'code', requireServerMode: true });
|
|
612
655
|
if (!gated)
|
|
613
656
|
return;
|
|
614
|
-
const { task } = gated;
|
|
657
|
+
const { task, expectedTask } = gated;
|
|
615
658
|
await runConsumedSignal(task, task.qaAgentId ?? task.agentId, 'code-reviewed', async () => {
|
|
616
659
|
const qa = manager.getAgentConfig(task.qaAgentId ?? '');
|
|
617
660
|
if (!qa) {
|
|
@@ -652,16 +695,16 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
652
695
|
const batchFindings = [...(roundData.batchFindings ?? [])];
|
|
653
696
|
batchFindings[task.batchIndex] = findings;
|
|
654
697
|
current = { ...roundData, batchFindings };
|
|
655
|
-
if (!(await putVerdictRound(task, qa.id, 'code-reviewed', current)))
|
|
698
|
+
if (!(await putVerdictRound(task, qa.id, 'code-reviewed', current, { expectedTask })))
|
|
656
699
|
return;
|
|
657
|
-
if (!(await deleteFindingsOrHold(task, qa, 'code-reviewed')))
|
|
700
|
+
if (!(await deleteFindingsOrHold(task, qa, 'code-reviewed', expectedTask)))
|
|
658
701
|
return;
|
|
659
702
|
}
|
|
660
703
|
else {
|
|
661
704
|
current = { ...roundData, findings, completedAt: new Date().toISOString() };
|
|
662
|
-
if (!(await putVerdictRound(task, qa.id, 'code-reviewed', current)))
|
|
705
|
+
if (!(await putVerdictRound(task, qa.id, 'code-reviewed', current, { expectedTask })))
|
|
663
706
|
return;
|
|
664
|
-
if (!(await deleteFindingsOrHold(task, qa, 'code-reviewed')))
|
|
707
|
+
if (!(await deleteFindingsOrHold(task, qa, 'code-reviewed', expectedTask)))
|
|
665
708
|
return;
|
|
666
709
|
}
|
|
667
710
|
const isRecheck = round > 1;
|
|
@@ -673,6 +716,8 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
673
716
|
recheck: isRecheck,
|
|
674
717
|
...(prev?.findings ? { priorFindingsJson: JSON.stringify(prev.findings) } : {}),
|
|
675
718
|
...(prev?.response ? { priorResponseJson: JSON.stringify(prev.response) } : {}),
|
|
719
|
+
expectedTask,
|
|
720
|
+
settlePermit: settlePermitOf(event),
|
|
676
721
|
});
|
|
677
722
|
if (!dispatched) {
|
|
678
723
|
await holdConsumedSignal(task, qa.id, 'code-reviewed', 'handler-failed', 'server-code-review-continuation-dispatch-failed');
|
|
@@ -686,28 +731,30 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
686
731
|
...current,
|
|
687
732
|
findings: aggregated,
|
|
688
733
|
completedAt: new Date().toISOString(),
|
|
689
|
-
});
|
|
734
|
+
}, { expectedTask });
|
|
690
735
|
if (!stored)
|
|
691
736
|
return;
|
|
692
737
|
}
|
|
693
|
-
await manager.
|
|
694
|
-
|
|
738
|
+
const cleared = await manager.updateTaskIfStatus(task.id, expectedTask.status, { batchIndex: undefined, batchTotal: undefined }, expectedTask);
|
|
739
|
+
if (!cleared)
|
|
740
|
+
return;
|
|
741
|
+
await routeCodeVerdict({ ...task, batchIndex: undefined, batchTotal: undefined }, aggregated, expectedTask);
|
|
695
742
|
return;
|
|
696
743
|
}
|
|
697
|
-
await routeCodeVerdict(task, current.findings);
|
|
744
|
+
await routeCodeVerdict(task, current.findings, expectedTask);
|
|
698
745
|
});
|
|
699
746
|
});
|
|
700
|
-
async function routeCodeVerdict(task, findings) {
|
|
747
|
+
async function routeCodeVerdict(task, findings, expectedTask) {
|
|
701
748
|
if (findings.verdict === 'approve') {
|
|
702
|
-
const afterDone = await manager.commitServerAfterDone(task.id);
|
|
749
|
+
const afterDone = await manager.commitServerAfterDone(task.id, expectedTask);
|
|
703
750
|
if (afterDone === null) {
|
|
704
|
-
const ready = await manager.transitionTaskStatus(task.id, 'ready', { fromStatus: ['review'] });
|
|
751
|
+
const ready = await manager.transitionTaskStatus(task.id, 'ready', { fromStatus: ['review'], expectTask: expectedTask });
|
|
705
752
|
if (!ready) {
|
|
706
753
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'code-reviewed', 'handler-failed', 'server-code-ready-transition-failed');
|
|
707
754
|
}
|
|
708
755
|
return;
|
|
709
756
|
}
|
|
710
|
-
const approved = await manager.transitionTaskStatus(task.id, 'approved', { fromStatus: ['review'] });
|
|
757
|
+
const approved = await manager.transitionTaskStatus(task.id, 'approved', { fromStatus: ['review'], expectTask: expectedTask });
|
|
711
758
|
if (!approved) {
|
|
712
759
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'code-reviewed', 'handler-failed', 'server-code-approved-transition-failed');
|
|
713
760
|
return;
|
|
@@ -720,7 +767,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
720
767
|
}
|
|
721
768
|
const cap = manager.getConfig().review.rounds + (task.maxRoundsContinues ?? 0);
|
|
722
769
|
if (task.reviewRound >= cap) {
|
|
723
|
-
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', { fromStatus: ['review'] });
|
|
770
|
+
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', { fromStatus: ['review'], expectTask: expectedTask });
|
|
724
771
|
if (!capResult)
|
|
725
772
|
return;
|
|
726
773
|
const paused = capResult.task;
|
|
@@ -738,30 +785,35 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
738
785
|
});
|
|
739
786
|
return;
|
|
740
787
|
}
|
|
741
|
-
const dispatched = await manager.dispatchServerFixToDev(task.id, JSON.stringify(findings), { callerOwnsConsumedSignalFailure: true });
|
|
788
|
+
const dispatched = await manager.dispatchServerFixToDev(task.id, JSON.stringify(findings), { callerOwnsConsumedSignalFailure: true, expectedTask });
|
|
742
789
|
if (!dispatched) {
|
|
743
790
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'code-reviewed', 'handler-failed', 'server-code-fix-dispatch-failed');
|
|
744
791
|
}
|
|
745
792
|
}
|
|
746
|
-
async function recheckCodeOrAutoApprove(task, priorFindingsJson, priorResponseJson) {
|
|
747
|
-
if (await pauseForUnavailableQa(task, 'code-fixed', 'server-code-qa-unavailable-after-fix')) {
|
|
793
|
+
async function recheckCodeOrAutoApprove(task, priorFindingsJson, priorResponseJson, opts = {}) {
|
|
794
|
+
if (await pauseForUnavailableQa(task, 'code-fixed', 'server-code-qa-unavailable-after-fix', opts.onSideEffect)) {
|
|
748
795
|
return false;
|
|
749
796
|
}
|
|
750
797
|
if (!task.qaAgentId) {
|
|
751
|
-
await autoApproveCode(task, 'code-fixed');
|
|
798
|
+
await autoApproveCode(task, opts.expectedTask, 'code-fixed', opts.onSideEffect);
|
|
752
799
|
return true;
|
|
753
800
|
}
|
|
754
|
-
return prepareAndDispatchCodeReview(task, {
|
|
801
|
+
return prepareAndDispatchCodeReview(task, {
|
|
802
|
+
recheck: true,
|
|
803
|
+
priorFindingsJson,
|
|
804
|
+
priorResponseJson,
|
|
805
|
+
...opts,
|
|
806
|
+
});
|
|
755
807
|
}
|
|
756
|
-
async function runCodeFixSubmission(task) {
|
|
808
|
+
async function runCodeFixSubmission(task, opts = {}) {
|
|
757
809
|
const dev = manager.getAgentConfig(task.agentId);
|
|
758
810
|
if (!dev) {
|
|
759
|
-
return holdConsumedSignal(task, task.agentId, 'code-fixed', 'fix-no-dev-agent', 'server-code-fix-no-dev-agent');
|
|
811
|
+
return holdConsumedSignal(task, task.agentId, 'code-fixed', 'fix-no-dev-agent', 'server-code-fix-no-dev-agent', {}, opts.onSideEffect);
|
|
760
812
|
}
|
|
761
813
|
const round = Math.max(task.reviewRound, 1);
|
|
762
814
|
const roundData = await reviewStore.getRound(task.id, 'code', round);
|
|
763
815
|
if (!roundData?.findings) {
|
|
764
|
-
return holdConsumedSignal(task, task.agentId, 'code-fixed', 'fix-findings-missing', 'server-code-fix-findings-missing', { round });
|
|
816
|
+
return holdConsumedSignal(task, task.agentId, 'code-fixed', 'fix-findings-missing', 'server-code-fix-findings-missing', { round }, opts.onSideEffect);
|
|
765
817
|
}
|
|
766
818
|
await manager.refreshWorkdirCacheFor(task.agentId);
|
|
767
819
|
let readResult;
|
|
@@ -772,10 +824,10 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
772
824
|
return holdConsumedSignal(task, dev.id, 'code-fixed', 'response-read-failed', 'server-code-response-read-failed', {
|
|
773
825
|
reason: err instanceof ReviewExchangeError ? err.reason : 'read-failed',
|
|
774
826
|
error: err instanceof Error ? err.message : String(err),
|
|
775
|
-
});
|
|
827
|
+
}, opts.onSideEffect);
|
|
776
828
|
}
|
|
777
829
|
if (readResult.kind === 'unknown') {
|
|
778
|
-
return holdConsumedSignal(task, dev.id, 'code-fixed', 'response-read-failed', 'server-code-response-read-failed', { reason: readResult.error.reason, error: readResult.error.message });
|
|
830
|
+
return holdConsumedSignal(task, dev.id, 'code-fixed', 'response-read-failed', 'server-code-response-read-failed', { reason: readResult.error.reason, error: readResult.error.message }, opts.onSideEffect);
|
|
779
831
|
}
|
|
780
832
|
if (readResult.kind === 'invalid') {
|
|
781
833
|
return rejectServerResponse(task, dev, roundData, {
|
|
@@ -785,16 +837,16 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
785
837
|
responseDigest: readResult.responseDigest,
|
|
786
838
|
schemaViolationCodes: readResult.schemaViolationCodes,
|
|
787
839
|
details: { error: readResult.error.message },
|
|
788
|
-
});
|
|
840
|
+
}, opts.onSideEffect);
|
|
789
841
|
}
|
|
790
842
|
if (readResult.kind === 'absent') {
|
|
791
843
|
if (roundData.response === undefined) {
|
|
792
844
|
return rejectServerResponse(task, dev, roundData, {
|
|
793
845
|
reason: 'response-missing',
|
|
794
846
|
failurePhase: 'server-code-response-missing',
|
|
795
|
-
});
|
|
847
|
+
}, opts.onSideEffect);
|
|
796
848
|
}
|
|
797
|
-
return recheckCodeOrAutoApprove(task, JSON.stringify(roundData.findings), JSON.stringify(roundData.response));
|
|
849
|
+
return recheckCodeOrAutoApprove(task, JSON.stringify(roundData.findings), JSON.stringify(roundData.response), opts);
|
|
798
850
|
}
|
|
799
851
|
const { response } = readResult;
|
|
800
852
|
if (response.round !== round) {
|
|
@@ -804,7 +856,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
804
856
|
rawResponse: readResult.raw,
|
|
805
857
|
responseDigest: readResult.responseDigest,
|
|
806
858
|
details: { payloadRound: response.round },
|
|
807
|
-
});
|
|
859
|
+
}, opts.onSideEffect);
|
|
808
860
|
}
|
|
809
861
|
if (response.token !== task.signalToken) {
|
|
810
862
|
return rejectServerResponse(task, dev, roundData, {
|
|
@@ -812,7 +864,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
812
864
|
failurePhase: 'server-code-response-token-mismatch',
|
|
813
865
|
rawResponse: readResult.raw,
|
|
814
866
|
responseDigest: readResult.responseDigest,
|
|
815
|
-
});
|
|
867
|
+
}, opts.onSideEffect);
|
|
816
868
|
}
|
|
817
869
|
const findingsDigest = reviewFindingsDigest(roundData.findings);
|
|
818
870
|
if (response.findingsDigest !== findingsDigest) {
|
|
@@ -821,7 +873,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
821
873
|
failurePhase: 'server-code-response-findings-digest-mismatch',
|
|
822
874
|
rawResponse: readResult.raw,
|
|
823
875
|
responseDigest: readResult.responseDigest,
|
|
824
|
-
});
|
|
876
|
+
}, opts.onSideEffect);
|
|
825
877
|
}
|
|
826
878
|
const gaps = coverageGaps(roundData.findings, new Set(response.responses.map(r => r.findingId)));
|
|
827
879
|
if (gaps.missing.length > 0 || gaps.unknown.length > 0) {
|
|
@@ -832,24 +884,20 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
832
884
|
responseDigest: readResult.responseDigest,
|
|
833
885
|
missingFindingIds: gaps.missing,
|
|
834
886
|
unknownFindingIds: gaps.unknown,
|
|
835
|
-
});
|
|
887
|
+
}, opts.onSideEffect);
|
|
836
888
|
}
|
|
837
|
-
if (!(await putVerdictRound(task, dev.id, 'code-fixed', { ...roundData, response })))
|
|
889
|
+
if (!(await putVerdictRound(task, dev.id, 'code-fixed', { ...roundData, response }, opts)))
|
|
838
890
|
return false;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
catch (err) {
|
|
843
|
-
return holdConsumedSignal(task, dev.id, 'code-fixed', 'handler-failed', 'server-code-response-cleanup-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
844
|
-
}
|
|
845
|
-
return recheckCodeOrAutoApprove(task, JSON.stringify(roundData.findings), JSON.stringify(response));
|
|
891
|
+
if (!(await deleteResponseOrHold(task, dev, 'code-fixed', opts)))
|
|
892
|
+
return false;
|
|
893
|
+
return recheckCodeOrAutoApprove(task, JSON.stringify(roundData.findings), JSON.stringify(response), opts);
|
|
846
894
|
}
|
|
847
895
|
bus.on('server.code.fix.submitted', async (event) => {
|
|
848
896
|
const gated = await gate(bus, manager, event, { status: 'fixing', phase: 'code', requireServerMode: true });
|
|
849
897
|
if (!gated)
|
|
850
898
|
return;
|
|
851
899
|
await runConsumedSignal(gated.task, gated.task.agentId, 'code-fixed', async () => {
|
|
852
|
-
await runCodeFixSubmission(gated.task);
|
|
900
|
+
await runCodeFixSubmission(gated.task, { expectedTask: gated.expectedTask, settlePermit: settlePermitOf(event) });
|
|
853
901
|
});
|
|
854
902
|
});
|
|
855
903
|
bus.on('server.code.published', async (event) => {
|
|
@@ -884,6 +932,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
884
932
|
});
|
|
885
933
|
return;
|
|
886
934
|
}
|
|
935
|
+
let verifiedBaseBranch;
|
|
887
936
|
if (prNumber !== undefined) {
|
|
888
937
|
const verified = await manager.verifyPaneSignalPrNumber(task.id, prNumber);
|
|
889
938
|
if (!verified) {
|
|
@@ -899,19 +948,24 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
899
948
|
});
|
|
900
949
|
return;
|
|
901
950
|
}
|
|
902
|
-
|
|
903
|
-
prNumber,
|
|
904
|
-
...(task.baseBranch === undefined ? { baseBranch: verified.targetBranch } : {}),
|
|
905
|
-
});
|
|
951
|
+
verifiedBaseBranch = verified.targetBranch;
|
|
906
952
|
}
|
|
953
|
+
let ready;
|
|
907
954
|
try {
|
|
908
|
-
await manager.
|
|
955
|
+
ready = await manager.transitionTaskStatus(task.id, 'ready', { fromStatus: ['approved'], expectTask: gated.expectedTask }, {
|
|
956
|
+
...(prNumber !== undefined ? { prNumber } : {}),
|
|
957
|
+
...(prNumber !== undefined
|
|
958
|
+
&& task.baseBranch === undefined
|
|
959
|
+
&& verifiedBaseBranch !== undefined
|
|
960
|
+
? { baseBranch: verifiedBaseBranch }
|
|
961
|
+
: {}),
|
|
962
|
+
latestHeadSha: publishedHead,
|
|
963
|
+
});
|
|
909
964
|
}
|
|
910
965
|
catch (err) {
|
|
911
966
|
await holdConsumedSignal(task, task.agentId, 'code-ready', 'handler-failed', 'server-code-published-head-capture-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
912
967
|
return;
|
|
913
968
|
}
|
|
914
|
-
const ready = await manager.transitionTaskStatus(task.id, 'ready', { fromStatus: ['approved'] });
|
|
915
969
|
if (!ready) {
|
|
916
970
|
await holdConsumedSignal(task, task.agentId, 'code-ready', 'handler-failed', 'server-code-published-transition-failed');
|
|
917
971
|
}
|
|
@@ -922,22 +976,26 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
922
976
|
if (!gated)
|
|
923
977
|
return;
|
|
924
978
|
await runConsumedSignal(gated.task, gated.task.agentId, 'spec-done', async () => {
|
|
925
|
-
await dispatchSpecReview(gated.task);
|
|
979
|
+
await dispatchSpecReview(gated.task, undefined, { expectedTask: gated.expectedTask, settlePermit: settlePermitOf(event) });
|
|
926
980
|
});
|
|
927
981
|
});
|
|
928
982
|
async function dispatchSpecReview(task, prior, opts = {}) {
|
|
929
983
|
const dev = manager.getAgentConfig(task.agentId);
|
|
930
984
|
if (!dev) {
|
|
931
985
|
const kind = task.status === 'fixing' ? 'spec-fixed' : 'spec-done';
|
|
932
|
-
return holdConsumedSignal(task, task.agentId, kind, 'fix-no-dev-agent', 'server-spec-review-no-dev-agent');
|
|
986
|
+
return holdConsumedSignal(task, task.agentId, kind, 'fix-no-dev-agent', 'server-spec-review-no-dev-agent', {}, opts.onSideEffect);
|
|
933
987
|
}
|
|
934
988
|
const cap = manager.getConfig().review.rounds + (task.maxRoundsContinues ?? 0);
|
|
935
989
|
const currentRound = task.specReviewRound ?? 0;
|
|
936
990
|
const nextRound = opts.bumpRound === false ? currentRound : currentRound + 1;
|
|
937
991
|
if (nextRound > cap) {
|
|
938
|
-
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
992
|
+
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
993
|
+
fromStatus: ['in_progress', 'fixing'],
|
|
994
|
+
...(opts.expectedTask !== undefined ? { expectTask: opts.expectedTask } : {}),
|
|
995
|
+
});
|
|
939
996
|
if (!capResult)
|
|
940
997
|
return false;
|
|
998
|
+
opts.onSideEffect?.();
|
|
941
999
|
const paused = capResult.task;
|
|
942
1000
|
if (paused.qaAgentId)
|
|
943
1001
|
await releaseAtCap(paused, paused.qaAgentId, 'qaAgentId');
|
|
@@ -960,22 +1018,27 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
960
1018
|
}
|
|
961
1019
|
catch (err) {
|
|
962
1020
|
const kind = task.status === 'fixing' ? 'spec-fixed' : 'spec-done';
|
|
963
|
-
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-spec-content-read-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1021
|
+
return holdConsumedSignal(task, task.agentId, kind, 'handler-failed', 'server-spec-content-read-failed', { error: err instanceof Error ? err.message : String(err) }, opts.onSideEffect);
|
|
964
1022
|
}
|
|
965
1023
|
try {
|
|
966
1024
|
if (!content.documents)
|
|
967
1025
|
throw new Error('spec transport returned no documents');
|
|
968
|
-
|
|
1026
|
+
const roundData = {
|
|
969
1027
|
round: nextRound,
|
|
970
1028
|
phase: 'spec',
|
|
971
1029
|
content: content.content,
|
|
972
1030
|
documents: content.documents,
|
|
973
1031
|
startedAt: new Date().toISOString(),
|
|
974
|
-
}
|
|
1032
|
+
};
|
|
1033
|
+
const stored = opts.expectedTask
|
|
1034
|
+
? await manager.putReviewRoundIfCurrent(task.id, roundData, opts.expectedTask)
|
|
1035
|
+
: (await reviewStore.putRound(task.id, 'spec', roundData), true);
|
|
1036
|
+
if (!stored)
|
|
1037
|
+
return false;
|
|
975
1038
|
}
|
|
976
1039
|
catch (err) {
|
|
977
1040
|
const kind = task.status === 'fixing' ? 'spec-fixed' : 'spec-done';
|
|
978
|
-
return holdConsumedSignal(task, task.agentId, kind, 'verdict-store-failed', 'server-spec-round-store-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1041
|
+
return holdConsumedSignal(task, task.agentId, kind, 'verdict-store-failed', 'server-spec-round-store-failed', { error: err instanceof Error ? err.message : String(err) }, opts.onSideEffect);
|
|
979
1042
|
}
|
|
980
1043
|
}
|
|
981
1044
|
if (!resolveActiveQa(task)) {
|
|
@@ -986,31 +1049,39 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
986
1049
|
});
|
|
987
1050
|
}
|
|
988
1051
|
if (specApprovalIsHuman(task) || task.qaAgentId) {
|
|
989
|
-
const parked = await manager.parkTaskAtSpecReady(task.id, {
|
|
1052
|
+
const parked = await manager.parkTaskAtSpecReady(task.id, {
|
|
1053
|
+
specReviewRound: nextRound,
|
|
1054
|
+
...(opts.expectedTask !== undefined ? { expectedTask: opts.expectedTask } : {}),
|
|
1055
|
+
});
|
|
1056
|
+
if (parked)
|
|
1057
|
+
opts.onSideEffect?.();
|
|
990
1058
|
if (!parked) {
|
|
991
|
-
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-park-transition-failed');
|
|
1059
|
+
await holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-park-transition-failed', {}, opts.onSideEffect);
|
|
992
1060
|
}
|
|
993
1061
|
return !!parked;
|
|
994
1062
|
}
|
|
995
|
-
return autoApproveSpec(task);
|
|
1063
|
+
return autoApproveSpec(task, opts.expectedTask, opts.onSideEffect);
|
|
996
1064
|
}
|
|
997
1065
|
const dispatched = await manager.dispatchServerReviewToQa(task.id, {
|
|
998
1066
|
phase: 'spec',
|
|
999
|
-
...(opts.bumpRound
|
|
1067
|
+
...(opts.bumpRound !== undefined ? { bumpRound: opts.bumpRound } : {}),
|
|
1000
1068
|
content: content.content,
|
|
1001
1069
|
...(prior ? { priorFindingsJson: prior.findingsJson } : {}),
|
|
1002
1070
|
...(prior?.responseJson ? { priorResponseJson: prior.responseJson } : {}),
|
|
1003
1071
|
callerOwnsConsumedSignalFailure: true,
|
|
1072
|
+
...(opts.settlePermit !== undefined ? { settlePermit: opts.settlePermit } : {}),
|
|
1073
|
+
...(opts.expectedTask !== undefined ? { expectedTask: opts.expectedTask } : {}),
|
|
1074
|
+
...(opts.onSideEffect !== undefined ? { onSideEffect: opts.onSideEffect } : {}),
|
|
1004
1075
|
});
|
|
1005
1076
|
if (dispatched)
|
|
1006
1077
|
return true;
|
|
1007
|
-
return holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-review-dispatch-failed');
|
|
1078
|
+
return holdConsumedSignal(task, task.agentId, task.status === 'fixing' ? 'spec-fixed' : 'spec-done', 'handler-failed', 'server-spec-review-dispatch-failed', {}, opts.onSideEffect);
|
|
1008
1079
|
}
|
|
1009
1080
|
bus.on('server.spec.review.submitted', async (event) => {
|
|
1010
1081
|
const gated = await gate(bus, manager, event, { status: 'review', phase: 'spec', requireServerMode: false });
|
|
1011
1082
|
if (!gated)
|
|
1012
1083
|
return;
|
|
1013
|
-
const { task } = gated;
|
|
1084
|
+
const { task, expectedTask } = gated;
|
|
1014
1085
|
await runConsumedSignal(task, task.qaAgentId ?? task.agentId, 'spec-reviewed', async () => {
|
|
1015
1086
|
const qa = manager.getAgentConfig(task.qaAgentId ?? '');
|
|
1016
1087
|
if (!qa) {
|
|
@@ -1049,19 +1120,22 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1049
1120
|
...roundData,
|
|
1050
1121
|
findings: effective,
|
|
1051
1122
|
completedAt: new Date().toISOString(),
|
|
1052
|
-
});
|
|
1123
|
+
}, { expectedTask });
|
|
1053
1124
|
if (!stored)
|
|
1054
1125
|
return;
|
|
1055
|
-
if (!(await deleteFindingsOrHold(task, qa, 'spec-reviewed')))
|
|
1126
|
+
if (!(await deleteFindingsOrHold(task, qa, 'spec-reviewed', expectedTask)))
|
|
1056
1127
|
return;
|
|
1057
1128
|
}
|
|
1058
1129
|
if (effective.verdict === 'approve') {
|
|
1059
|
-
await advanceApprovedSpec(task, 'server-spec-approve-transition-failed');
|
|
1130
|
+
await advanceApprovedSpec(task, 'server-spec-approve-transition-failed', expectedTask);
|
|
1060
1131
|
return;
|
|
1061
1132
|
}
|
|
1062
1133
|
const cap = manager.getConfig().review.rounds + (task.maxRoundsContinues ?? 0);
|
|
1063
1134
|
if ((task.specReviewRound ?? 0) >= cap) {
|
|
1064
|
-
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
1135
|
+
const capResult = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
1136
|
+
fromStatus: ['review'],
|
|
1137
|
+
expectTask: expectedTask,
|
|
1138
|
+
});
|
|
1065
1139
|
if (!capResult)
|
|
1066
1140
|
return;
|
|
1067
1141
|
const paused = capResult.task;
|
|
@@ -1071,21 +1145,21 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1071
1145
|
await releaseAtCap(paused, paused.agentId, 'agentId');
|
|
1072
1146
|
return;
|
|
1073
1147
|
}
|
|
1074
|
-
const dispatched = await manager.dispatchServerFixToDev(task.id, JSON.stringify(effective), { callerOwnsConsumedSignalFailure: true });
|
|
1148
|
+
const dispatched = await manager.dispatchServerFixToDev(task.id, JSON.stringify(effective), { callerOwnsConsumedSignalFailure: true, expectedTask });
|
|
1075
1149
|
if (!dispatched) {
|
|
1076
1150
|
await holdConsumedSignal(task, task.qaAgentId ?? task.agentId, 'spec-reviewed', 'handler-failed', 'server-spec-fix-dispatch-failed');
|
|
1077
1151
|
}
|
|
1078
1152
|
});
|
|
1079
1153
|
});
|
|
1080
|
-
async function runSpecFixSubmission(task) {
|
|
1154
|
+
async function runSpecFixSubmission(task, opts = {}) {
|
|
1081
1155
|
const dev = manager.getAgentConfig(task.agentId);
|
|
1082
1156
|
if (!dev) {
|
|
1083
|
-
return holdConsumedSignal(task, task.agentId, 'spec-fixed', 'fix-no-dev-agent', 'server-spec-fix-no-dev-agent');
|
|
1157
|
+
return holdConsumedSignal(task, task.agentId, 'spec-fixed', 'fix-no-dev-agent', 'server-spec-fix-no-dev-agent', {}, opts.onSideEffect);
|
|
1084
1158
|
}
|
|
1085
1159
|
const round = task.specReviewRound ?? 1;
|
|
1086
1160
|
const roundData = await reviewStore.getRound(task.id, 'spec', round);
|
|
1087
1161
|
if (!roundData?.findings) {
|
|
1088
|
-
return holdConsumedSignal(task, task.agentId, 'spec-fixed', 'fix-findings-missing', 'server-spec-fix-findings-missing', { round });
|
|
1162
|
+
return holdConsumedSignal(task, task.agentId, 'spec-fixed', 'fix-findings-missing', 'server-spec-fix-findings-missing', { round }, opts.onSideEffect);
|
|
1089
1163
|
}
|
|
1090
1164
|
await manager.refreshWorkdirCacheFor(task.agentId);
|
|
1091
1165
|
let readResult;
|
|
@@ -1096,10 +1170,10 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1096
1170
|
return holdConsumedSignal(task, dev.id, 'spec-fixed', 'response-read-failed', 'server-spec-response-read-failed', {
|
|
1097
1171
|
reason: err instanceof ReviewExchangeError ? err.reason : 'read-failed',
|
|
1098
1172
|
error: err instanceof Error ? err.message : String(err),
|
|
1099
|
-
});
|
|
1173
|
+
}, opts.onSideEffect);
|
|
1100
1174
|
}
|
|
1101
1175
|
if (readResult.kind === 'unknown') {
|
|
1102
|
-
return holdConsumedSignal(task, dev.id, 'spec-fixed', 'response-read-failed', 'server-spec-response-read-failed', { reason: readResult.error.reason, error: readResult.error.message });
|
|
1176
|
+
return holdConsumedSignal(task, dev.id, 'spec-fixed', 'response-read-failed', 'server-spec-response-read-failed', { reason: readResult.error.reason, error: readResult.error.message }, opts.onSideEffect);
|
|
1103
1177
|
}
|
|
1104
1178
|
if (readResult.kind === 'invalid') {
|
|
1105
1179
|
return rejectServerResponse(task, dev, roundData, {
|
|
@@ -1109,19 +1183,19 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1109
1183
|
responseDigest: readResult.responseDigest,
|
|
1110
1184
|
schemaViolationCodes: readResult.schemaViolationCodes,
|
|
1111
1185
|
details: { error: readResult.error.message },
|
|
1112
|
-
});
|
|
1186
|
+
}, opts.onSideEffect);
|
|
1113
1187
|
}
|
|
1114
1188
|
if (readResult.kind === 'absent') {
|
|
1115
1189
|
if (roundData.response === undefined) {
|
|
1116
1190
|
return rejectServerResponse(task, dev, roundData, {
|
|
1117
1191
|
reason: 'response-missing',
|
|
1118
1192
|
failurePhase: 'server-spec-response-missing',
|
|
1119
|
-
});
|
|
1193
|
+
}, opts.onSideEffect);
|
|
1120
1194
|
}
|
|
1121
1195
|
return dispatchSpecReview(task, {
|
|
1122
1196
|
findingsJson: JSON.stringify(roundData.findings),
|
|
1123
1197
|
responseJson: JSON.stringify(roundData.response),
|
|
1124
|
-
});
|
|
1198
|
+
}, opts);
|
|
1125
1199
|
}
|
|
1126
1200
|
const { response } = readResult;
|
|
1127
1201
|
if (response.round !== round) {
|
|
@@ -1131,7 +1205,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1131
1205
|
rawResponse: readResult.raw,
|
|
1132
1206
|
responseDigest: readResult.responseDigest,
|
|
1133
1207
|
details: { payloadRound: response.round },
|
|
1134
|
-
});
|
|
1208
|
+
}, opts.onSideEffect);
|
|
1135
1209
|
}
|
|
1136
1210
|
if (response.token !== task.signalToken) {
|
|
1137
1211
|
return rejectServerResponse(task, dev, roundData, {
|
|
@@ -1139,7 +1213,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1139
1213
|
failurePhase: 'server-spec-response-token-mismatch',
|
|
1140
1214
|
rawResponse: readResult.raw,
|
|
1141
1215
|
responseDigest: readResult.responseDigest,
|
|
1142
|
-
});
|
|
1216
|
+
}, opts.onSideEffect);
|
|
1143
1217
|
}
|
|
1144
1218
|
const findingsDigest = reviewFindingsDigest(roundData.findings);
|
|
1145
1219
|
if (response.findingsDigest !== findingsDigest) {
|
|
@@ -1148,7 +1222,7 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1148
1222
|
failurePhase: 'server-spec-response-findings-digest-mismatch',
|
|
1149
1223
|
rawResponse: readResult.raw,
|
|
1150
1224
|
responseDigest: readResult.responseDigest,
|
|
1151
|
-
});
|
|
1225
|
+
}, opts.onSideEffect);
|
|
1152
1226
|
}
|
|
1153
1227
|
const gaps = coverageGaps(roundData.findings, new Set(response.responses.map(r => r.findingId)));
|
|
1154
1228
|
if (gaps.missing.length > 0 || gaps.unknown.length > 0) {
|
|
@@ -1159,27 +1233,23 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1159
1233
|
responseDigest: readResult.responseDigest,
|
|
1160
1234
|
missingFindingIds: gaps.missing,
|
|
1161
1235
|
unknownFindingIds: gaps.unknown,
|
|
1162
|
-
});
|
|
1236
|
+
}, opts.onSideEffect);
|
|
1163
1237
|
}
|
|
1164
|
-
if (!(await putVerdictRound(task, dev.id, 'spec-fixed', { ...roundData, response })))
|
|
1238
|
+
if (!(await putVerdictRound(task, dev.id, 'spec-fixed', { ...roundData, response }, opts)))
|
|
1239
|
+
return false;
|
|
1240
|
+
if (!(await deleteResponseOrHold(task, dev, 'spec-fixed', opts)))
|
|
1165
1241
|
return false;
|
|
1166
|
-
try {
|
|
1167
|
-
await transport().deleteResponse(dev);
|
|
1168
|
-
}
|
|
1169
|
-
catch (err) {
|
|
1170
|
-
return holdConsumedSignal(task, dev.id, 'spec-fixed', 'handler-failed', 'server-spec-response-cleanup-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1171
|
-
}
|
|
1172
1242
|
return dispatchSpecReview(task, {
|
|
1173
1243
|
findingsJson: JSON.stringify(roundData.findings),
|
|
1174
1244
|
responseJson: JSON.stringify(response),
|
|
1175
|
-
});
|
|
1245
|
+
}, opts);
|
|
1176
1246
|
}
|
|
1177
1247
|
bus.on('server.spec.fix.submitted', async (event) => {
|
|
1178
1248
|
const gated = await gate(bus, manager, event, { status: 'fixing', phase: 'spec', requireServerMode: false });
|
|
1179
1249
|
if (!gated)
|
|
1180
1250
|
return;
|
|
1181
1251
|
await runConsumedSignal(gated.task, gated.task.agentId, 'spec-fixed', async () => {
|
|
1182
|
-
await runSpecFixSubmission(gated.task);
|
|
1252
|
+
await runSpecFixSubmission(gated.task, { expectedTask: gated.expectedTask, settlePermit: settlePermitOf(event) });
|
|
1183
1253
|
});
|
|
1184
1254
|
});
|
|
1185
1255
|
async function latestVerdictRound(taskId, phase, fromRound) {
|
|
@@ -1195,18 +1265,21 @@ export function registerServerEventHandlers(bus, manager) {
|
|
|
1195
1265
|
manager.setServerReviewDriver({
|
|
1196
1266
|
dispatchCodeReview: async (task, opts = {}) => {
|
|
1197
1267
|
if (task.status === 'fixing')
|
|
1198
|
-
return runCodeFixSubmission(task);
|
|
1268
|
+
return runCodeFixSubmission(task, opts);
|
|
1199
1269
|
const prior = await latestVerdictRound(task.id, 'code', task.reviewRound);
|
|
1200
1270
|
return prepareAndDispatchCodeReview(task, {
|
|
1201
1271
|
recheck: prior !== null,
|
|
1202
|
-
...(opts.bumpRound
|
|
1272
|
+
...(opts.bumpRound !== undefined ? { bumpRound: opts.bumpRound } : {}),
|
|
1273
|
+
...(opts.expectedTask !== undefined ? { expectedTask: opts.expectedTask } : {}),
|
|
1274
|
+
...(opts.onSideEffect !== undefined ? { onSideEffect: opts.onSideEffect } : {}),
|
|
1275
|
+
...(opts.settlePermit !== undefined ? { settlePermit: opts.settlePermit } : {}),
|
|
1203
1276
|
...(prior?.findings ? { priorFindingsJson: JSON.stringify(prior.findings) } : {}),
|
|
1204
1277
|
...(prior?.response ? { priorResponseJson: JSON.stringify(prior.response) } : {}),
|
|
1205
1278
|
});
|
|
1206
1279
|
},
|
|
1207
1280
|
dispatchSpecReview: async (task, opts = {}) => {
|
|
1208
1281
|
if (task.status === 'fixing')
|
|
1209
|
-
return runSpecFixSubmission(task);
|
|
1282
|
+
return runSpecFixSubmission(task, opts);
|
|
1210
1283
|
const prior = await latestVerdictRound(task.id, 'spec', task.specReviewRound ?? 0);
|
|
1211
1284
|
const priorContext = prior?.findings
|
|
1212
1285
|
? {
|