baxian 1.2.61 → 2.0.0
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 +7 -4
- package/README.zh-CN.md +7 -4
- package/dist/agent/bootstrap-poller.d.ts +1 -1
- package/dist/agent/bootstrap-poller.d.ts.map +1 -1
- package/dist/agent/bootstrap-poller.js.map +1 -1
- package/dist/agent/bootstrap.d.ts +1 -1
- package/dist/agent/bootstrap.d.ts.map +1 -1
- package/dist/agent/bootstrap.js +6 -2
- package/dist/agent/bootstrap.js.map +1 -1
- package/dist/agent/branch.d.ts +1 -2
- package/dist/agent/branch.d.ts.map +1 -1
- package/dist/agent/branch.js +2 -2
- package/dist/agent/branch.js.map +1 -1
- package/dist/agent/dispatch-reconciler.d.ts +0 -1
- package/dist/agent/dispatch-reconciler.d.ts.map +1 -1
- package/dist/agent/dispatch-reconciler.js +5 -44
- package/dist/agent/dispatch-reconciler.js.map +1 -1
- package/dist/agent/manager.d.ts +175 -82
- package/dist/agent/manager.d.ts.map +1 -1
- package/dist/agent/manager.js +2453 -1447
- package/dist/agent/manager.js.map +1 -1
- package/dist/agent/phase-signal-watcher.d.ts +5 -1
- package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
- package/dist/agent/phase-signal-watcher.js +22 -11
- package/dist/agent/phase-signal-watcher.js.map +1 -1
- package/dist/agent/phase-signal.d.ts +2 -7
- package/dist/agent/phase-signal.d.ts.map +1 -1
- package/dist/agent/phase-signal.js +4 -4
- package/dist/agent/phase-signal.js.map +1 -1
- package/dist/agent/preflight.d.ts +4 -1
- package/dist/agent/preflight.d.ts.map +1 -1
- package/dist/agent/preflight.js +46 -45
- package/dist/agent/preflight.js.map +1 -1
- package/dist/agent/prompt.d.ts +8 -0
- package/dist/agent/prompt.d.ts.map +1 -1
- package/dist/agent/prompt.js +30 -13
- package/dist/agent/prompt.js.map +1 -1
- package/dist/agent/repo-store.d.ts +4 -0
- package/dist/agent/repo-store.d.ts.map +1 -1
- package/dist/agent/repo-store.js +37 -8
- package/dist/agent/repo-store.js.map +1 -1
- package/dist/agent/review-transport.d.ts +20 -1
- package/dist/agent/review-transport.d.ts.map +1 -1
- package/dist/agent/review-transport.js +68 -20
- package/dist/agent/review-transport.js.map +1 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +12 -11
- package/dist/api/config.js.map +1 -1
- package/dist/api/hosts.d.ts.map +1 -1
- package/dist/api/hosts.js +3 -2
- package/dist/api/hosts.js.map +1 -1
- package/dist/api/platform-guard.d.ts +10 -2
- package/dist/api/platform-guard.d.ts.map +1 -1
- package/dist/api/platform-guard.js +112 -10
- package/dist/api/platform-guard.js.map +1 -1
- package/dist/api/projects.d.ts.map +1 -1
- package/dist/api/projects.js +34 -9
- package/dist/api/projects.js.map +1 -1
- package/dist/api/tasks.d.ts.map +1 -1
- package/dist/api/tasks.js +21 -36
- package/dist/api/tasks.js.map +1 -1
- package/dist/app.d.ts +4 -4
- package/dist/app.d.ts.map +1 -1
- package/dist/config/hot-reload.d.ts +11 -2
- package/dist/config/hot-reload.d.ts.map +1 -1
- package/dist/config/hot-reload.js +43 -8
- package/dist/config/hot-reload.js.map +1 -1
- package/dist/config/loader.js +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/validator.d.ts +3 -1
- package/dist/config/validator.d.ts.map +1 -1
- package/dist/config/validator.js +53 -31
- package/dist/config/validator.js.map +1 -1
- package/dist/event/handlers.d.ts.map +1 -1
- package/dist/event/handlers.js +616 -1067
- package/dist/event/handlers.js.map +1 -1
- package/dist/event/server-handlers.d.ts.map +1 -1
- package/dist/event/server-handlers.js +667 -440
- package/dist/event/server-handlers.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -79
- package/dist/index.js.map +1 -1
- package/dist/platform/command-renderer.d.ts +1 -0
- package/dist/platform/command-renderer.d.ts.map +1 -1
- package/dist/platform/command-renderer.js +16 -2
- package/dist/platform/command-renderer.js.map +1 -1
- package/dist/platform/comment-cursor.d.ts +9 -0
- package/dist/platform/comment-cursor.d.ts.map +1 -1
- package/dist/platform/comment-cursor.js +63 -5
- 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 +0 -2
- 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 +17 -6
- package/dist/platform/driver-spec.js.map +1 -1
- package/dist/platform/feedback.d.ts.map +1 -1
- package/dist/platform/feedback.js +0 -9
- package/dist/platform/feedback.js.map +1 -1
- package/dist/platform/git-driver.d.ts +3 -0
- package/dist/platform/git-driver.d.ts.map +1 -1
- package/dist/platform/git-driver.js +45 -3
- package/dist/platform/git-driver.js.map +1 -1
- package/dist/platform/platform-poller.d.ts +18 -5
- package/dist/platform/platform-poller.d.ts.map +1 -1
- package/dist/platform/platform-poller.js +282 -33
- package/dist/platform/platform-poller.js.map +1 -1
- package/dist/platform/plugins/github/baxian-plugin.json +1 -1
- package/dist/platform/plugins/github/driver.json +14 -3
- package/dist/platform/plugins/github/skills/baxian-cli-gh/SKILL.md +7 -6
- package/dist/platform/plugins/github/skills/baxian-cli-gh/agents/openai.yaml +2 -0
- package/dist/platform/pr-binding.d.ts.map +1 -1
- package/dist/platform/pr-binding.js +0 -2
- package/dist/platform/pr-binding.js.map +1 -1
- package/dist/platform/pr-conversation-cache.d.ts.map +1 -0
- package/dist/platform/pr-conversation-cache.js.map +1 -0
- package/dist/platform/row-schema.d.ts.map +1 -1
- package/dist/platform/row-schema.js +1 -0
- package/dist/platform/row-schema.js.map +1 -1
- package/dist/platform/startup.d.ts +29 -1
- package/dist/platform/startup.d.ts.map +1 -1
- package/dist/platform/startup.js +82 -4
- package/dist/platform/startup.js.map +1 -1
- package/dist/platform/types.d.ts +2 -0
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/platform/types.js +6 -4
- 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 -2
- package/dist/platform/verdict-engine.js.map +1 -1
- package/dist/shared/types.d.ts +73 -5
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/skills/baxian-pr-feedback/SKILL.md +4 -12
- package/dist/skills/baxian-pr-recheck/SKILL.md +3 -44
- package/dist/skills/baxian-pr-review/SKILL.md +4 -43
- package/dist/skills/baxian-server-feedback/SKILL.md +7 -5
- package/dist/skills/baxian-signals/SKILL.md +5 -7
- package/dist/skills/baxian-task-check/SKILL.md +3 -5
- package/dist/state/agent-store.js +0 -2
- package/dist/state/agent-store.js.map +1 -1
- package/dist/state/review-store.d.ts +21 -1
- package/dist/state/review-store.d.ts.map +1 -1
- package/dist/state/review-store.js +132 -0
- package/dist/state/review-store.js.map +1 -1
- package/dist/state/task-store.d.ts +6 -0
- package/dist/state/task-store.d.ts.map +1 -1
- package/dist/state/task-store.js +259 -18
- package/dist/state/task-store.js.map +1 -1
- package/dist/web/assets/index-BXfgaFNm.js +18 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/github/mapper.d.ts +0 -9
- package/dist/github/mapper.d.ts.map +0 -1
- package/dist/github/mapper.js +0 -25
- package/dist/github/mapper.js.map +0 -1
- package/dist/github/poller.d.ts +0 -73
- package/dist/github/poller.d.ts.map +0 -1
- package/dist/github/poller.js +0 -556
- package/dist/github/poller.js.map +0 -1
- package/dist/github/pr-conversation-cache.d.ts.map +0 -1
- package/dist/github/pr-conversation-cache.js.map +0 -1
- package/dist/github/pr-conversation.d.ts +0 -7
- package/dist/github/pr-conversation.d.ts.map +0 -1
- package/dist/github/pr-conversation.js +0 -175
- package/dist/github/pr-conversation.js.map +0 -1
- package/dist/github/resolver.d.ts +0 -8
- package/dist/github/resolver.d.ts.map +0 -1
- package/dist/github/resolver.js +0 -28
- package/dist/github/resolver.js.map +0 -1
- package/dist/state/post-approve-store.d.ts +0 -23
- package/dist/state/post-approve-store.d.ts.map +0 -1
- package/dist/state/post-approve-store.js +0 -82
- package/dist/state/post-approve-store.js.map +0 -1
- package/dist/web/assets/index-CUJtd0bC.js +0 -16
- /package/dist/{github → platform}/pr-conversation-cache.d.ts +0 -0
- /package/dist/{github → platform}/pr-conversation-cache.js +0 -0
|
@@ -3,6 +3,7 @@ import { computeBackoffMs } from '../timing/backoff.js';
|
|
|
3
3
|
import { computePollerHealth } from './poller-health.js';
|
|
4
4
|
import { BRANCH_PREFIX } from '../shared/constants.js';
|
|
5
5
|
import { repoIdentityKey } from '../shared/git-url.js';
|
|
6
|
+
import { TASK_TERMINAL_STATUS_SET } from '../shared/constants.js';
|
|
6
7
|
import { DriverOpError } from './git-driver.js';
|
|
7
8
|
import { versionTimeOf } from './row-schema.js';
|
|
8
9
|
import { CommentCursorStore } from './comment-cursor.js';
|
|
@@ -11,6 +12,28 @@ import { collectValidAcks, rowBodyDigest } from './markers.js';
|
|
|
11
12
|
import { createHash } from 'node:crypto';
|
|
12
13
|
import { buildAckCarrierRows, feedbackEventTarget, scanCommentSourcesOnce } from './feedback.js';
|
|
13
14
|
import { checkPrBinding } from './pr-binding.js';
|
|
15
|
+
// TaskState 到轮询视图的唯一投影:poller 只看这些字段,其余任务状态一律不进轮询面。
|
|
16
|
+
export function platformTaskView(task) {
|
|
17
|
+
return {
|
|
18
|
+
taskId: task.id,
|
|
19
|
+
terminal: TASK_TERMINAL_STATUS_SET.has(task.status),
|
|
20
|
+
reviewMode: task.reviewMode,
|
|
21
|
+
status: task.status,
|
|
22
|
+
inReview: task.status === 'review',
|
|
23
|
+
closedUnmergedAnchor: task.closedUnmergedAnchor !== undefined && task.closedUnmergedAnchor.cleared !== true,
|
|
24
|
+
...(task.branch !== undefined ? { branch: task.branch } : {}),
|
|
25
|
+
...(task.phase !== undefined ? { phase: task.phase } : {}),
|
|
26
|
+
...(task.prNumber !== undefined ? { prNumber: task.prNumber } : {}),
|
|
27
|
+
...(task.reviewHeadAnchorSha !== undefined ? { anchorSha: task.reviewHeadAnchorSha } : {}),
|
|
28
|
+
...(task.passToken !== undefined ? { passToken: task.passToken } : {}),
|
|
29
|
+
...(task.failToken !== undefined ? { failToken: task.failToken } : {}),
|
|
30
|
+
...(task.signalToken !== undefined ? { signalToken: task.signalToken } : {}),
|
|
31
|
+
...(task.baseBranch !== undefined ? { expectedBase: task.baseBranch } : {}),
|
|
32
|
+
...(task.latestHeadSha !== undefined ? { latestHeadSha: task.latestHeadSha } : {}),
|
|
33
|
+
...(task.replyActorId !== undefined ? { replyActorId: task.replyActorId } : {}),
|
|
34
|
+
...(task.replyActorStatus !== undefined ? { replyActorStatus: task.replyActorStatus } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
14
37
|
// 限流后必须停止请求并退避(GitHub 平台要求,持续触发 secondary limit 会招致封禁):
|
|
15
38
|
// 命中即短路该 entry 本周期剩余工作,退避窗口内整周期跳过,成功周期重置。
|
|
16
39
|
// abort 携带此前已入列的全部失败:混合周期(如 ACCESS_DENIED → RATE_LIMIT)的真实
|
|
@@ -32,10 +55,17 @@ class CycleFailuresError extends Error {
|
|
|
32
55
|
this.name = 'CycleFailuresError';
|
|
33
56
|
}
|
|
34
57
|
}
|
|
58
|
+
class EntryRetiredError extends Error {
|
|
59
|
+
constructor() {
|
|
60
|
+
super('platform poller entry retired during reconciliation');
|
|
61
|
+
this.name = 'EntryRetiredError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
35
64
|
const RATE_LIMIT_BACKOFF_MIN_MS = 60_000;
|
|
36
65
|
const RATE_LIMIT_BACKOFF_MAX_MS = 900_000;
|
|
37
66
|
// 与 spec §8 base 快照同一时效纪律:projectView 连续失败 ≥3 周期后缓存默认分支视为缺失。
|
|
38
67
|
const DEFAULT_BRANCH_STALE_CYCLES = 3;
|
|
68
|
+
const ADOPTION_DEFERRAL_INTERVENTION_CYCLES = 3;
|
|
39
69
|
export class PlatformPoller {
|
|
40
70
|
opts;
|
|
41
71
|
entries = [];
|
|
@@ -43,6 +73,7 @@ export class PlatformPoller {
|
|
|
43
73
|
periodicRunner;
|
|
44
74
|
isPolling = false;
|
|
45
75
|
intervalMs;
|
|
76
|
+
lifecycleHook;
|
|
46
77
|
now;
|
|
47
78
|
constructor(opts) {
|
|
48
79
|
this.opts = opts;
|
|
@@ -52,6 +83,7 @@ export class PlatformPoller {
|
|
|
52
83
|
this.entries.push({
|
|
53
84
|
...init,
|
|
54
85
|
repoKey: repoIdentityKey(init.repoUrl),
|
|
86
|
+
retired: false,
|
|
55
87
|
status: { isPolling: false, consecutiveFailures: 0 },
|
|
56
88
|
defaultBranchStale: 0,
|
|
57
89
|
observedPr: new Map(),
|
|
@@ -59,14 +91,74 @@ export class PlatformPoller {
|
|
|
59
91
|
deliveredVerdicts: new Map(),
|
|
60
92
|
loggedUndated: new Set(),
|
|
61
93
|
baseMismatch: new Map(),
|
|
94
|
+
adoptionDeferrals: new Map(),
|
|
62
95
|
});
|
|
63
96
|
}
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
|
|
97
|
+
reconcile(wanted) {
|
|
98
|
+
const wantedByKey = this.indexWantedEntries(wanted);
|
|
99
|
+
this.retireUnwantedEntries(wantedByKey);
|
|
100
|
+
this.upsertWantedEntries(wantedByKey);
|
|
101
|
+
this.fireLifecycle();
|
|
102
|
+
}
|
|
103
|
+
indexWantedEntries(wanted) {
|
|
104
|
+
const wantedByKey = new Map();
|
|
105
|
+
for (const init of wanted) {
|
|
106
|
+
const key = repoIdentityKey(init.repoUrl);
|
|
107
|
+
if (!wantedByKey.has(key))
|
|
108
|
+
wantedByKey.set(key, init);
|
|
109
|
+
}
|
|
110
|
+
return wantedByKey;
|
|
111
|
+
}
|
|
112
|
+
retireUnwantedEntries(wantedByKey) {
|
|
113
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
114
|
+
if (!wantedByKey.has(this.entries[i].repoKey)) {
|
|
115
|
+
this.entries[i].retired = true;
|
|
116
|
+
this.entries.splice(i, 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
upsertWantedEntries(wantedByKey) {
|
|
121
|
+
// Reusing repo-keyed entries keeps durable cursors and in-memory observations aligned across reloads.
|
|
122
|
+
const existingByKey = new Map(this.entries.map(e => [e.repoKey, e]));
|
|
123
|
+
for (const [key, init] of wantedByKey) {
|
|
124
|
+
const existing = existingByKey.get(key);
|
|
125
|
+
if (existing) {
|
|
126
|
+
if (existing.status.isPolling) {
|
|
127
|
+
existing.pendingInit = { projectId: init.projectId, driver: init.driver };
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.applyEntryInit(existing, init);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this.add(init);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
applyEntryInit(entry, init) {
|
|
139
|
+
if (entry.retired)
|
|
140
|
+
return;
|
|
141
|
+
entry.projectId = init.projectId;
|
|
142
|
+
const samePreflight = entry.driver.preflightIdentity !== undefined
|
|
143
|
+
&& init.driver.preflightIdentity !== undefined
|
|
144
|
+
? entry.driver.preflightIdentity === init.driver.preflightIdentity
|
|
145
|
+
: entry.driver === init.driver;
|
|
146
|
+
if (!samePreflight)
|
|
147
|
+
entry.preflightPassed = undefined;
|
|
148
|
+
entry.driver = init.driver;
|
|
149
|
+
}
|
|
150
|
+
setLifecycleHook(fn) {
|
|
151
|
+
this.lifecycleHook = fn;
|
|
152
|
+
}
|
|
153
|
+
fireLifecycle() {
|
|
154
|
+
if (!this.lifecycleHook)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
this.lifecycleHook();
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error('[PlatformPoller] lifecycle hook threw:', err);
|
|
161
|
+
}
|
|
70
162
|
}
|
|
71
163
|
// 超龄即视为缺失;纯缓存读,派发路径零网络调用。
|
|
72
164
|
defaultBranchSnapshot(repoKey) {
|
|
@@ -107,6 +199,12 @@ export class PlatformPoller {
|
|
|
107
199
|
}
|
|
108
200
|
this.periodicRunner.start();
|
|
109
201
|
}
|
|
202
|
+
// 热重载改 pollIntervalMs:只调整已在运行的 runner,不替一个从未 start 的 poller 启动
|
|
203
|
+
// (启动是 index 装配的职责)。未运行时仅记下新间隔,下次 start 生效。
|
|
204
|
+
reschedule(intervalMs) {
|
|
205
|
+
this.intervalMs = intervalMs;
|
|
206
|
+
this.periodicRunner?.reschedule(intervalMs);
|
|
207
|
+
}
|
|
110
208
|
stop() {
|
|
111
209
|
this.periodicRunner?.stop();
|
|
112
210
|
}
|
|
@@ -119,11 +217,15 @@ export class PlatformPoller {
|
|
|
119
217
|
}
|
|
120
218
|
this.isPolling = true;
|
|
121
219
|
try {
|
|
122
|
-
for (const entry of this.entries) {
|
|
220
|
+
for (const entry of [...this.entries]) {
|
|
221
|
+
if (entry.retired || !this.entries.includes(entry))
|
|
222
|
+
continue;
|
|
123
223
|
try {
|
|
124
224
|
await this.pollOne(entry);
|
|
125
225
|
}
|
|
126
226
|
catch (e) {
|
|
227
|
+
if (e instanceof EntryRetiredError)
|
|
228
|
+
continue;
|
|
127
229
|
console.warn(`[PlatformPoller] entry ${entry.repoKey} failed:`, e instanceof Error ? e.message : e);
|
|
128
230
|
}
|
|
129
231
|
}
|
|
@@ -133,11 +235,15 @@ export class PlatformPoller {
|
|
|
133
235
|
}
|
|
134
236
|
}
|
|
135
237
|
async pollOne(entry) {
|
|
136
|
-
|
|
238
|
+
this.assertEntryActive(entry);
|
|
239
|
+
if (entry.rateLimit !== undefined && this.now() < entry.rateLimit.until) {
|
|
240
|
+
entry.defaultBranchStale += 1;
|
|
137
241
|
return;
|
|
242
|
+
}
|
|
138
243
|
const startedAt = this.now();
|
|
139
244
|
entry.status.isPolling = true;
|
|
140
245
|
entry.status.lastPollStartedAt = new Date(startedAt).toISOString();
|
|
246
|
+
this.fireLifecycle();
|
|
141
247
|
try {
|
|
142
248
|
if (entry.cursor === undefined) {
|
|
143
249
|
const cursor = new CommentCursorStore(entry.statePath, entry.repoUrl);
|
|
@@ -154,6 +260,8 @@ export class PlatformPoller {
|
|
|
154
260
|
entry.status.lastErrorClass = undefined;
|
|
155
261
|
}
|
|
156
262
|
catch (e) {
|
|
263
|
+
if (e instanceof EntryRetiredError)
|
|
264
|
+
return;
|
|
157
265
|
if (e instanceof RateLimitAbort) {
|
|
158
266
|
// 不计凭据健康度(spec §5.3 要点):退避顺延重试并在状态里如实标注 rate-limited。
|
|
159
267
|
const attempt = (entry.rateLimit?.attempt ?? 0) + 1;
|
|
@@ -178,9 +286,14 @@ export class PlatformPoller {
|
|
|
178
286
|
}
|
|
179
287
|
finally {
|
|
180
288
|
entry.status.isPolling = false;
|
|
289
|
+
if (!entry.retired && entry.pendingInit !== undefined) {
|
|
290
|
+
this.applyEntryInit(entry, entry.pendingInit);
|
|
291
|
+
entry.pendingInit = undefined;
|
|
292
|
+
}
|
|
181
293
|
const endedAt = this.now();
|
|
182
294
|
entry.status.lastPollEndedAt = new Date(endedAt).toISOString();
|
|
183
295
|
entry.status.lastPollDurationMs = endedAt - startedAt;
|
|
296
|
+
this.fireLifecycle();
|
|
184
297
|
}
|
|
185
298
|
}
|
|
186
299
|
recordFailure(entry, message, errorClass) {
|
|
@@ -192,6 +305,8 @@ export class PlatformPoller {
|
|
|
192
305
|
async pollCycle(entry) {
|
|
193
306
|
const failures = [];
|
|
194
307
|
const fail = (context, e) => {
|
|
308
|
+
if (e instanceof EntryRetiredError)
|
|
309
|
+
throw e;
|
|
195
310
|
const failure = {
|
|
196
311
|
message: `${context}: ${e instanceof Error ? e.message : String(e)}`,
|
|
197
312
|
errorClass: e instanceof DriverOpError ? e.info.errorClass : undefined,
|
|
@@ -202,17 +317,29 @@ export class PlatformPoller {
|
|
|
202
317
|
};
|
|
203
318
|
try {
|
|
204
319
|
await entry.cursor.flushIfDirty();
|
|
320
|
+
this.assertEntryActive(entry);
|
|
205
321
|
}
|
|
206
322
|
catch (e) {
|
|
207
323
|
fail('cursor flush', e);
|
|
208
324
|
}
|
|
209
325
|
const tasks = await this.opts.tasks(entry.projectId);
|
|
326
|
+
this.assertEntryActive(entry);
|
|
210
327
|
const byId = new Map(tasks.map(t => [t.taskId, t]));
|
|
211
328
|
for (const gen of entry.cursor.generations()) {
|
|
212
|
-
|
|
213
|
-
|
|
329
|
+
let authoritative;
|
|
330
|
+
try {
|
|
331
|
+
authoritative = await this.opts.task(gen);
|
|
332
|
+
this.assertEntryActive(entry);
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
fail(`read task ${gen}`, e);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (authoritative === null || authoritative.terminal) {
|
|
214
339
|
try {
|
|
340
|
+
this.assertEntryActive(entry);
|
|
215
341
|
await entry.cursor.dropGeneration(gen);
|
|
342
|
+
this.assertEntryActive(entry);
|
|
216
343
|
}
|
|
217
344
|
catch (e) {
|
|
218
345
|
fail(`dropGeneration ${gen}`, e);
|
|
@@ -258,6 +385,7 @@ export class PlatformPoller {
|
|
|
258
385
|
let steps;
|
|
259
386
|
try {
|
|
260
387
|
steps = await entry.driver.runPreflightSteps();
|
|
388
|
+
this.assertEntryActive(entry);
|
|
261
389
|
}
|
|
262
390
|
catch (e) {
|
|
263
391
|
entry.defaultBranchStale += 1;
|
|
@@ -274,6 +402,7 @@ export class PlatformPoller {
|
|
|
274
402
|
let defaultBranch;
|
|
275
403
|
try {
|
|
276
404
|
const [row] = await entry.driver.runOp('projectView');
|
|
405
|
+
this.assertEntryActive(entry);
|
|
277
406
|
if (row !== undefined && typeof row.defaultBranch === 'string') {
|
|
278
407
|
entry.defaultBranch = row.defaultBranch;
|
|
279
408
|
entry.defaultBranchStale = 0;
|
|
@@ -282,6 +411,7 @@ export class PlatformPoller {
|
|
|
282
411
|
if (row !== undefined && entry.preflightPassed !== true && entry.driver.runPreflightSteps !== undefined) {
|
|
283
412
|
if (row.pushPermitted === false) {
|
|
284
413
|
fail('preflight-push', new Error(`push permission missing for ${entry.repoUrl} — server merge/close requires write access`));
|
|
414
|
+
return failures;
|
|
285
415
|
}
|
|
286
416
|
else {
|
|
287
417
|
if (row.pushPermitted === undefined) {
|
|
@@ -302,6 +432,7 @@ export class PlatformPoller {
|
|
|
302
432
|
? entry.defaultBranch
|
|
303
433
|
: undefined);
|
|
304
434
|
await this.pollListPrs(entry, tasks, byId, defaultBranch, fail, failures);
|
|
435
|
+
this.assertEntryActive(entry);
|
|
305
436
|
for (const task of tasks) {
|
|
306
437
|
if (task.terminal || task.prNumber === undefined)
|
|
307
438
|
continue;
|
|
@@ -310,6 +441,7 @@ export class PlatformPoller {
|
|
|
310
441
|
if (task.closedUnmergedAnchor === true)
|
|
311
442
|
continue;
|
|
312
443
|
await this.subPoll(entry, task, usableDefault, fail);
|
|
444
|
+
this.assertEntryActive(entry);
|
|
313
445
|
}
|
|
314
446
|
return failures;
|
|
315
447
|
}
|
|
@@ -341,19 +473,56 @@ export class PlatformPoller {
|
|
|
341
473
|
return vt !== undefined && vt < prsCursor.watermarkTime;
|
|
342
474
|
});
|
|
343
475
|
});
|
|
476
|
+
this.assertEntryActive(entry);
|
|
344
477
|
}
|
|
345
478
|
catch (e) {
|
|
346
479
|
fail('listPrs', e);
|
|
347
480
|
return;
|
|
348
481
|
}
|
|
482
|
+
const listedPrRows = [...prRows];
|
|
483
|
+
const pendingByPr = new Map();
|
|
484
|
+
const activeDeferrals = new Set();
|
|
485
|
+
for (const pending of entry.cursor.pendingAdoptions()) {
|
|
486
|
+
let task;
|
|
487
|
+
try {
|
|
488
|
+
task = await this.opts.task(pending.taskId);
|
|
489
|
+
this.assertEntryActive(entry);
|
|
490
|
+
if (task === null || task.terminal || (task.prNumber !== undefined && task.prNumber !== pending.prNumber)) {
|
|
491
|
+
await entry.cursor.clearAdoptionPending(pending.taskId, pending.prNumber);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (task.prNumber === pending.prNumber) {
|
|
495
|
+
await entry.cursor.markAdoptionDelivered(pending.taskId, pending.prNumber);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
byId.set(task.taskId, task);
|
|
499
|
+
pendingByPr.set(pending.prNumber, pending.taskId);
|
|
500
|
+
if (prRows.some(row => row.prNumber === pending.prNumber))
|
|
501
|
+
continue;
|
|
502
|
+
const [row] = await entry.driver.runOp('prView', { prNumber: pending.prNumber });
|
|
503
|
+
this.assertEntryActive(entry);
|
|
504
|
+
if (row !== undefined)
|
|
505
|
+
prRows.push({ ...row, prNumber: pending.prNumber });
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
activeDeferrals.add(pending.prNumber);
|
|
509
|
+
fail(`retry adoption pr#${pending.prNumber}`, e);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
349
512
|
const boundByPr = new Map();
|
|
350
513
|
for (const t of tasks) {
|
|
351
514
|
if (!t.terminal && t.prNumber !== undefined)
|
|
352
515
|
boundByPr.set(t.prNumber, t);
|
|
353
516
|
}
|
|
354
517
|
const failuresBefore = failures.length;
|
|
355
|
-
let deferredAdoptions = 0;
|
|
356
518
|
const seenPrs = new Set();
|
|
519
|
+
const clearPending = async (prNumber) => {
|
|
520
|
+
const taskId = pendingByPr.get(prNumber);
|
|
521
|
+
if (taskId === undefined)
|
|
522
|
+
return;
|
|
523
|
+
await entry.cursor.clearAdoptionPending(taskId, prNumber);
|
|
524
|
+
pendingByPr.delete(prNumber);
|
|
525
|
+
};
|
|
357
526
|
for (const row of prRows) {
|
|
358
527
|
const prNumber = row.prNumber;
|
|
359
528
|
seenPrs.add(prNumber);
|
|
@@ -364,10 +533,11 @@ export class PlatformPoller {
|
|
|
364
533
|
};
|
|
365
534
|
const boundTask = boundByPr.get(prNumber);
|
|
366
535
|
if (boundTask !== undefined) {
|
|
536
|
+
await clearPending(prNumber);
|
|
367
537
|
// reopen 是显式 durable 观察:closed-unmerged 锚在 TaskState,主轮询见重开即合成(spec §6)。
|
|
368
538
|
if (boundTask.closedUnmergedAnchor === true && row.state === 'open') {
|
|
369
539
|
try {
|
|
370
|
-
await this.emit(entry, 'pr.updated', { ...base, kind: 'reopened' });
|
|
540
|
+
await this.emit(entry, 'pr.updated', { ...base, kind: 'reopened' }, boundTask.taskId);
|
|
371
541
|
entry.observedPr.delete(`${boundTask.taskId}:${prNumber}`);
|
|
372
542
|
}
|
|
373
543
|
catch (e) {
|
|
@@ -376,31 +546,48 @@ export class PlatformPoller {
|
|
|
376
546
|
}
|
|
377
547
|
continue;
|
|
378
548
|
}
|
|
379
|
-
if (row.state !== 'open' || row.draft === true)
|
|
549
|
+
if (row.state !== 'open' || row.draft === true) {
|
|
550
|
+
await clearPending(prNumber);
|
|
380
551
|
continue;
|
|
381
|
-
|
|
552
|
+
}
|
|
553
|
+
if (!base.branch.startsWith(BRANCH_PREFIX)) {
|
|
554
|
+
await clearPending(prNumber);
|
|
382
555
|
continue;
|
|
556
|
+
}
|
|
383
557
|
const task = byId.get(base.branch.slice(BRANCH_PREFIX.length));
|
|
384
|
-
if (task === undefined || task.terminal || task.prNumber !== undefined)
|
|
558
|
+
if (task === undefined || task.terminal || task.prNumber !== undefined) {
|
|
559
|
+
await clearPending(prNumber);
|
|
385
560
|
continue;
|
|
561
|
+
}
|
|
562
|
+
const pendingTaskId = pendingByPr.get(prNumber);
|
|
563
|
+
if (pendingTaskId !== undefined && pendingTaskId !== task.taskId)
|
|
564
|
+
await clearPending(prNumber);
|
|
386
565
|
// 统一谓词 branch === task.branch:branch 缺失的任务视图无从核验绑定,收编一并 fail closed
|
|
387
566
|
// (与子轮询 bindingCheck 对称——「无法核验」不是「匹配」)。
|
|
388
|
-
if (task.branch !== base.branch)
|
|
567
|
+
if (task.branch !== base.branch) {
|
|
568
|
+
await clearPending(prNumber);
|
|
389
569
|
continue;
|
|
570
|
+
}
|
|
390
571
|
if (row.sourceProjectId === null || row.sourceProjectId === undefined
|
|
391
|
-
|| row.sourceProjectId !== row.targetProjectId)
|
|
572
|
+
|| row.sourceProjectId !== row.targetProjectId) {
|
|
573
|
+
await clearPending(prNumber);
|
|
392
574
|
continue;
|
|
575
|
+
}
|
|
393
576
|
const target = String(row.targetBranch);
|
|
577
|
+
if (entry.cursor.isAdoptionDelivered(task.taskId, prNumber))
|
|
578
|
+
continue;
|
|
394
579
|
try {
|
|
395
580
|
if (task.expectedBase !== undefined) {
|
|
396
|
-
if (target !== task.expectedBase)
|
|
581
|
+
if (target !== task.expectedBase) {
|
|
582
|
+
await clearPending(prNumber);
|
|
397
583
|
continue;
|
|
584
|
+
}
|
|
398
585
|
}
|
|
399
586
|
else {
|
|
400
587
|
if (defaultBranch === undefined) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
588
|
+
await entry.cursor.markAdoptionPending(task.taskId, prNumber);
|
|
589
|
+
pendingByPr.set(prNumber, task.taskId);
|
|
590
|
+
activeDeferrals.add(prNumber);
|
|
404
591
|
continue;
|
|
405
592
|
}
|
|
406
593
|
if (target !== defaultBranch) {
|
|
@@ -410,17 +597,61 @@ export class PlatformPoller {
|
|
|
410
597
|
if (entry.baseMismatch.get(prNumber) !== fp) {
|
|
411
598
|
await this.emit(entry, 'human.intervention', {
|
|
412
599
|
...base, reason: 'base-mismatch', targetBranch: target, expectedBase: defaultBranch, taskId: task.taskId,
|
|
413
|
-
});
|
|
600
|
+
}, task.taskId);
|
|
414
601
|
entry.baseMismatch.set(prNumber, fp);
|
|
415
602
|
}
|
|
603
|
+
await clearPending(prNumber);
|
|
416
604
|
continue;
|
|
417
605
|
}
|
|
418
606
|
}
|
|
607
|
+
const adoptionFingerprint = [
|
|
608
|
+
task.taskId,
|
|
609
|
+
task.status ?? '',
|
|
610
|
+
task.phase ?? '',
|
|
611
|
+
task.signalToken ?? '',
|
|
612
|
+
task.reviewMode,
|
|
613
|
+
base.branch,
|
|
614
|
+
target,
|
|
615
|
+
String(row.headSha),
|
|
616
|
+
].join('\0');
|
|
617
|
+
const deferred = entry.adoptionDeferrals.get(prNumber);
|
|
618
|
+
if (deferred?.fingerprint === adoptionFingerprint) {
|
|
619
|
+
deferred.cycles += 1;
|
|
620
|
+
activeDeferrals.add(prNumber);
|
|
621
|
+
if (!deferred.interventionDelivered
|
|
622
|
+
&& deferred.cycles >= ADOPTION_DEFERRAL_INTERVENTION_CYCLES) {
|
|
623
|
+
await this.emit(entry, 'human.intervention', {
|
|
624
|
+
...base,
|
|
625
|
+
reason: 'pr-adoption-deferred',
|
|
626
|
+
taskId: task.taskId,
|
|
627
|
+
status: task.status,
|
|
628
|
+
phase: task.phase,
|
|
629
|
+
cycles: deferred.cycles,
|
|
630
|
+
}, task.taskId);
|
|
631
|
+
deferred.interventionDelivered = true;
|
|
632
|
+
}
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
entry.adoptionDeferrals.delete(prNumber);
|
|
419
636
|
await this.emit(entry, 'pr.created', {
|
|
420
637
|
...base, headSha: String(row.headSha), targetBranch: target,
|
|
421
638
|
...(typeof row.prAuthorId === 'string' ? { prAuthorId: row.prAuthorId } : {}),
|
|
422
|
-
});
|
|
639
|
+
}, task.taskId);
|
|
640
|
+
const adoptedTask = await this.opts.task(task.taskId);
|
|
641
|
+
if (adoptedTask?.prNumber !== prNumber) {
|
|
642
|
+
await entry.cursor.markAdoptionPending(task.taskId, prNumber);
|
|
643
|
+
pendingByPr.set(prNumber, task.taskId);
|
|
644
|
+
entry.adoptionDeferrals.set(prNumber, {
|
|
645
|
+
fingerprint: adoptionFingerprint,
|
|
646
|
+
cycles: 1,
|
|
647
|
+
interventionDelivered: false,
|
|
648
|
+
});
|
|
649
|
+
activeDeferrals.add(prNumber);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
await entry.cursor.markAdoptionDelivered(task.taskId, prNumber);
|
|
423
653
|
entry.baseMismatch.delete(prNumber);
|
|
654
|
+
entry.adoptionDeferrals.delete(prNumber);
|
|
424
655
|
}
|
|
425
656
|
catch (e) {
|
|
426
657
|
fail(`adopt pr#${prNumber}`, e);
|
|
@@ -430,10 +661,15 @@ export class PlatformPoller {
|
|
|
430
661
|
if (!seenPrs.has(pr))
|
|
431
662
|
entry.baseMismatch.delete(pr);
|
|
432
663
|
}
|
|
433
|
-
|
|
664
|
+
for (const pr of entry.adoptionDeferrals.keys()) {
|
|
665
|
+
if (!activeDeferrals.has(pr))
|
|
666
|
+
entry.adoptionDeferrals.delete(pr);
|
|
667
|
+
}
|
|
668
|
+
if (failures.length > failuresBefore)
|
|
434
669
|
return;
|
|
435
670
|
try {
|
|
436
|
-
|
|
671
|
+
this.assertEntryActive(entry);
|
|
672
|
+
await entry.cursor.commitListPrs(listedPrRows, scanStartedAt - entry.driver.visibilityLagMs);
|
|
437
673
|
}
|
|
438
674
|
catch (e) {
|
|
439
675
|
fail('listPrs cursor commit', e);
|
|
@@ -445,6 +681,7 @@ export class PlatformPoller {
|
|
|
445
681
|
let prRow;
|
|
446
682
|
try {
|
|
447
683
|
[prRow] = await entry.driver.runOp('prView', { prNumber });
|
|
684
|
+
this.assertEntryActive(entry);
|
|
448
685
|
}
|
|
449
686
|
catch (e) {
|
|
450
687
|
fail(`prView pr#${prNumber}`, e);
|
|
@@ -467,7 +704,7 @@ export class PlatformPoller {
|
|
|
467
704
|
taskId: task.taskId,
|
|
468
705
|
targetBranch: String(prRow.targetBranch),
|
|
469
706
|
...(task.expectedBase !== undefined ? { expectedBase: task.expectedBase } : {}),
|
|
470
|
-
});
|
|
707
|
+
}, task.taskId);
|
|
471
708
|
observed.bindingMismatch = binding.mismatch;
|
|
472
709
|
}
|
|
473
710
|
catch (e) {
|
|
@@ -482,7 +719,7 @@ export class PlatformPoller {
|
|
|
482
719
|
if (typeof prRow.mergedAt === 'string') {
|
|
483
720
|
if (observed.merged !== true) {
|
|
484
721
|
try {
|
|
485
|
-
await this.emit(entry, 'pr.merged', base);
|
|
722
|
+
await this.emit(entry, 'pr.merged', base, task.taskId);
|
|
486
723
|
observed.merged = true;
|
|
487
724
|
}
|
|
488
725
|
catch (e) {
|
|
@@ -492,7 +729,7 @@ export class PlatformPoller {
|
|
|
492
729
|
}
|
|
493
730
|
else if (observed.closedUnmerged !== true) {
|
|
494
731
|
try {
|
|
495
|
-
await this.emit(entry, 'pr.updated', { ...base, kind: 'closed-unmerged' });
|
|
732
|
+
await this.emit(entry, 'pr.updated', { ...base, kind: 'closed-unmerged' }, task.taskId);
|
|
496
733
|
observed.closedUnmerged = true;
|
|
497
734
|
}
|
|
498
735
|
catch (e) {
|
|
@@ -503,10 +740,14 @@ export class PlatformPoller {
|
|
|
503
740
|
return;
|
|
504
741
|
}
|
|
505
742
|
observed.closedUnmerged = false;
|
|
743
|
+
if (task.reviewMode === 'server') {
|
|
744
|
+
this.engine.dropCandidate(task.taskId, prNumber);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
506
747
|
const headSha = String(prRow.headSha);
|
|
507
748
|
if (task.latestHeadSha !== undefined && headSha !== task.latestHeadSha.toLowerCase() && observed.pushSha !== headSha) {
|
|
508
749
|
try {
|
|
509
|
-
await this.emit(entry, 'pr.updated', { ...base, kind: 'push', headSha });
|
|
750
|
+
await this.emit(entry, 'pr.updated', { ...base, kind: 'push', headSha }, task.taskId);
|
|
510
751
|
observed.pushSha = headSha;
|
|
511
752
|
}
|
|
512
753
|
catch (e) {
|
|
@@ -548,6 +789,7 @@ export class PlatformPoller {
|
|
|
548
789
|
// 评论源扫描最长可达分钟级,扫描期间 head/状态可被平台改写(TOCTOU):emit 前以
|
|
549
790
|
// 权威 prView 复核 open/!draft/绑定/锚点,失配清候选走 push/recheck,查询失败按瞬态重试。
|
|
550
791
|
const [freshRow] = await entry.driver.runOp('prView', { prNumber });
|
|
792
|
+
this.assertEntryActive(entry);
|
|
551
793
|
const freshEligible = freshRow !== undefined
|
|
552
794
|
&& this.bindingCheck(task, freshRow, defaultBranch) === undefined
|
|
553
795
|
&& freshRow.state === 'open' && freshRow.draft === false
|
|
@@ -558,6 +800,7 @@ export class PlatformPoller {
|
|
|
558
800
|
}
|
|
559
801
|
await this.emit(entry, 'review.submitted', {
|
|
560
802
|
...base,
|
|
803
|
+
source: 'platform-poller',
|
|
561
804
|
action: decision.kind === 'pass' ? 'APPROVE' : 'REQUEST_CHANGES',
|
|
562
805
|
headSha: decision.anchorSha,
|
|
563
806
|
currentHeadSha: String(freshRow.headSha),
|
|
@@ -566,7 +809,7 @@ export class PlatformPoller {
|
|
|
566
809
|
verdictToken: decision.token,
|
|
567
810
|
verdictConflict: decision.conflict,
|
|
568
811
|
verdictCarrier: decision.carrier,
|
|
569
|
-
});
|
|
812
|
+
}, task.taskId);
|
|
570
813
|
entry.deliveredVerdicts.set(obsKey, fp);
|
|
571
814
|
}
|
|
572
815
|
catch (e) {
|
|
@@ -648,7 +891,7 @@ export class PlatformPoller {
|
|
|
648
891
|
...(scan.sourceClass === 'threaded' && row.discussionId !== null && row.discussionId !== undefined
|
|
649
892
|
? { reviewCommentReply: true }
|
|
650
893
|
: {}),
|
|
651
|
-
});
|
|
894
|
+
}, task.taskId);
|
|
652
895
|
await entry.cursor.markDelivered(task.taskId, base.prNumber, scan.key, id, digest);
|
|
653
896
|
}
|
|
654
897
|
catch (e) {
|
|
@@ -658,6 +901,7 @@ export class PlatformPoller {
|
|
|
658
901
|
}
|
|
659
902
|
if (delivered) {
|
|
660
903
|
try {
|
|
904
|
+
this.assertEntryActive(entry);
|
|
661
905
|
await entry.cursor.commitSource(task.taskId, base.prNumber, scan.key, scan.rows, cutoff);
|
|
662
906
|
}
|
|
663
907
|
catch (e) {
|
|
@@ -677,8 +921,13 @@ export class PlatformPoller {
|
|
|
677
921
|
}
|
|
678
922
|
return scans;
|
|
679
923
|
}
|
|
680
|
-
async emit(entry, type, data) {
|
|
681
|
-
|
|
924
|
+
async emit(entry, type, data, taskId) {
|
|
925
|
+
this.assertEntryActive(entry);
|
|
926
|
+
await this.opts.onEvent(entry.projectId, { type, repo: entry.repoKey, data, taskId });
|
|
927
|
+
}
|
|
928
|
+
assertEntryActive(entry) {
|
|
929
|
+
if (entry.retired || !this.entries.includes(entry))
|
|
930
|
+
throw new EntryRetiredError();
|
|
682
931
|
}
|
|
683
932
|
}
|
|
684
933
|
//# sourceMappingURL=platform-poller.js.map
|