baxian 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/README.zh-CN.md +5 -7
- package/dist/agent/bootstrap.d.ts.map +1 -1
- package/dist/agent/bootstrap.js +2 -4
- package/dist/agent/bootstrap.js.map +1 -1
- package/dist/agent/branch.d.ts +0 -12
- package/dist/agent/branch.d.ts.map +1 -1
- package/dist/agent/branch.js +1 -78
- package/dist/agent/branch.js.map +1 -1
- package/dist/agent/detect/region.d.ts.map +1 -1
- package/dist/agent/detect/region.js +0 -3
- package/dist/agent/detect/region.js.map +1 -1
- package/dist/agent/dispatch-reconciler.d.ts.map +1 -1
- package/dist/agent/dispatch-reconciler.js +8 -59
- package/dist/agent/dispatch-reconciler.js.map +1 -1
- package/dist/agent/lineage.d.ts.map +1 -1
- package/dist/agent/lineage.js +0 -9
- package/dist/agent/lineage.js.map +1 -1
- package/dist/agent/manager.d.ts +66 -142
- package/dist/agent/manager.d.ts.map +1 -1
- package/dist/agent/manager.js +936 -2738
- package/dist/agent/manager.js.map +1 -1
- package/dist/agent/net-exec.d.ts.map +1 -1
- package/dist/agent/net-exec.js +0 -5
- package/dist/agent/net-exec.js.map +1 -1
- package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
- package/dist/agent/pane-streamer-manager.js +0 -36
- package/dist/agent/pane-streamer-manager.js.map +1 -1
- package/dist/agent/pane-streamer.d.ts +10 -1
- package/dist/agent/pane-streamer.d.ts.map +1 -1
- package/dist/agent/pane-streamer.js +142 -79
- package/dist/agent/pane-streamer.js.map +1 -1
- package/dist/agent/phase-signal-watcher.d.ts +2 -11
- package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
- package/dist/agent/phase-signal-watcher.js +36 -160
- package/dist/agent/phase-signal-watcher.js.map +1 -1
- package/dist/agent/phase-signal.d.ts +13 -40
- package/dist/agent/phase-signal.d.ts.map +1 -1
- package/dist/agent/phase-signal.js +38 -98
- package/dist/agent/phase-signal.js.map +1 -1
- package/dist/agent/preflight.d.ts +0 -1
- package/dist/agent/preflight.d.ts.map +1 -1
- package/dist/agent/preflight.js +6 -31
- package/dist/agent/preflight.js.map +1 -1
- package/dist/agent/prompt.d.ts +1 -30
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +20 -148
- 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 -42
- package/dist/agent/repo-store.js.map +1 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +0 -2
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tmux-install.js +0 -1
- package/dist/agent/tmux-install.js.map +1 -1
- package/dist/agent/tmux-probe-poller.d.ts.map +1 -1
- package/dist/agent/tmux-probe-poller.js +0 -7
- package/dist/agent/tmux-probe-poller.js.map +1 -1
- package/dist/agent/tmux.d.ts.map +1 -1
- package/dist/agent/tmux.js +0 -86
- package/dist/agent/tmux.js.map +1 -1
- package/dist/agent/vt-visible-text.d.ts +87 -0
- package/dist/agent/vt-visible-text.d.ts.map +1 -0
- package/dist/agent/vt-visible-text.js +1058 -0
- package/dist/agent/vt-visible-text.js.map +1 -0
- package/dist/api/agents.d.ts.map +1 -1
- package/dist/api/agents.js +1 -42
- package/dist/api/agents.js.map +1 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +8 -53
- package/dist/api/config.js.map +1 -1
- package/dist/api/hosts.d.ts.map +1 -1
- package/dist/api/hosts.js +2 -57
- package/dist/api/hosts.js.map +1 -1
- package/dist/api/platform-guard.d.ts.map +1 -1
- package/dist/api/platform-guard.js +2 -3
- package/dist/api/platform-guard.js.map +1 -1
- package/dist/api/probe.d.ts.map +1 -1
- package/dist/api/probe.js +0 -1
- package/dist/api/probe.js.map +1 -1
- package/dist/api/projects.d.ts.map +1 -1
- package/dist/api/projects.js +599 -332
- package/dist/api/projects.js.map +1 -1
- package/dist/api/tasks.d.ts.map +1 -1
- package/dist/api/tasks.js +15 -40
- package/dist/api/tasks.js.map +1 -1
- package/dist/app.d.ts +0 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +0 -6
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -29
- package/dist/cli.js.map +1 -1
- package/dist/config/hot-reload.d.ts.map +1 -1
- package/dist/config/hot-reload.js +0 -4
- package/dist/config/hot-reload.js.map +1 -1
- package/dist/config/loader.d.ts +5 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +36 -47
- package/dist/config/loader.js.map +1 -1
- package/dist/config/normalizer.d.ts.map +1 -1
- package/dist/config/normalizer.js +0 -19
- package/dist/config/normalizer.js.map +1 -1
- package/dist/config/validator.d.ts +7 -4
- package/dist/config/validator.d.ts.map +1 -1
- package/dist/config/validator.js +56 -270
- package/dist/config/validator.js.map +1 -1
- package/dist/event/handlers.d.ts.map +1 -1
- package/dist/event/handlers.js +356 -234
- package/dist/event/handlers.js.map +1 -1
- package/dist/event/log.js +0 -2
- package/dist/event/log.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +1 -1
- package/dist/platform/body-digest.d.ts.map +1 -1
- package/dist/platform/body-digest.js +0 -4
- package/dist/platform/body-digest.js.map +1 -1
- package/dist/platform/command-renderer.d.ts +6 -0
- package/dist/platform/command-renderer.d.ts.map +1 -1
- package/dist/platform/command-renderer.js +20 -3
- package/dist/platform/command-renderer.js.map +1 -1
- package/dist/platform/comment-cursor.d.ts.map +1 -1
- package/dist/platform/comment-cursor.js +0 -21
- package/dist/platform/comment-cursor.js.map +1 -1
- package/dist/platform/driver-host.d.ts.map +1 -1
- package/dist/platform/driver-host.js +3 -1
- package/dist/platform/driver-host.js.map +1 -1
- package/dist/platform/driver-spec.d.ts.map +1 -1
- package/dist/platform/driver-spec.js +33 -46
- package/dist/platform/driver-spec.js.map +1 -1
- package/dist/platform/field-mapper.d.ts.map +1 -1
- package/dist/platform/field-mapper.js +3 -11
- package/dist/platform/field-mapper.js.map +1 -1
- package/dist/platform/git-driver.d.ts +2 -0
- package/dist/platform/git-driver.d.ts.map +1 -1
- package/dist/platform/git-driver.js +9 -23
- package/dist/platform/git-driver.js.map +1 -1
- package/dist/platform/manifest.d.ts.map +1 -1
- package/dist/platform/manifest.js +0 -3
- package/dist/platform/manifest.js.map +1 -1
- package/dist/platform/markers.d.ts.map +1 -1
- package/dist/platform/markers.js +0 -19
- package/dist/platform/markers.js.map +1 -1
- package/dist/platform/platform-poller.d.ts +0 -1
- package/dist/platform/platform-poller.d.ts.map +1 -1
- package/dist/platform/platform-poller.js +0 -49
- package/dist/platform/platform-poller.js.map +1 -1
- package/dist/platform/plugin-registry.d.ts.map +1 -1
- package/dist/platform/plugin-registry.js +1 -12
- package/dist/platform/plugin-registry.js.map +1 -1
- package/dist/platform/plugins/github/driver.json +5 -0
- package/dist/platform/plugins/github/skills/baxian-cli-gh/SKILL.md +20 -14
- package/dist/platform/pr-conversation-cache.d.ts.map +1 -1
- package/dist/platform/pr-conversation-cache.js +0 -17
- package/dist/platform/pr-conversation-cache.js.map +1 -1
- package/dist/platform/preflight-exec.d.ts.map +1 -1
- package/dist/platform/preflight-exec.js +0 -9
- package/dist/platform/preflight-exec.js.map +1 -1
- package/dist/platform/response-parser.d.ts.map +1 -1
- package/dist/platform/response-parser.js +0 -4
- package/dist/platform/response-parser.js.map +1 -1
- package/dist/platform/review-timeline.d.ts.map +1 -1
- package/dist/platform/review-timeline.js +0 -22
- package/dist/platform/review-timeline.js.map +1 -1
- package/dist/platform/row-schema.d.ts.map +1 -1
- package/dist/platform/row-schema.js +0 -11
- package/dist/platform/row-schema.js.map +1 -1
- package/dist/platform/startup.d.ts.map +1 -1
- package/dist/platform/startup.js +6 -17
- package/dist/platform/startup.js.map +1 -1
- package/dist/platform/types.d.ts +1 -0
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/platform/types.js +3 -8
- package/dist/platform/types.js.map +1 -1
- package/dist/platform/verdict-engine.d.ts.map +1 -1
- package/dist/platform/verdict-engine.js +0 -15
- package/dist/platform/verdict-engine.js.map +1 -1
- package/dist/shared/concurrency.d.ts.map +1 -1
- package/dist/shared/concurrency.js +0 -2
- package/dist/shared/concurrency.js.map +1 -1
- package/dist/shared/constants.d.ts +1 -8
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +2 -32
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/git-url.d.ts.map +1 -1
- package/dist/shared/git-url.js +0 -9
- package/dist/shared/git-url.js.map +1 -1
- package/dist/shared/types.d.ts +28 -135
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +7 -6
- package/dist/shared/types.js.map +1 -1
- package/dist/skill/registry.d.ts.map +1 -1
- package/dist/skill/registry.js +0 -5
- package/dist/skill/registry.js.map +1 -1
- package/dist/skills/baxian-pr-feedback/SKILL.md +5 -3
- package/dist/skills/baxian-pr-recheck/SKILL.md +4 -0
- package/dist/skills/baxian-pr-review/SKILL.md +4 -0
- package/dist/skills/baxian-signals/SKILL.md +15 -38
- package/dist/skills/baxian-task-check/SKILL.md +13 -14
- package/dist/state/agent-store.d.ts.map +1 -1
- package/dist/state/agent-store.js +0 -6
- 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 +0 -5
- package/dist/state/error-record-store.js.map +1 -1
- package/dist/state/init.d.ts.map +1 -1
- package/dist/state/init.js +0 -1
- package/dist/state/init.js.map +1 -1
- package/dist/state/lock.d.ts.map +1 -1
- package/dist/state/lock.js +0 -1
- package/dist/state/lock.js.map +1 -1
- package/dist/state/snapshot.d.ts.map +1 -1
- package/dist/state/snapshot.js +5 -2
- package/dist/state/snapshot.js.map +1 -1
- package/dist/state/task-store.d.ts.map +1 -1
- package/dist/state/task-store.js +56 -143
- package/dist/state/task-store.js.map +1 -1
- package/dist/terminal/attach.d.ts.map +1 -1
- package/dist/terminal/attach.js +0 -4
- package/dist/terminal/attach.js.map +1 -1
- package/dist/terminal/stream-ws.js +0 -5
- package/dist/terminal/stream-ws.js.map +1 -1
- package/dist/web/assets/index-CiAnjs1_.js +15 -0
- package/dist/web/assets/index-yqxawAPJ.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/agent/review-transport.d.ts +0 -92
- package/dist/agent/review-transport.d.ts.map +0 -1
- package/dist/agent/review-transport.js +0 -512
- package/dist/agent/review-transport.js.map +0 -1
- package/dist/agent/root-agent-runtime.d.ts +0 -80
- package/dist/agent/root-agent-runtime.d.ts.map +0 -1
- package/dist/agent/root-agent-runtime.js +0 -635
- package/dist/agent/root-agent-runtime.js.map +0 -1
- package/dist/agent/root-recovery-coordinator.d.ts +0 -125
- package/dist/agent/root-recovery-coordinator.d.ts.map +0 -1
- package/dist/agent/root-recovery-coordinator.js +0 -1579
- package/dist/agent/root-recovery-coordinator.js.map +0 -1
- package/dist/event/server-handlers.d.ts +0 -4
- package/dist/event/server-handlers.d.ts.map +0 -1
- package/dist/event/server-handlers.js +0 -1294
- package/dist/event/server-handlers.js.map +0 -1
- package/dist/skills/baxian-research/SKILL.md +0 -28
- package/dist/skills/baxian-server-feedback/SKILL.md +0 -40
- package/dist/skills/baxian-server-feedback/agents/openai.yaml +0 -2
- package/dist/skills/baxian-server-review/SKILL.md +0 -58
- package/dist/skills/baxian-server-review/agents/openai.yaml +0 -2
- package/dist/state/review-store.d.ts +0 -33
- package/dist/state/review-store.d.ts.map +0 -1
- package/dist/state/review-store.js +0 -272
- package/dist/state/review-store.js.map +0 -1
- package/dist/state/root-recovery-store.d.ts +0 -107
- package/dist/state/root-recovery-store.d.ts.map +0 -1
- package/dist/state/root-recovery-store.js +0 -400
- package/dist/state/root-recovery-store.js.map +0 -1
- package/dist/web/assets/index-BPv6vv3U.css +0 -1
- package/dist/web/assets/index-BXfgaFNm.js +0 -18
package/dist/event/handlers.js
CHANGED
|
@@ -3,15 +3,21 @@ import { BODY_DIGEST_SOURCE } from '../platform/body-digest.js';
|
|
|
3
3
|
import { ackRevisionKey } from '../platform/markers.js';
|
|
4
4
|
import { LINE_SAFE_ID_RE } from '../platform/row-schema.js';
|
|
5
5
|
import { SHA_HEX_SOURCE, SOURCE_KEY_PATTERN } from '../platform/types.js';
|
|
6
|
-
import { BRANCH_PREFIX,
|
|
6
|
+
import { BRANCH_PREFIX, effectiveTaskReviewRound, isRecord, isValidBranchName, taskGenerationGuard, taskReviewRound, } from '../shared/index.js';
|
|
7
7
|
import { DispatchTerminalError, isRecoverableQaDispatchHold, } from '../agent/manager.js';
|
|
8
|
-
import { ReplNotReadyError } from '../agent/tmux.js';
|
|
9
|
-
import { DirtyWorkdirError } from '../agent/branch.js';
|
|
10
8
|
const HEAD_SHA_RE = new RegExp(`^${SHA_HEX_SOURCE}$`);
|
|
11
9
|
const BODY_DIGEST_RE = new RegExp(`^${BODY_DIGEST_SOURCE}$`);
|
|
12
10
|
const PR_UPDATED_REVIEW_FROM_STATUSES = [
|
|
13
|
-
'in_progress', 'fixing', 'review', 'approved', 'merge-ready',
|
|
11
|
+
'in_progress', 'fixing', 'review', 'spec-ready', 'approved', 'merge-ready',
|
|
14
12
|
];
|
|
13
|
+
const PREMATURE_MERGE_BLOCKABLE_STATUSES = [
|
|
14
|
+
'pending', 'in_progress', 'review', 'fixing', 'spec-ready', 'approved', 'max_rounds',
|
|
15
|
+
];
|
|
16
|
+
function requireTaskQaAgentId(task, operation) {
|
|
17
|
+
if (!task.qaAgentId)
|
|
18
|
+
throw new Error(`${operation}: task ${task.id} has no QA participant`);
|
|
19
|
+
return task.qaAgentId;
|
|
20
|
+
}
|
|
15
21
|
function validHeadSha(value) {
|
|
16
22
|
return typeof value === 'string' && HEAD_SHA_RE.test(value) ? value : undefined;
|
|
17
23
|
}
|
|
@@ -96,11 +102,9 @@ function parseGitFeedbackRevision(value) {
|
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
async function reArmDevelopWatcher(manager, task, agentId, opts = {}) {
|
|
99
|
-
const kinds = task.phase === '
|
|
100
|
-
? ['
|
|
101
|
-
:
|
|
102
|
-
? ['pr-created']
|
|
103
|
-
: ['spec-done', 'pr-created'];
|
|
105
|
+
const kinds = task.phase === 'code'
|
|
106
|
+
? ['pr-created']
|
|
107
|
+
: ['spec-done', 'pr-created'];
|
|
104
108
|
await manager.setupPhaseSignal(task.id, agentId, kinds, { skipSnapshot: opts.skipSnapshot ?? true });
|
|
105
109
|
}
|
|
106
110
|
async function emitIntervention(bus, projectId, agentId, taskId, data) {
|
|
@@ -120,9 +124,49 @@ async function emitIntervention(bus, projectId, agentId, taskId, data) {
|
|
|
120
124
|
console.warn(`[EventHandler] human.intervention emit failed (phase=${data.phase}):`, emitErr);
|
|
121
125
|
}
|
|
122
126
|
}
|
|
127
|
+
async function cleanupPrematureMergeParticipants(bus, manager, task) {
|
|
128
|
+
const participantIds = [...new Set([task.agentId, task.qaAgentId]
|
|
129
|
+
.filter((id) => typeof id === 'string' && id !== ''))];
|
|
130
|
+
const errors = [];
|
|
131
|
+
try {
|
|
132
|
+
await manager.cancelTask(task.id);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
136
|
+
}
|
|
137
|
+
const remainingParticipants = new Set();
|
|
138
|
+
for (const participantId of participantIds) {
|
|
139
|
+
try {
|
|
140
|
+
if ((await manager.getAgentState(participantId))?.taskId === task.id) {
|
|
141
|
+
remainingParticipants.add(participantId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
remainingParticipants.add(participantId);
|
|
146
|
+
errors.push(`${participantId}: binding probe failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
if (await manager.getAgentLockOwner(participantId) === task.id) {
|
|
150
|
+
remainingParticipants.add(participantId);
|
|
151
|
+
errors.push(`${participantId}: exclusive lock still owned by task ${task.id}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
remainingParticipants.add(participantId);
|
|
156
|
+
errors.push(`${participantId}: lock probe failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (errors.length === 0 && remainingParticipants.size === 0)
|
|
160
|
+
return;
|
|
161
|
+
await emitIntervention(bus, task.projectId, task.agentId, task.id, {
|
|
162
|
+
phase: 'premature-merge-participant-cleanup-failed',
|
|
163
|
+
participants: participantIds,
|
|
164
|
+
remainingParticipants: [...remainingParticipants],
|
|
165
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
123
168
|
const POST_APPROVE_REDISPATCH_CAP = 10;
|
|
124
169
|
const POST_APPROVE_RECOVERY_MIN_AGE_MS = 60_000;
|
|
125
|
-
// 损坏值不可自愈,每周期 sweep 会重踩同一分支:按 task+值去重,值变化或修复后允许重报
|
|
126
170
|
const unparseableUpdatedAtWarned = new Map();
|
|
127
171
|
const VERDICT_FRESHNESS_SKEW_MS = 5000;
|
|
128
172
|
function postApproveEpisodeKey(task) {
|
|
@@ -144,7 +188,7 @@ async function dispatchGitDevPostApproveCheck(bus, manager, snapshot, approvedHe
|
|
|
144
188
|
return;
|
|
145
189
|
}
|
|
146
190
|
const live = await manager.getTask(snapshot.id);
|
|
147
|
-
if (!live || live.status !== 'approved'
|
|
191
|
+
if (!live || live.status !== 'approved')
|
|
148
192
|
return;
|
|
149
193
|
let key = postApproveEpisodeKey(live);
|
|
150
194
|
if (!key || key.headSha !== approvedHeadSha || live.postApprovePhase === undefined) {
|
|
@@ -222,7 +266,7 @@ async function dispatchGitDevPostApproveCheck(bus, manager, snapshot, approvedHe
|
|
|
222
266
|
async function handlePrMergeReady(bus, manager, event) {
|
|
223
267
|
const taskId = event.taskId;
|
|
224
268
|
const taskNow = await manager.getTask(taskId);
|
|
225
|
-
if (!taskNow
|
|
269
|
+
if (!taskNow)
|
|
226
270
|
return;
|
|
227
271
|
const eventPrNumber = event.data.prNumber;
|
|
228
272
|
const eventPrUrl = event.data.prUrl;
|
|
@@ -260,7 +304,6 @@ async function handlePrMergeReady(bus, manager, event) {
|
|
|
260
304
|
]);
|
|
261
305
|
if (!freshTask
|
|
262
306
|
|| freshTask.status !== 'approved'
|
|
263
|
-
|| freshTask.reviewMode !== 'git'
|
|
264
307
|
|| freshTask.agentId !== taskNow.agentId
|
|
265
308
|
|| freshTask.postApproveRevoked !== undefined
|
|
266
309
|
|| entryCompletion?.token !== signalToken) {
|
|
@@ -308,14 +351,15 @@ async function handlePrMergeReady(bus, manager, event) {
|
|
|
308
351
|
patch: { latestHeadSha: gitEpisodeKey.headSha },
|
|
309
352
|
});
|
|
310
353
|
if (begun?.task.reviewDispatch) {
|
|
354
|
+
const qaAgentId = requireTaskQaAgentId(begun.task, 'handlePostApproveMergeReady');
|
|
311
355
|
try {
|
|
312
356
|
await manager.dispatchGitReviewLease(freshTask.id, {
|
|
313
357
|
expectedGeneration: begun.task.reviewDispatch.generation,
|
|
314
358
|
});
|
|
315
359
|
}
|
|
316
360
|
catch (err) {
|
|
317
|
-
if (err instanceof DispatchTerminalError
|
|
318
|
-
await manager.failTaskForDispatchError(freshTask.id, 'recheck',
|
|
361
|
+
if (err instanceof DispatchTerminalError) {
|
|
362
|
+
await manager.failTaskForDispatchError(freshTask.id, 'recheck', qaAgentId, err, { expectedReviewDispatch: begun.task.reviewDispatch });
|
|
319
363
|
return;
|
|
320
364
|
}
|
|
321
365
|
await emitIntervention(bus, freshTask.projectId, freshTask.agentId, freshTask.id, {
|
|
@@ -347,7 +391,6 @@ async function handlePrMergeReady(bus, manager, event) {
|
|
|
347
391
|
expectedHeadSha: gitEpisodeKey.headSha,
|
|
348
392
|
});
|
|
349
393
|
if (!revoked) {
|
|
350
|
-
// The pass rotated after the entry re-validation; this merge-ready and its cap decision are stale.
|
|
351
394
|
await emitIntervention(bus, taskNow.projectId, taskNow.agentId, taskNow.id, {
|
|
352
395
|
phase: 'post-approve-merge-skipped-stale-task',
|
|
353
396
|
});
|
|
@@ -382,7 +425,7 @@ async function handlePrMergeReady(bus, manager, event) {
|
|
|
382
425
|
async function handlePrFeedback(bus, manager, event) {
|
|
383
426
|
const taskId = event.taskId;
|
|
384
427
|
const taskNow = await manager.getTask(taskId);
|
|
385
|
-
if (!taskNow
|
|
428
|
+
if (!taskNow)
|
|
386
429
|
return;
|
|
387
430
|
const revision = parseGitFeedbackRevision(event.data.revision);
|
|
388
431
|
if (!revision) {
|
|
@@ -447,7 +490,7 @@ export async function recoverGitPostApprovePending(bus, manager) {
|
|
|
447
490
|
async function recoverOneGitPendingTask(bus, manager, task) {
|
|
448
491
|
{
|
|
449
492
|
const live = await manager.getTask(task.id);
|
|
450
|
-
if (!live || live.
|
|
493
|
+
if (!live || live.status !== 'approved' || live.pendingRedispatch !== true)
|
|
451
494
|
return;
|
|
452
495
|
if (live.postApproveRevoked)
|
|
453
496
|
return;
|
|
@@ -516,8 +559,19 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
516
559
|
const eventKind = event.data.kind;
|
|
517
560
|
const eventHeadSha = validHeadSha(event.data.headSha);
|
|
518
561
|
const task = await manager.getTask(taskId);
|
|
519
|
-
if (!task
|
|
562
|
+
if (!task)
|
|
563
|
+
return;
|
|
564
|
+
if (task.status === 'in_progress'
|
|
565
|
+
&& (task.phase === undefined || task.deliveryConfirmation?.phase !== task.phase)) {
|
|
566
|
+
if (eventPrNumber !== undefined && eventHeadSha !== undefined) {
|
|
567
|
+
await manager.recordGitPrObservation(taskId, {
|
|
568
|
+
prNumber: eventPrNumber,
|
|
569
|
+
...(eventPrUrl !== undefined ? { prUrl: eventPrUrl } : {}),
|
|
570
|
+
headSha: eventHeadSha,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
520
573
|
return;
|
|
574
|
+
}
|
|
521
575
|
if (eventKind === 'push' && eventHeadSha
|
|
522
576
|
&& event.data.source !== 'pr-fixed'
|
|
523
577
|
&& task.reviewDispatch === undefined
|
|
@@ -529,6 +583,16 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
529
583
|
console.warn(`[EventHandler] pr.updated: task ${taskId} in_progress but neither task nor event has prNumber; deferring catch-up`);
|
|
530
584
|
return;
|
|
531
585
|
}
|
|
586
|
+
if (task.status === 'max_rounds') {
|
|
587
|
+
if (eventPrNumber !== undefined && eventHeadSha !== undefined) {
|
|
588
|
+
await manager.recordGitPrObservation(taskId, {
|
|
589
|
+
prNumber: eventPrNumber,
|
|
590
|
+
...(eventPrUrl !== undefined ? { prUrl: eventPrUrl } : {}),
|
|
591
|
+
headSha: eventHeadSha,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
532
596
|
if (!PR_UPDATED_REVIEW_FROM_STATUSES.includes(task.status))
|
|
533
597
|
return;
|
|
534
598
|
let reviewHead = eventHeadSha ?? validHeadSha(task.latestHeadSha);
|
|
@@ -545,6 +609,7 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
545
609
|
return;
|
|
546
610
|
}
|
|
547
611
|
const bumpRound = task.status === 'in_progress'
|
|
612
|
+
|| task.status === 'spec-ready'
|
|
548
613
|
|| task.status === 'approved'
|
|
549
614
|
|| task.status === 'merge-ready'
|
|
550
615
|
|| (task.status === 'review' && task.reviewRoundPending === true);
|
|
@@ -573,6 +638,7 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
573
638
|
return;
|
|
574
639
|
}
|
|
575
640
|
const qaPhase = begun.task.reviewDispatch.qaPhase;
|
|
641
|
+
const qaAgentId = requireTaskQaAgentId(begun.task, 'handlePrUpdated');
|
|
576
642
|
try {
|
|
577
643
|
await manager.dispatchGitReviewLease(taskId, {
|
|
578
644
|
expectedGeneration: begun.task.reviewDispatch.generation,
|
|
@@ -587,8 +653,8 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
587
653
|
error: err.message,
|
|
588
654
|
});
|
|
589
655
|
}
|
|
590
|
-
else if (err instanceof DispatchTerminalError
|
|
591
|
-
await manager.failTaskForDispatchError(taskId, qaPhase,
|
|
656
|
+
else if (err instanceof DispatchTerminalError) {
|
|
657
|
+
await manager.failTaskForDispatchError(taskId, qaPhase, qaAgentId, err, {
|
|
592
658
|
expectedReviewDispatch: begun.task.reviewDispatch,
|
|
593
659
|
});
|
|
594
660
|
}
|
|
@@ -604,28 +670,22 @@ async function handlePrCodePush(bus, manager, event) {
|
|
|
604
670
|
async function redispatchReviewForStaleVerdict(manager, task) {
|
|
605
671
|
if (task.status !== 'review')
|
|
606
672
|
return false;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
try {
|
|
612
|
-
qaState = await manager.getAgentState(task.qaAgentId);
|
|
613
|
-
}
|
|
614
|
-
catch (err) {
|
|
615
|
-
// 无状态证据时 fail closed:不自动重派,让调用方落 stale-verdict 兜底介入
|
|
616
|
-
console.warn(`[EventHandler] stale-verdict QA state read failed for ${task.qaAgentId}; not redispatching:`, err);
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
if (qaState?.taskId === task.id && !isRecoverableQaDispatchHold(qaState))
|
|
620
|
-
return false;
|
|
673
|
+
const qaAgentId = requireTaskQaAgentId(task, 'redispatchReviewForStaleVerdict');
|
|
674
|
+
let qaState;
|
|
675
|
+
try {
|
|
676
|
+
qaState = await manager.getAgentState(qaAgentId);
|
|
621
677
|
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
console.warn(`[EventHandler] stale-verdict QA state read failed for ${qaAgentId}; not redispatching:`, err);
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
if (qaState?.taskId === task.id && !isRecoverableQaDispatchHold(qaState))
|
|
683
|
+
return false;
|
|
622
684
|
try {
|
|
623
|
-
// fromStatus/expectSignalToken 在 dispatch 锁内复核:本地快照到这里的窗口里任务可能已离开 review
|
|
624
|
-
// 或被并发 dispatcher 换代 pass,不能靠上面的预检
|
|
625
685
|
const entry = manager.getPendingDispatchRetry(task.id);
|
|
626
686
|
const qaPhase = entry?.kind === 'qa-recheck' && entry.signalToken === task.signalToken && entry.qaPhase !== undefined
|
|
627
687
|
? entry.qaPhase
|
|
628
|
-
: (task
|
|
688
|
+
: (taskReviewRound(task) === 0 ? 'review' : undefined);
|
|
629
689
|
await manager.dispatchReviewToQa(task.id, {
|
|
630
690
|
fromStatus: ['review'],
|
|
631
691
|
bumpRound: task.reviewRoundPending === true,
|
|
@@ -653,6 +713,7 @@ async function handleReviewApproval(bus, manager, task, reviewedHeadSha, current
|
|
|
653
713
|
}
|
|
654
714
|
if (task.failToken === undefined || task.signalToken === undefined)
|
|
655
715
|
return;
|
|
716
|
+
const expectedSignalToken = task.signalToken;
|
|
656
717
|
const provenance = {
|
|
657
718
|
sourceKey: gitVerdict.carrier.sourceKey,
|
|
658
719
|
id: gitVerdict.carrier.id,
|
|
@@ -661,9 +722,67 @@ async function handleReviewApproval(bus, manager, task, reviewedHeadSha, current
|
|
|
661
722
|
failToken: task.failToken,
|
|
662
723
|
anchorSha: reviewedHeadSha,
|
|
663
724
|
};
|
|
664
|
-
const reviewedRound =
|
|
725
|
+
const reviewedRound = effectiveTaskReviewRound(task);
|
|
726
|
+
if (task.phase === 'spec') {
|
|
727
|
+
const humanApproval = manager.getProjectConfig(task.projectId)?.specApproval === 'human';
|
|
728
|
+
if (!humanApproval) {
|
|
729
|
+
let verified;
|
|
730
|
+
try {
|
|
731
|
+
verified = await manager.platformVerifyPrBinding(task.id, gitVerdict.prNumber);
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
await emitIntervention(bus, task.projectId, task.agentId, task.id, {
|
|
735
|
+
phase: 'git-spec-auto-approve-head-verify-failed',
|
|
736
|
+
error: err instanceof Error ? err.message : String(err),
|
|
737
|
+
});
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
if (!verified.ok) {
|
|
741
|
+
await emitIntervention(bus, task.projectId, task.agentId, task.id, {
|
|
742
|
+
phase: 'git-spec-auto-approve-binding-invalid',
|
|
743
|
+
reason: verified.reason,
|
|
744
|
+
});
|
|
745
|
+
throw new Error(`automatic spec approval binding is invalid: ${verified.reason}`);
|
|
746
|
+
}
|
|
747
|
+
if (verified.headSha.toLowerCase() !== reviewedHeadSha.toLowerCase()) {
|
|
748
|
+
await emitIntervention(bus, task.projectId, task.agentId, task.id, {
|
|
749
|
+
phase: 'git-spec-auto-approve-head-mismatch',
|
|
750
|
+
reviewedHeadSha,
|
|
751
|
+
currentHeadSha: verified.headSha,
|
|
752
|
+
source: 'platform-live-read',
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const expectedTask = taskGenerationGuard(task);
|
|
758
|
+
const approval = {
|
|
759
|
+
specReviewRound: reviewedRound,
|
|
760
|
+
expectedSignalToken,
|
|
761
|
+
...(prPatch.prNumber !== undefined ? { prNumber: prPatch.prNumber } : {}),
|
|
762
|
+
...(prPatch.prUrl !== undefined ? { prUrl: prPatch.prUrl } : {}),
|
|
763
|
+
headSha: reviewedHeadSha,
|
|
764
|
+
passProvenance: provenance,
|
|
765
|
+
};
|
|
766
|
+
const transitioned = humanApproval
|
|
767
|
+
? await manager.parkTaskAtSpecReady(task.id, {
|
|
768
|
+
specReviewRound: reviewedRound,
|
|
769
|
+
expectedTask,
|
|
770
|
+
expectedSignalToken,
|
|
771
|
+
...(prPatch.prNumber !== undefined ? { prNumber: prPatch.prNumber } : {}),
|
|
772
|
+
...(prPatch.prUrl !== undefined ? { prUrl: prPatch.prUrl } : {}),
|
|
773
|
+
headSha: reviewedHeadSha,
|
|
774
|
+
passProvenance: provenance,
|
|
775
|
+
})
|
|
776
|
+
: await manager.transitionToCodePhase(task.id, expectedTask, approval);
|
|
777
|
+
if (!transitioned) {
|
|
778
|
+
await emitIntervention(bus, task.projectId, task.agentId, task.id, {
|
|
779
|
+
phase: 'git-spec-approve-transition-failed',
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
665
784
|
const transitioned = await manager.approveGitReviewPass(task.id, {
|
|
666
|
-
expectedSignalToken
|
|
785
|
+
expectedSignalToken,
|
|
667
786
|
headSha: reviewedHeadSha,
|
|
668
787
|
reviewRound: reviewedRound,
|
|
669
788
|
...(prPatch.prNumber !== undefined ? { prNumber: prPatch.prNumber } : {}),
|
|
@@ -673,10 +792,9 @@ async function handleReviewApproval(bus, manager, task, reviewedHeadSha, current
|
|
|
673
792
|
if (!transitioned)
|
|
674
793
|
return;
|
|
675
794
|
manager.stopPhaseSignalWatcher(transitioned.id);
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
795
|
+
const qaAgentId = requireTaskQaAgentId(transitioned, 'handleReviewApproval');
|
|
796
|
+
await manager.releaseAgentForTask(qaAgentId, transitioned.id, 'idle', { allowAwaitingHuman: true })
|
|
797
|
+
.catch(err => console.error(`[EventHandler] APPROVE releaseAgentForTask(QA=${qaAgentId}) failed:`, err));
|
|
680
798
|
await dispatchGitDevPostApproveCheck(bus, manager, transitioned, reviewedHeadSha);
|
|
681
799
|
}
|
|
682
800
|
function gitHeadPatch(task, headSha) {
|
|
@@ -694,9 +812,10 @@ async function handleReviewRequestChanges(bus, manager, task, reviewedHeadSha, c
|
|
|
694
812
|
});
|
|
695
813
|
return;
|
|
696
814
|
}
|
|
697
|
-
const reviewedRound =
|
|
815
|
+
const reviewedRound = effectiveTaskReviewRound(task);
|
|
698
816
|
const nextRound = reviewedRound + 1;
|
|
699
|
-
|
|
817
|
+
const cap = manager.getConfig().review.rounds + (task.maxRoundsContinues ?? 0);
|
|
818
|
+
if (nextRound > cap) {
|
|
700
819
|
return handleMaxRounds(bus, manager, task, prPatch, reviewedRound);
|
|
701
820
|
}
|
|
702
821
|
const result = await manager.transitionTaskStatus(task.id, 'fixing', {
|
|
@@ -705,7 +824,9 @@ async function handleReviewRequestChanges(bus, manager, task, reviewedHeadSha, c
|
|
|
705
824
|
}, {
|
|
706
825
|
...prPatch,
|
|
707
826
|
...gitHeadPatch(task, currentHeadSha),
|
|
708
|
-
|
|
827
|
+
...(task.phase === 'spec'
|
|
828
|
+
? { specReviewRound: nextRound }
|
|
829
|
+
: { reviewRound: nextRound }),
|
|
709
830
|
reviewRoundPending: undefined,
|
|
710
831
|
reviewDispatch: undefined,
|
|
711
832
|
fixDispatchedAt: new Date().toISOString(),
|
|
@@ -715,80 +836,7 @@ async function handleReviewRequestChanges(bus, manager, task, reviewedHeadSha, c
|
|
|
715
836
|
const { task: transitioned } = result;
|
|
716
837
|
await manager.clearPostApproveCompletion(transitioned.id);
|
|
717
838
|
manager.stopPhaseSignalWatcher(transitioned.id);
|
|
718
|
-
|
|
719
|
-
const qaReleased = await manager
|
|
720
|
-
.releaseAgentForTask(transitioned.qaAgentId, transitioned.id, 'idle', { allowAwaitingHuman: true })
|
|
721
|
-
.catch(err => {
|
|
722
|
-
console.error(`[EventHandler] REQUEST_CHANGES releaseAgentForTask(QA=${transitioned.qaAgentId}) failed:`, err);
|
|
723
|
-
return false;
|
|
724
|
-
});
|
|
725
|
-
if (!qaReleased) {
|
|
726
|
-
await emitIntervention(bus, transitioned.projectId, transitioned.agentId, transitioned.id, {
|
|
727
|
-
phase: 'qa-release-failed-but-dev-dispatched',
|
|
728
|
-
qaAgentId: transitioned.qaAgentId,
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
const acquired = await manager.acquireAgentForTask(transitioned.agentId, transitioned.id, 'fix');
|
|
733
|
-
if (!acquired) {
|
|
734
|
-
await emitIntervention(bus, transitioned.projectId, transitioned.agentId, transitioned.id, {
|
|
735
|
-
phase: 'dev-acquire-failed-fix',
|
|
736
|
-
devAgentId: transitioned.agentId,
|
|
737
|
-
});
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
const { token: fixToken, armed } = await manager.rotateAndSetupPhaseSignal(transitioned.id, transitioned.agentId, 'pr-fixed', { expectedStatus: 'fixing', expectedToken: transitioned.signalToken });
|
|
741
|
-
if (!armed) {
|
|
742
|
-
console.warn(`[EventHandler] REQUEST_CHANGES pr-fixed watcher failed to arm for task=${transitioned.id}; holding dev (not dispatching fix)`);
|
|
743
|
-
await manager.markAwaitingHuman(transitioned.agentId, 'signal-arm-failed:pr-fixed', 'pr-fixed watcher failed to arm; the fix was not dispatched (its completion signal would have no consumer). Cancel the task or delete the agent to retry.', { expectedTaskId: transitioned.id });
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
let resumed = false;
|
|
747
|
-
let dispatchErr = null;
|
|
748
|
-
try {
|
|
749
|
-
resumed = await manager.continueSession(transitioned.id, transitioned.agentId, 'fix', {
|
|
750
|
-
signalToken: fixToken,
|
|
751
|
-
guardBeforeInject: async () => {
|
|
752
|
-
const freshTask = await manager.getTask(transitioned.id);
|
|
753
|
-
return freshTask?.status === 'fixing' && freshTask.signalToken === fixToken;
|
|
754
|
-
},
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
catch (err) {
|
|
758
|
-
dispatchErr = err;
|
|
759
|
-
console.error(`[EventHandler] REQUEST_CHANGES continueSession(dev=${transitioned.agentId}, fix) failed:`, err);
|
|
760
|
-
}
|
|
761
|
-
if (!resumed) {
|
|
762
|
-
console.warn(`[EventHandler] REQUEST_CHANGES dev=${transitioned.agentId} not resumable for task=${transitioned.id}; ` +
|
|
763
|
-
`task remains in 'fixing' but no dev session is attached`);
|
|
764
|
-
if (dispatchErr instanceof DispatchTerminalError) {
|
|
765
|
-
await manager.failTaskForDispatchError(transitioned.id, 'fix', transitioned.agentId, dispatchErr);
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
const parked = await manager.markAgentWaiting(transitioned.agentId, transitioned.id)
|
|
769
|
-
.catch(err => {
|
|
770
|
-
console.error(`[EventHandler] REQUEST_CHANGES markAgentWaiting(dev=${transitioned.agentId}) rollback failed:`, err);
|
|
771
|
-
return false;
|
|
772
|
-
});
|
|
773
|
-
if (parked && (dispatchErr instanceof ReplNotReadyError || dispatchErr instanceof DirtyWorkdirError)) {
|
|
774
|
-
// dev 回合在途(忙碌/未提交改动)是常态而非故障:登记待补派,回合结束后由对账 re-continue。
|
|
775
|
-
// 停驻失败说明绑定/锁已不受控,pending 无法证明还有可承接的 session,必须走人工告警
|
|
776
|
-
manager.registerPendingDispatchRetry(transitioned.id, {
|
|
777
|
-
kind: 'dev-fix',
|
|
778
|
-
agentId: transitioned.agentId,
|
|
779
|
-
signalToken: fixToken,
|
|
780
|
-
});
|
|
781
|
-
console.warn(`[EventHandler] REQUEST_CHANGES fix deferred for task=${transitioned.id}: dev pane busy or workdir dirty ` +
|
|
782
|
-
`(${dispatchErr.name}); reconciler will re-continue when idle`);
|
|
783
|
-
}
|
|
784
|
-
else {
|
|
785
|
-
await emitIntervention(bus, transitioned.projectId, transitioned.agentId, transitioned.id, {
|
|
786
|
-
phase: 'fix-resume-failed',
|
|
787
|
-
reviewRound: transitioned.reviewRound,
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
839
|
+
await manager.dispatchGitFixToDev(transitioned.id);
|
|
792
840
|
}
|
|
793
841
|
async function handleMaxRounds(bus, manager, task, prPatch, reviewedRound) {
|
|
794
842
|
const result = await manager.transitionTaskStatus(task.id, 'max_rounds', {
|
|
@@ -796,7 +844,9 @@ async function handleMaxRounds(bus, manager, task, prPatch, reviewedRound) {
|
|
|
796
844
|
expectSignalToken: task.signalToken,
|
|
797
845
|
}, {
|
|
798
846
|
...prPatch,
|
|
799
|
-
|
|
847
|
+
...(task.phase === 'spec'
|
|
848
|
+
? { specReviewRound: reviewedRound }
|
|
849
|
+
: { reviewRound: reviewedRound }),
|
|
800
850
|
reviewRoundPending: undefined,
|
|
801
851
|
reviewDispatch: undefined,
|
|
802
852
|
});
|
|
@@ -805,16 +855,15 @@ async function handleMaxRounds(bus, manager, task, prPatch, reviewedRound) {
|
|
|
805
855
|
const { task: transitioned } = result;
|
|
806
856
|
await manager.clearPostApproveCompletion(transitioned.id);
|
|
807
857
|
manager.stopPhaseSignalWatcher(transitioned.id);
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
858
|
+
const qaAgentId = requireTaskQaAgentId(transitioned, 'handleMaxRounds');
|
|
859
|
+
const qaState = await manager.getAgentState(qaAgentId);
|
|
860
|
+
if (qaState?.taskId === transitioned.id) {
|
|
861
|
+
await manager
|
|
862
|
+
.releaseAgentForTask(qaAgentId, transitioned.id, 'idle', { allowAwaitingHuman: true })
|
|
863
|
+
.catch(err => {
|
|
864
|
+
console.error(`[EventHandler] max_rounds releaseAgentForTask(QA=${qaAgentId}) failed:`, err);
|
|
865
|
+
return false;
|
|
866
|
+
});
|
|
818
867
|
}
|
|
819
868
|
try {
|
|
820
869
|
await bus.emit({
|
|
@@ -824,7 +873,10 @@ async function handleMaxRounds(bus, manager, task, prPatch, reviewedRound) {
|
|
|
824
873
|
projectId: transitioned.projectId,
|
|
825
874
|
agentId: transitioned.agentId,
|
|
826
875
|
taskId: transitioned.id,
|
|
827
|
-
data: {
|
|
876
|
+
data: {
|
|
877
|
+
phase: transitioned.phase,
|
|
878
|
+
reviewRound: taskReviewRound(transitioned),
|
|
879
|
+
},
|
|
828
880
|
});
|
|
829
881
|
}
|
|
830
882
|
catch (emitErr) {
|
|
@@ -832,37 +884,17 @@ async function handleMaxRounds(bus, manager, task, prPatch, reviewedRound) {
|
|
|
832
884
|
}
|
|
833
885
|
}
|
|
834
886
|
export function registerEventHandlers(bus, manager) {
|
|
835
|
-
|
|
887
|
+
const handleGitPrDelivery = async (event, deliveryPhase) => {
|
|
836
888
|
if (!event.taskId || !event.agentId)
|
|
837
889
|
return;
|
|
838
890
|
const taskAtEntry = await manager.getTask(event.taskId);
|
|
839
891
|
if (!taskAtEntry)
|
|
840
892
|
return;
|
|
841
|
-
if (taskAtEntry.reviewMode === 'server') {
|
|
842
|
-
const prNumber = event.data.prNumber;
|
|
843
|
-
if (!Number.isInteger(prNumber) || prNumber < 1)
|
|
844
|
-
return;
|
|
845
|
-
const adopted = await manager.adoptServerPr(event.taskId, {
|
|
846
|
-
prNumber: prNumber,
|
|
847
|
-
...(typeof event.data.prUrl === 'string' ? { prUrl: event.data.prUrl } : {}),
|
|
848
|
-
...(validHeadSha(event.data.headSha) !== undefined ? { headSha: validHeadSha(event.data.headSha) } : {}),
|
|
849
|
-
...(typeof event.data.targetBranch === 'string' ? { targetBranch: event.data.targetBranch } : {}),
|
|
850
|
-
});
|
|
851
|
-
if (!adopted) {
|
|
852
|
-
await emitIntervention(bus, taskAtEntry.projectId, taskAtEntry.agentId, taskAtEntry.id, {
|
|
853
|
-
phase: 'server-pr-adoption-refused',
|
|
854
|
-
prNumber,
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
if (isSpecStagePhase(taskAtEntry.phase)) {
|
|
860
|
-
console.warn(`[EventHandler] pr.created ignored for task ${event.taskId}: task in spec phase`);
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
893
|
if (event.data.source === 'pane-signal'
|
|
864
894
|
&& taskAtEntry.prNumber !== undefined && taskAtEntry.prNumber === event.data.prNumber
|
|
865
895
|
&& taskAtEntry.status !== 'in_progress') {
|
|
896
|
+
if (taskAtEntry.phase !== deliveryPhase)
|
|
897
|
+
return;
|
|
866
898
|
if (taskAtEntry.pendingPrSignalToken === undefined
|
|
867
899
|
|| event.data.token !== taskAtEntry.pendingPrSignalToken) {
|
|
868
900
|
return;
|
|
@@ -948,8 +980,8 @@ export function registerEventHandlers(bus, manager) {
|
|
|
948
980
|
return;
|
|
949
981
|
}
|
|
950
982
|
}
|
|
951
|
-
|
|
952
|
-
if (!task
|
|
983
|
+
let task = await manager.getTask(event.taskId);
|
|
984
|
+
if (!task)
|
|
953
985
|
return;
|
|
954
986
|
if (event.data.source !== 'pane-signal'
|
|
955
987
|
&& task.branch !== BRANCH_PREFIX + event.taskId
|
|
@@ -958,26 +990,9 @@ export function registerEventHandlers(bus, manager) {
|
|
|
958
990
|
`for task ${event.taskId} (untracked; custom-branch PRs adopt via the pane signal)`);
|
|
959
991
|
return;
|
|
960
992
|
}
|
|
961
|
-
const adoption = {};
|
|
962
993
|
const target = event.data.source === 'pane-signal'
|
|
963
994
|
? verifiedTargetBranch
|
|
964
995
|
: (typeof event.data.targetBranch === 'string' ? event.data.targetBranch : undefined);
|
|
965
|
-
if (target !== undefined && task.baseBranch === undefined)
|
|
966
|
-
adoption.baseBranch = target;
|
|
967
|
-
if (event.data.source === 'pane-signal') {
|
|
968
|
-
const actorId = typeof event.data.actorB64 === 'string'
|
|
969
|
-
? decodeSignalActorId(event.data.actorB64)
|
|
970
|
-
: undefined;
|
|
971
|
-
if (actorId !== undefined) {
|
|
972
|
-
adoption.replyActorId = actorId;
|
|
973
|
-
adoption.replyActorStatus = 'verified';
|
|
974
|
-
adoption.pendingPrSignalToken = undefined;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
else if (typeof event.data.prAuthorId === 'string' && task.replyActorStatus !== 'verified') {
|
|
978
|
-
adoption.replyActorId = event.data.prAuthorId;
|
|
979
|
-
adoption.replyActorStatus = 'provisional';
|
|
980
|
-
}
|
|
981
996
|
let reviewHead = validHeadSha(event.data.headSha) ?? paneVerifiedHeadSha;
|
|
982
997
|
if (reviewHead === undefined && typeof event.data.prNumber === 'number') {
|
|
983
998
|
const verified = await manager.platformVerifyPrBinding(event.taskId, event.data.prNumber);
|
|
@@ -990,6 +1005,54 @@ export function registerEventHandlers(bus, manager) {
|
|
|
990
1005
|
});
|
|
991
1006
|
return;
|
|
992
1007
|
}
|
|
1008
|
+
if (deliveryPhase === undefined) {
|
|
1009
|
+
if (typeof event.data.prNumber !== 'number')
|
|
1010
|
+
return;
|
|
1011
|
+
await manager.recordGitPrObservation(event.taskId, {
|
|
1012
|
+
prNumber: event.data.prNumber,
|
|
1013
|
+
...(typeof event.data.prUrl === 'string' ? { prUrl: event.data.prUrl } : {}),
|
|
1014
|
+
headSha: reviewHead,
|
|
1015
|
+
...(target !== undefined ? { targetBranch: target } : {}),
|
|
1016
|
+
...(typeof event.data.prAuthorId === 'string' ? { prAuthorId: event.data.prAuthorId } : {}),
|
|
1017
|
+
});
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const actorId = typeof event.data.actorB64 === 'string'
|
|
1021
|
+
? decodeSignalActorId(event.data.actorB64)
|
|
1022
|
+
: undefined;
|
|
1023
|
+
if (actorId === undefined || typeof event.data.prNumber !== 'number') {
|
|
1024
|
+
await reArmDevelopWatcher(manager, task, event.agentId);
|
|
1025
|
+
await emitIntervention(bus, task.projectId, event.agentId, event.taskId, {
|
|
1026
|
+
phase: 'git-pr-delivery-actor-missing',
|
|
1027
|
+
deliveryPhase,
|
|
1028
|
+
claimedPrNumber: event.data.prNumber,
|
|
1029
|
+
});
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const confirmed = await manager.confirmGitDelivery(event.taskId, {
|
|
1033
|
+
phase: deliveryPhase,
|
|
1034
|
+
source: 'signal',
|
|
1035
|
+
prNumber: event.data.prNumber,
|
|
1036
|
+
...(typeof event.data.prUrl === 'string' ? { prUrl: event.data.prUrl } : {}),
|
|
1037
|
+
headSha: reviewHead,
|
|
1038
|
+
...(target !== undefined ? { targetBranch: target } : {}),
|
|
1039
|
+
...(reconciledBranch !== undefined ? { branch: reconciledBranch } : {}),
|
|
1040
|
+
actorId,
|
|
1041
|
+
expectedSignalToken: typeof event.data.token === 'string' ? event.data.token : undefined,
|
|
1042
|
+
});
|
|
1043
|
+
if (!confirmed) {
|
|
1044
|
+
const current = await manager.getTask(event.taskId);
|
|
1045
|
+
if (current?.status === 'in_progress') {
|
|
1046
|
+
await reArmDevelopWatcher(manager, current, event.agentId);
|
|
1047
|
+
await emitIntervention(bus, current.projectId, event.agentId, event.taskId, {
|
|
1048
|
+
phase: 'pane-pr-delivery-transition-failed',
|
|
1049
|
+
deliveryPhase,
|
|
1050
|
+
claimedPrNumber: event.data.prNumber,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
task = confirmed;
|
|
993
1056
|
if (task.status !== 'in_progress' && task.status !== 'fixing')
|
|
994
1057
|
return;
|
|
995
1058
|
const begun = await manager.beginGitReviewPass(event.taskId, {
|
|
@@ -1000,11 +1063,7 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1000
1063
|
expectPhase: task.phase,
|
|
1001
1064
|
expectSignalToken: task.signalToken,
|
|
1002
1065
|
patch: {
|
|
1003
|
-
...(event.data.prNumber !== undefined ? { prNumber: event.data.prNumber } : {}),
|
|
1004
|
-
...(event.data.prUrl !== undefined ? { prUrl: event.data.prUrl } : {}),
|
|
1005
1066
|
latestHeadSha: reviewHead,
|
|
1006
|
-
...(reconciledBranch ? { branch: reconciledBranch } : {}),
|
|
1007
|
-
...adoption,
|
|
1008
1067
|
},
|
|
1009
1068
|
});
|
|
1010
1069
|
if (!begun?.task.reviewDispatch) {
|
|
@@ -1025,18 +1084,7 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1025
1084
|
if (reconciledBranch) {
|
|
1026
1085
|
console.log(`[EventHandler] pr.created reconciled branch for task ${event.taskId}: ${task.branch} → ${reconciledBranch}`);
|
|
1027
1086
|
}
|
|
1028
|
-
|
|
1029
|
-
const parked = await manager.markAgentWaiting(event.agentId, begun.task.id);
|
|
1030
|
-
if (!parked) {
|
|
1031
|
-
await emitIntervention(bus, begun.task.projectId, begun.task.agentId, begun.task.id, {
|
|
1032
|
-
phase: 'dev-wait-gate-failed-no-qa',
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
await emitIntervention(bus, begun.task.projectId, begun.task.agentId, begun.task.id, {
|
|
1036
|
-
phase: 'git-review-no-qa-partner',
|
|
1037
|
-
});
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1087
|
+
const qaAgentId = requireTaskQaAgentId(begun.task, 'pr.created');
|
|
1040
1088
|
try {
|
|
1041
1089
|
await manager.dispatchGitReviewLease(begun.task.id, {
|
|
1042
1090
|
expectedGeneration: begun.task.reviewDispatch.generation,
|
|
@@ -1052,7 +1100,7 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1052
1100
|
});
|
|
1053
1101
|
}
|
|
1054
1102
|
else if (err instanceof DispatchTerminalError) {
|
|
1055
|
-
await manager.failTaskForDispatchError(begun.task.id, 'review',
|
|
1103
|
+
await manager.failTaskForDispatchError(begun.task.id, 'review', qaAgentId, err, {
|
|
1056
1104
|
expectedReviewDispatch: begun.task.reviewDispatch,
|
|
1057
1105
|
});
|
|
1058
1106
|
}
|
|
@@ -1064,16 +1112,20 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1064
1112
|
});
|
|
1065
1113
|
}
|
|
1066
1114
|
}
|
|
1115
|
+
};
|
|
1116
|
+
bus.on('pr.created', async (event) => {
|
|
1117
|
+
await handleGitPrDelivery(event, event.data.source === 'pane-signal' ? 'code' : undefined);
|
|
1118
|
+
});
|
|
1119
|
+
bus.on('spec.ready', async (event) => {
|
|
1120
|
+
const task = event.taskId ? await manager.getTask(event.taskId) : null;
|
|
1121
|
+
if (!task)
|
|
1122
|
+
return;
|
|
1123
|
+
await handleGitPrDelivery(event, 'spec');
|
|
1067
1124
|
});
|
|
1068
1125
|
bus.on('pr.updated', async (event) => {
|
|
1069
1126
|
if (!event.taskId)
|
|
1070
1127
|
return;
|
|
1071
|
-
const taskAtEntry = await manager.getTask(event.taskId);
|
|
1072
1128
|
const eventKind = event.data.kind;
|
|
1073
|
-
if (taskAtEntry && isSpecStagePhase(taskAtEntry.phase)) {
|
|
1074
|
-
console.warn(`[EventHandler] pr.updated (kind=${eventKind ?? 'push'}) ignored for task ${event.taskId}: task in spec phase`);
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
1129
|
if (eventKind === 'pr-merge-ready')
|
|
1078
1130
|
return handlePrMergeReady(bus, manager, event);
|
|
1079
1131
|
if (eventKind === 'closed-unmerged' || eventKind === 'reopened') {
|
|
@@ -1089,8 +1141,6 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1089
1141
|
}
|
|
1090
1142
|
if (!event.agentId)
|
|
1091
1143
|
return;
|
|
1092
|
-
if (taskAtEntry?.reviewMode === 'server')
|
|
1093
|
-
return;
|
|
1094
1144
|
if (eventKind === 'comment' || eventKind === 'review-comment') {
|
|
1095
1145
|
return handlePrFeedback(bus, manager, event);
|
|
1096
1146
|
}
|
|
@@ -1100,43 +1150,125 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1100
1150
|
bus.on('pr.merged', async (event) => {
|
|
1101
1151
|
if (!event.taskId)
|
|
1102
1152
|
return;
|
|
1153
|
+
const taskId = event.taskId;
|
|
1103
1154
|
const eventPrNumber = event.data.prNumber;
|
|
1104
1155
|
const eventPrUrl = event.data.prUrl;
|
|
1105
|
-
|
|
1106
|
-
// this task never tracked (e.g. a reused custom branch name). bx/<taskId> is unique to this task,
|
|
1107
|
-
// so its merge is definitively ours even if pr.created was missed and prNumber was never recorded;
|
|
1108
|
-
// for any other branch require the tracked PR number to match, else a foreign/stale merge on a
|
|
1109
|
-
// reused custom branch name could prematurely finish an active task.
|
|
1110
|
-
const taskBefore = await manager.getTask(event.taskId);
|
|
1156
|
+
const taskBefore = await manager.getTask(taskId);
|
|
1111
1157
|
if (!taskBefore)
|
|
1112
1158
|
return;
|
|
1113
|
-
|
|
1159
|
+
const mergedOwnBxBranch = event.data.branch === BRANCH_PREFIX + taskId;
|
|
1160
|
+
const sameBoundPr = eventPrNumber !== undefined
|
|
1161
|
+
&& (taskBefore.prNumber === eventPrNumber || (taskBefore.prNumber === undefined && mergedOwnBxBranch));
|
|
1162
|
+
if (eventPrNumber !== undefined && !sameBoundPr) {
|
|
1163
|
+
await emitIntervention(bus, taskBefore.projectId, taskBefore.agentId, taskBefore.id, {
|
|
1164
|
+
phase: 'pr-merged-status-race',
|
|
1165
|
+
status: taskBefore.status,
|
|
1166
|
+
reason: taskBefore.prNumber === undefined ? 'pr-binding-unconfirmed' : 'pr-binding-changed',
|
|
1167
|
+
boundPrNumber: taskBefore.prNumber,
|
|
1168
|
+
prNumber: eventPrNumber,
|
|
1169
|
+
});
|
|
1114
1170
|
return;
|
|
1115
|
-
|
|
1116
|
-
if (
|
|
1171
|
+
}
|
|
1172
|
+
if ((taskBefore.status === 'merged' || taskBefore.status === 'done') && sameBoundPr)
|
|
1117
1173
|
return;
|
|
1174
|
+
let mergeExpectedPrNumber = taskBefore.prNumber;
|
|
1175
|
+
let mergeBindingConfirmed = sameBoundPr;
|
|
1118
1176
|
const prPatch = {
|
|
1119
1177
|
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1120
1178
|
...(eventPrUrl !== undefined ? { prUrl: eventPrUrl } : {}),
|
|
1121
1179
|
};
|
|
1122
|
-
|
|
1123
|
-
|
|
1180
|
+
if (taskBefore.status !== 'merge-ready') {
|
|
1181
|
+
if (!sameBoundPr || !PREMATURE_MERGE_BLOCKABLE_STATUSES.includes(taskBefore.status)) {
|
|
1182
|
+
await emitIntervention(bus, taskBefore.projectId, taskBefore.agentId, taskBefore.id, {
|
|
1183
|
+
phase: 'pr-merged-outside-merge-ready',
|
|
1184
|
+
status: taskBefore.status,
|
|
1185
|
+
taskPhase: taskBefore.phase,
|
|
1186
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const blocked = await manager.transitionTaskStatus(taskId, 'failed', {
|
|
1191
|
+
fromStatus: PREMATURE_MERGE_BLOCKABLE_STATUSES,
|
|
1192
|
+
expectPrNumber: taskBefore.prNumber,
|
|
1193
|
+
}, prPatch);
|
|
1194
|
+
if (blocked) {
|
|
1195
|
+
manager.stopPhaseSignalWatcher(taskId);
|
|
1196
|
+
await emitIntervention(bus, blocked.task.projectId, blocked.task.agentId, blocked.task.id, {
|
|
1197
|
+
phase: 'pr-merged-outside-merge-ready',
|
|
1198
|
+
status: blocked.previousStatus,
|
|
1199
|
+
taskPhase: blocked.task.phase,
|
|
1200
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1201
|
+
});
|
|
1202
|
+
await cleanupPrematureMergeParticipants(bus, manager, blocked.task);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const current = await manager.getTask(taskId);
|
|
1206
|
+
if (!current)
|
|
1207
|
+
return;
|
|
1208
|
+
const currentSameBoundPr = eventPrNumber !== undefined
|
|
1209
|
+
&& (current.prNumber === eventPrNumber || (current.prNumber === undefined && mergedOwnBxBranch));
|
|
1210
|
+
if ((current.status === 'merged' || current.status === 'done') && currentSameBoundPr)
|
|
1211
|
+
return;
|
|
1212
|
+
if (!currentSameBoundPr) {
|
|
1213
|
+
await emitIntervention(bus, current.projectId, current.agentId, current.id, {
|
|
1214
|
+
phase: 'pr-merged-status-race',
|
|
1215
|
+
status: current.status,
|
|
1216
|
+
reason: 'pr-binding-changed',
|
|
1217
|
+
boundPrNumber: current.prNumber,
|
|
1218
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1219
|
+
});
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (current.status !== 'merge-ready') {
|
|
1223
|
+
await emitIntervention(bus, current.projectId, current.agentId, current.id, {
|
|
1224
|
+
phase: 'pr-merged-status-race',
|
|
1225
|
+
status: current.status,
|
|
1226
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1227
|
+
});
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
mergeExpectedPrNumber = current.prNumber;
|
|
1231
|
+
mergeBindingConfirmed = true;
|
|
1232
|
+
}
|
|
1233
|
+
if (!mergeBindingConfirmed) {
|
|
1234
|
+
await emitIntervention(bus, taskBefore.projectId, taskBefore.agentId, taskBefore.id, {
|
|
1235
|
+
phase: 'pr-merged-status-race',
|
|
1236
|
+
status: taskBefore.status,
|
|
1237
|
+
reason: 'pr-binding-unconfirmed',
|
|
1238
|
+
boundPrNumber: taskBefore.prNumber,
|
|
1239
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1240
|
+
});
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const result = await manager.transitionTaskStatus(taskId, 'merged', { fromStatus: ['merge-ready'], expectPrNumber: mergeExpectedPrNumber }, prPatch);
|
|
1244
|
+
if (!result) {
|
|
1245
|
+
const current = await manager.getTask(event.taskId);
|
|
1246
|
+
if (current && (current.status === 'merged' || current.status === 'done')
|
|
1247
|
+
&& eventPrNumber !== undefined && current.prNumber === eventPrNumber)
|
|
1248
|
+
return;
|
|
1249
|
+
if (current) {
|
|
1250
|
+
await emitIntervention(bus, current.projectId, current.agentId, current.id, {
|
|
1251
|
+
phase: 'pr-merged-status-race',
|
|
1252
|
+
status: current.status,
|
|
1253
|
+
...(eventPrNumber !== undefined ? { prNumber: eventPrNumber } : {}),
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1124
1256
|
return;
|
|
1257
|
+
}
|
|
1125
1258
|
const { task: transitioned } = result;
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1259
|
+
const qaAgentId = requireTaskQaAgentId(transitioned, 'pr.merged');
|
|
1260
|
+
if (transitioned.prNumber && transitioned.branch) {
|
|
1261
|
+
await manager.dispatchPostMergeCleanup(qaAgentId, {
|
|
1262
|
+
taskId: transitioned.id,
|
|
1263
|
+
branch: transitioned.branch,
|
|
1264
|
+
}).catch(err => console.warn(`[EventHandler] pr.merged dispatchPostMergeCleanup(QA=${qaAgentId}) failed:`, err));
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
try {
|
|
1268
|
+
await manager.releaseAgentForTask(qaAgentId, transitioned.id, 'idle');
|
|
1132
1269
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
await manager.releaseAgentForTask(transitioned.qaAgentId, transitioned.id, 'idle');
|
|
1136
|
-
}
|
|
1137
|
-
catch (err) {
|
|
1138
|
-
console.error(`[EventHandler] pr.merged releaseAgentForTask(QA=${transitioned.qaAgentId}) failed:`, err);
|
|
1139
|
-
}
|
|
1270
|
+
catch (err) {
|
|
1271
|
+
console.error(`[EventHandler] pr.merged releaseAgentForTask(QA=${qaAgentId}) failed:`, err);
|
|
1140
1272
|
}
|
|
1141
1273
|
}
|
|
1142
1274
|
try {
|
|
@@ -1152,8 +1284,6 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1152
1284
|
const task = await manager.getTask(event.taskId);
|
|
1153
1285
|
if (!task)
|
|
1154
1286
|
return;
|
|
1155
|
-
if (task.reviewMode === 'server')
|
|
1156
|
-
return;
|
|
1157
1287
|
if (event.data.source === 'pane-signal') {
|
|
1158
1288
|
console.log(`[EventHandler] review.submitted pane verdict ignored for task ${task.id} (comment-token protocol is the sole authority)`);
|
|
1159
1289
|
return;
|
|
@@ -1199,10 +1329,6 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1199
1329
|
await rejectGitPayload(new GitVerdictPayloadError('verdict token is not current'));
|
|
1200
1330
|
return;
|
|
1201
1331
|
}
|
|
1202
|
-
if (isSpecStagePhase(task.phase)) {
|
|
1203
|
-
console.warn(`[EventHandler] review.submitted (action=${action}) ignored for task ${task.id}: task in spec phase`);
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
1332
|
const verdictSubmittedAt = typeof event.data.submittedAt === 'string' ? event.data.submittedAt : undefined;
|
|
1207
1333
|
if (verdictSubmittedAt && task.reviewDispatchedAt) {
|
|
1208
1334
|
const submittedMs = Date.parse(verdictSubmittedAt);
|
|
@@ -1241,7 +1367,7 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1241
1367
|
if (!event.taskId || !event.agentId)
|
|
1242
1368
|
return;
|
|
1243
1369
|
const task = await manager.getTask(event.taskId);
|
|
1244
|
-
if (!task || task.
|
|
1370
|
+
if (!task || task.status !== 'fixing')
|
|
1245
1371
|
return;
|
|
1246
1372
|
const { projectId, agentId } = task;
|
|
1247
1373
|
const stayFixing = async (data) => {
|
|
@@ -1279,7 +1405,6 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1279
1405
|
return;
|
|
1280
1406
|
}
|
|
1281
1407
|
if (!verify.ok) {
|
|
1282
|
-
// PR 在修复期间被关闭/转 draft/retarget:推进 recheck 只会造出无裁决入口的评审轮
|
|
1283
1408
|
await stayFixing({ phase: 'fix-verify-binding-mismatch', reason: verify.reason });
|
|
1284
1409
|
return;
|
|
1285
1410
|
}
|
|
@@ -1309,12 +1434,9 @@ export function registerEventHandlers(bus, manager) {
|
|
|
1309
1434
|
return;
|
|
1310
1435
|
}
|
|
1311
1436
|
}
|
|
1312
|
-
// head fetch / 回复检查是秒级网络等待,期间真 push / verdict 可能已换代 pass;旧信号不得再驱动派发
|
|
1313
1437
|
const preEmit = await manager.getTask(task.id);
|
|
1314
1438
|
if (!preEmit || preEmit.status !== 'fixing' || preEmit.signalToken !== token)
|
|
1315
1439
|
return;
|
|
1316
|
-
// head 前进时不能指望 poller:push 事件已被消费(cursor 不回退),若那次派发失败回滚,
|
|
1317
|
-
// 这里的合成 push 是唯一重试入口;宁可与迟到的真 push 竞态重派一轮,也不静默滞留 fixing
|
|
1318
1440
|
await bus.emit({
|
|
1319
1441
|
id: '',
|
|
1320
1442
|
type: 'pr.updated',
|