baxian 1.2.61 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +7 -4
  3. package/dist/agent/bootstrap-poller.d.ts +1 -1
  4. package/dist/agent/bootstrap-poller.d.ts.map +1 -1
  5. package/dist/agent/bootstrap-poller.js.map +1 -1
  6. package/dist/agent/bootstrap.d.ts +1 -1
  7. package/dist/agent/bootstrap.d.ts.map +1 -1
  8. package/dist/agent/bootstrap.js +6 -2
  9. package/dist/agent/bootstrap.js.map +1 -1
  10. package/dist/agent/branch.d.ts +1 -3
  11. package/dist/agent/branch.d.ts.map +1 -1
  12. package/dist/agent/branch.js +2 -2
  13. package/dist/agent/branch.js.map +1 -1
  14. package/dist/agent/dispatch-reconciler.d.ts +0 -1
  15. package/dist/agent/dispatch-reconciler.d.ts.map +1 -1
  16. package/dist/agent/dispatch-reconciler.js +15 -49
  17. package/dist/agent/dispatch-reconciler.js.map +1 -1
  18. package/dist/agent/manager.d.ts +212 -90
  19. package/dist/agent/manager.d.ts.map +1 -1
  20. package/dist/agent/manager.js +2983 -1574
  21. package/dist/agent/manager.js.map +1 -1
  22. package/dist/agent/net-exec.d.ts +3 -0
  23. package/dist/agent/net-exec.d.ts.map +1 -1
  24. package/dist/agent/net-exec.js +6 -0
  25. package/dist/agent/net-exec.js.map +1 -1
  26. package/dist/agent/pane-streamer-manager.d.ts +6 -6
  27. package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
  28. package/dist/agent/pane-streamer-manager.js.map +1 -1
  29. package/dist/agent/pane-streamer.d.ts +2 -2
  30. package/dist/agent/pane-streamer.d.ts.map +1 -1
  31. package/dist/agent/pane-streamer.js +46 -36
  32. package/dist/agent/pane-streamer.js.map +1 -1
  33. package/dist/agent/phase-signal-watcher.d.ts +20 -2
  34. package/dist/agent/phase-signal-watcher.d.ts.map +1 -1
  35. package/dist/agent/phase-signal-watcher.js +135 -17
  36. package/dist/agent/phase-signal-watcher.js.map +1 -1
  37. package/dist/agent/phase-signal.d.ts +9 -7
  38. package/dist/agent/phase-signal.d.ts.map +1 -1
  39. package/dist/agent/phase-signal.js +18 -9
  40. package/dist/agent/phase-signal.js.map +1 -1
  41. package/dist/agent/preflight.d.ts +4 -1
  42. package/dist/agent/preflight.d.ts.map +1 -1
  43. package/dist/agent/preflight.js +46 -45
  44. package/dist/agent/preflight.js.map +1 -1
  45. package/dist/agent/prompt.d.ts +8 -5
  46. package/dist/agent/prompt.d.ts.map +1 -1
  47. package/dist/agent/prompt.js +34 -30
  48. package/dist/agent/prompt.js.map +1 -1
  49. package/dist/agent/repo-store.d.ts +8 -1
  50. package/dist/agent/repo-store.d.ts.map +1 -1
  51. package/dist/agent/repo-store.js +70 -15
  52. package/dist/agent/repo-store.js.map +1 -1
  53. package/dist/agent/review-transport.d.ts +20 -1
  54. package/dist/agent/review-transport.d.ts.map +1 -1
  55. package/dist/agent/review-transport.js +68 -20
  56. package/dist/agent/review-transport.js.map +1 -1
  57. package/dist/agent/root-agent-runtime.d.ts +80 -0
  58. package/dist/agent/root-agent-runtime.d.ts.map +1 -0
  59. package/dist/agent/root-agent-runtime.js +635 -0
  60. package/dist/agent/root-agent-runtime.js.map +1 -0
  61. package/dist/agent/root-recovery-coordinator.d.ts +125 -0
  62. package/dist/agent/root-recovery-coordinator.d.ts.map +1 -0
  63. package/dist/agent/root-recovery-coordinator.js +1579 -0
  64. package/dist/agent/root-recovery-coordinator.js.map +1 -0
  65. package/dist/agent/runner.d.ts +1 -0
  66. package/dist/agent/runner.d.ts.map +1 -1
  67. package/dist/agent/runner.js +103 -7
  68. package/dist/agent/runner.js.map +1 -1
  69. package/dist/agent/tmux-probe-poller.d.ts +1 -0
  70. package/dist/agent/tmux-probe-poller.d.ts.map +1 -1
  71. package/dist/agent/tmux-probe-poller.js +16 -10
  72. package/dist/agent/tmux-probe-poller.js.map +1 -1
  73. package/dist/agent/tmux.d.ts +3 -0
  74. package/dist/agent/tmux.d.ts.map +1 -1
  75. package/dist/agent/tmux.js +11 -5
  76. package/dist/agent/tmux.js.map +1 -1
  77. package/dist/api/agents.d.ts.map +1 -1
  78. package/dist/api/agents.js +42 -1
  79. package/dist/api/agents.js.map +1 -1
  80. package/dist/api/config.d.ts.map +1 -1
  81. package/dist/api/config.js +88 -17
  82. package/dist/api/config.js.map +1 -1
  83. package/dist/api/hosts.d.ts.map +1 -1
  84. package/dist/api/hosts.js +71 -15
  85. package/dist/api/hosts.js.map +1 -1
  86. package/dist/api/platform-guard.d.ts +10 -2
  87. package/dist/api/platform-guard.d.ts.map +1 -1
  88. package/dist/api/platform-guard.js +112 -10
  89. package/dist/api/platform-guard.js.map +1 -1
  90. package/dist/api/projects.d.ts.map +1 -1
  91. package/dist/api/projects.js +57 -16
  92. package/dist/api/projects.js.map +1 -1
  93. package/dist/api/tasks.d.ts.map +1 -1
  94. package/dist/api/tasks.js +21 -36
  95. package/dist/api/tasks.js.map +1 -1
  96. package/dist/app.d.ts +6 -4
  97. package/dist/app.d.ts.map +1 -1
  98. package/dist/app.js +6 -0
  99. package/dist/app.js.map +1 -1
  100. package/dist/cli.d.ts +1 -1
  101. package/dist/cli.d.ts.map +1 -1
  102. package/dist/cli.js +49 -7
  103. package/dist/cli.js.map +1 -1
  104. package/dist/config/hot-reload.d.ts +11 -2
  105. package/dist/config/hot-reload.d.ts.map +1 -1
  106. package/dist/config/hot-reload.js +43 -8
  107. package/dist/config/hot-reload.js.map +1 -1
  108. package/dist/config/loader.d.ts.map +1 -1
  109. package/dist/config/loader.js +23 -2
  110. package/dist/config/loader.js.map +1 -1
  111. package/dist/config/normalizer.d.ts.map +1 -1
  112. package/dist/config/normalizer.js +18 -0
  113. package/dist/config/normalizer.js.map +1 -1
  114. package/dist/config/validator.d.ts +3 -1
  115. package/dist/config/validator.d.ts.map +1 -1
  116. package/dist/config/validator.js +247 -54
  117. package/dist/config/validator.js.map +1 -1
  118. package/dist/event/handlers.d.ts.map +1 -1
  119. package/dist/event/handlers.js +615 -1069
  120. package/dist/event/handlers.js.map +1 -1
  121. package/dist/event/server-handlers.d.ts.map +1 -1
  122. package/dist/event/server-handlers.js +787 -487
  123. package/dist/event/server-handlers.js.map +1 -1
  124. package/dist/index.d.ts +4 -1
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +99 -79
  127. package/dist/index.js.map +1 -1
  128. package/dist/platform/command-renderer.d.ts +1 -0
  129. package/dist/platform/command-renderer.d.ts.map +1 -1
  130. package/dist/platform/command-renderer.js +16 -2
  131. package/dist/platform/command-renderer.js.map +1 -1
  132. package/dist/platform/comment-cursor.d.ts +9 -0
  133. package/dist/platform/comment-cursor.d.ts.map +1 -1
  134. package/dist/platform/comment-cursor.js +63 -5
  135. package/dist/platform/comment-cursor.js.map +1 -1
  136. package/dist/platform/driver-host.d.ts.map +1 -1
  137. package/dist/platform/driver-host.js +0 -2
  138. package/dist/platform/driver-host.js.map +1 -1
  139. package/dist/platform/driver-spec.d.ts.map +1 -1
  140. package/dist/platform/driver-spec.js +17 -6
  141. package/dist/platform/driver-spec.js.map +1 -1
  142. package/dist/platform/feedback.d.ts.map +1 -1
  143. package/dist/platform/feedback.js +0 -9
  144. package/dist/platform/feedback.js.map +1 -1
  145. package/dist/platform/git-driver.d.ts +3 -0
  146. package/dist/platform/git-driver.d.ts.map +1 -1
  147. package/dist/platform/git-driver.js +45 -3
  148. package/dist/platform/git-driver.js.map +1 -1
  149. package/dist/platform/platform-poller.d.ts +18 -5
  150. package/dist/platform/platform-poller.d.ts.map +1 -1
  151. package/dist/platform/platform-poller.js +283 -38
  152. package/dist/platform/platform-poller.js.map +1 -1
  153. package/dist/platform/plugins/github/baxian-plugin.json +1 -1
  154. package/dist/platform/plugins/github/driver.json +14 -3
  155. package/dist/platform/plugins/github/skills/baxian-cli-gh/SKILL.md +7 -6
  156. package/dist/platform/plugins/github/skills/baxian-cli-gh/agents/openai.yaml +2 -0
  157. package/dist/platform/pr-binding.d.ts.map +1 -1
  158. package/dist/platform/pr-binding.js +0 -2
  159. package/dist/platform/pr-binding.js.map +1 -1
  160. package/dist/platform/pr-conversation-cache.d.ts.map +1 -0
  161. package/dist/platform/pr-conversation-cache.js.map +1 -0
  162. package/dist/platform/row-schema.d.ts.map +1 -1
  163. package/dist/platform/row-schema.js +1 -0
  164. package/dist/platform/row-schema.js.map +1 -1
  165. package/dist/platform/startup.d.ts +29 -1
  166. package/dist/platform/startup.d.ts.map +1 -1
  167. package/dist/platform/startup.js +82 -4
  168. package/dist/platform/startup.js.map +1 -1
  169. package/dist/platform/types.d.ts +2 -0
  170. package/dist/platform/types.d.ts.map +1 -1
  171. package/dist/platform/types.js +6 -4
  172. package/dist/platform/types.js.map +1 -1
  173. package/dist/platform/verdict-engine.d.ts.map +1 -1
  174. package/dist/platform/verdict-engine.js +0 -2
  175. package/dist/platform/verdict-engine.js.map +1 -1
  176. package/dist/shared/constants.d.ts +7 -1
  177. package/dist/shared/constants.d.ts.map +1 -1
  178. package/dist/shared/constants.js +5 -0
  179. package/dist/shared/constants.js.map +1 -1
  180. package/dist/shared/types.d.ts +98 -7
  181. package/dist/shared/types.d.ts.map +1 -1
  182. package/dist/shared/types.js +18 -0
  183. package/dist/shared/types.js.map +1 -1
  184. package/dist/skills/baxian-pr-feedback/SKILL.md +5 -15
  185. package/dist/skills/baxian-pr-recheck/SKILL.md +5 -44
  186. package/dist/skills/baxian-pr-review/SKILL.md +6 -43
  187. package/dist/skills/baxian-research/SKILL.md +1 -1
  188. package/dist/skills/baxian-server-feedback/SKILL.md +7 -5
  189. package/dist/skills/baxian-server-review/SKILL.md +2 -2
  190. package/dist/skills/baxian-signals/SKILL.md +11 -7
  191. package/dist/skills/baxian-task-check/SKILL.md +3 -5
  192. package/dist/state/agent-store.js +0 -2
  193. package/dist/state/agent-store.js.map +1 -1
  194. package/dist/state/init.d.ts.map +1 -1
  195. package/dist/state/init.js +1 -0
  196. package/dist/state/init.js.map +1 -1
  197. package/dist/state/lock.d.ts +6 -0
  198. package/dist/state/lock.d.ts.map +1 -1
  199. package/dist/state/lock.js +8 -0
  200. package/dist/state/lock.js.map +1 -1
  201. package/dist/state/review-store.d.ts +21 -1
  202. package/dist/state/review-store.d.ts.map +1 -1
  203. package/dist/state/review-store.js +132 -0
  204. package/dist/state/review-store.js.map +1 -1
  205. package/dist/state/root-recovery-store.d.ts +107 -0
  206. package/dist/state/root-recovery-store.d.ts.map +1 -0
  207. package/dist/state/root-recovery-store.js +400 -0
  208. package/dist/state/root-recovery-store.js.map +1 -0
  209. package/dist/state/task-store.d.ts +6 -0
  210. package/dist/state/task-store.d.ts.map +1 -1
  211. package/dist/state/task-store.js +266 -21
  212. package/dist/state/task-store.js.map +1 -1
  213. package/dist/terminal/attach.d.ts +3 -3
  214. package/dist/terminal/attach.d.ts.map +1 -1
  215. package/dist/terminal/attach.js.map +1 -1
  216. package/dist/terminal/stream-ws.d.ts.map +1 -1
  217. package/dist/terminal/stream-ws.js.map +1 -1
  218. package/dist/web/assets/index-BXfgaFNm.js +18 -0
  219. package/dist/web/index.html +1 -1
  220. package/package.json +1 -1
  221. package/dist/github/mapper.d.ts +0 -9
  222. package/dist/github/mapper.d.ts.map +0 -1
  223. package/dist/github/mapper.js +0 -25
  224. package/dist/github/mapper.js.map +0 -1
  225. package/dist/github/poller.d.ts +0 -73
  226. package/dist/github/poller.d.ts.map +0 -1
  227. package/dist/github/poller.js +0 -556
  228. package/dist/github/poller.js.map +0 -1
  229. package/dist/github/pr-conversation-cache.d.ts.map +0 -1
  230. package/dist/github/pr-conversation-cache.js.map +0 -1
  231. package/dist/github/pr-conversation.d.ts +0 -7
  232. package/dist/github/pr-conversation.d.ts.map +0 -1
  233. package/dist/github/pr-conversation.js +0 -175
  234. package/dist/github/pr-conversation.js.map +0 -1
  235. package/dist/github/resolver.d.ts +0 -8
  236. package/dist/github/resolver.d.ts.map +0 -1
  237. package/dist/github/resolver.js +0 -28
  238. package/dist/github/resolver.js.map +0 -1
  239. package/dist/state/post-approve-store.d.ts +0 -23
  240. package/dist/state/post-approve-store.d.ts.map +0 -1
  241. package/dist/state/post-approve-store.js +0 -82
  242. package/dist/state/post-approve-store.js.map +0 -1
  243. package/dist/web/assets/index-CUJtd0bC.js +0 -16
  244. /package/dist/{github → platform}/pr-conversation-cache.d.ts +0 -0
  245. /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
- remove(projectId) {
65
- const i = this.entries.findIndex(e => e.projectId === projectId);
66
- if (i === -1)
67
- return false;
68
- this.entries.splice(i, 1);
69
- return true;
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
- if (entry.rateLimit !== undefined && this.now() < entry.rateLimit.until)
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
- const t = byId.get(gen);
213
- if (t === undefined || t.terminal) {
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,14 +441,11 @@ 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
  }
316
- // PR 收编后仍可被 retarget/改写:生命周期与裁决事件产出前对 fresh prView 复核绑定三元
317
- // (branch/fork/target,spec §7 fresh prView 完整谓词核验),失配转 intervention 并停子轮询。
318
- // 期望值缺失即无法核验:预设「无法核验 = 匹配」会给 retarget 后的 PR 继续发 push/merged/裁决
319
- // ——task.branch 缺失按失配交人工(任务视图结构异常),base 期望双缺失按不可核验静默停
320
- // (projectView 故障已在健康度,恢复即自愈)。
448
+ // PR 收编后仍可被 retarget/改写:产出生命周期/裁决事件前按 fresh prView 复核绑定,「无法核验」不得视同「匹配」。
321
449
  bindingCheck(task, prRow, defaultBranch) {
322
450
  return checkPrBinding(prRow, {
323
451
  branch: task.branch,
@@ -341,19 +469,56 @@ export class PlatformPoller {
341
469
  return vt !== undefined && vt < prsCursor.watermarkTime;
342
470
  });
343
471
  });
472
+ this.assertEntryActive(entry);
344
473
  }
345
474
  catch (e) {
346
475
  fail('listPrs', e);
347
476
  return;
348
477
  }
478
+ const listedPrRows = [...prRows];
479
+ const pendingByPr = new Map();
480
+ const activeDeferrals = new Set();
481
+ for (const pending of entry.cursor.pendingAdoptions()) {
482
+ let task;
483
+ try {
484
+ task = await this.opts.task(pending.taskId);
485
+ this.assertEntryActive(entry);
486
+ if (task === null || task.terminal || (task.prNumber !== undefined && task.prNumber !== pending.prNumber)) {
487
+ await entry.cursor.clearAdoptionPending(pending.taskId, pending.prNumber);
488
+ continue;
489
+ }
490
+ if (task.prNumber === pending.prNumber) {
491
+ await entry.cursor.markAdoptionDelivered(pending.taskId, pending.prNumber);
492
+ continue;
493
+ }
494
+ byId.set(task.taskId, task);
495
+ pendingByPr.set(pending.prNumber, pending.taskId);
496
+ if (prRows.some(row => row.prNumber === pending.prNumber))
497
+ continue;
498
+ const [row] = await entry.driver.runOp('prView', { prNumber: pending.prNumber });
499
+ this.assertEntryActive(entry);
500
+ if (row !== undefined)
501
+ prRows.push({ ...row, prNumber: pending.prNumber });
502
+ }
503
+ catch (e) {
504
+ activeDeferrals.add(pending.prNumber);
505
+ fail(`retry adoption pr#${pending.prNumber}`, e);
506
+ }
507
+ }
349
508
  const boundByPr = new Map();
350
509
  for (const t of tasks) {
351
510
  if (!t.terminal && t.prNumber !== undefined)
352
511
  boundByPr.set(t.prNumber, t);
353
512
  }
354
513
  const failuresBefore = failures.length;
355
- let deferredAdoptions = 0;
356
514
  const seenPrs = new Set();
515
+ const clearPending = async (prNumber) => {
516
+ const taskId = pendingByPr.get(prNumber);
517
+ if (taskId === undefined)
518
+ return;
519
+ await entry.cursor.clearAdoptionPending(taskId, prNumber);
520
+ pendingByPr.delete(prNumber);
521
+ };
357
522
  for (const row of prRows) {
358
523
  const prNumber = row.prNumber;
359
524
  seenPrs.add(prNumber);
@@ -364,10 +529,11 @@ export class PlatformPoller {
364
529
  };
365
530
  const boundTask = boundByPr.get(prNumber);
366
531
  if (boundTask !== undefined) {
532
+ await clearPending(prNumber);
367
533
  // reopen 是显式 durable 观察:closed-unmerged 锚在 TaskState,主轮询见重开即合成(spec §6)。
368
534
  if (boundTask.closedUnmergedAnchor === true && row.state === 'open') {
369
535
  try {
370
- await this.emit(entry, 'pr.updated', { ...base, kind: 'reopened' });
536
+ await this.emit(entry, 'pr.updated', { ...base, kind: 'reopened' }, boundTask.taskId);
371
537
  entry.observedPr.delete(`${boundTask.taskId}:${prNumber}`);
372
538
  }
373
539
  catch (e) {
@@ -376,31 +542,48 @@ export class PlatformPoller {
376
542
  }
377
543
  continue;
378
544
  }
379
- if (row.state !== 'open' || row.draft === true)
545
+ if (row.state !== 'open' || row.draft === true) {
546
+ await clearPending(prNumber);
380
547
  continue;
381
- if (!base.branch.startsWith(BRANCH_PREFIX))
548
+ }
549
+ if (!base.branch.startsWith(BRANCH_PREFIX)) {
550
+ await clearPending(prNumber);
382
551
  continue;
552
+ }
383
553
  const task = byId.get(base.branch.slice(BRANCH_PREFIX.length));
384
- if (task === undefined || task.terminal || task.prNumber !== undefined)
554
+ if (task === undefined || task.terminal || task.prNumber !== undefined) {
555
+ await clearPending(prNumber);
385
556
  continue;
557
+ }
558
+ const pendingTaskId = pendingByPr.get(prNumber);
559
+ if (pendingTaskId !== undefined && pendingTaskId !== task.taskId)
560
+ await clearPending(prNumber);
386
561
  // 统一谓词 branch === task.branch:branch 缺失的任务视图无从核验绑定,收编一并 fail closed
387
562
  // (与子轮询 bindingCheck 对称——「无法核验」不是「匹配」)。
388
- if (task.branch !== base.branch)
563
+ if (task.branch !== base.branch) {
564
+ await clearPending(prNumber);
389
565
  continue;
566
+ }
390
567
  if (row.sourceProjectId === null || row.sourceProjectId === undefined
391
- || row.sourceProjectId !== row.targetProjectId)
568
+ || row.sourceProjectId !== row.targetProjectId) {
569
+ await clearPending(prNumber);
392
570
  continue;
571
+ }
393
572
  const target = String(row.targetBranch);
573
+ if (entry.cursor.isAdoptionDelivered(task.taskId, prNumber))
574
+ continue;
394
575
  try {
395
576
  if (task.expectedBase !== undefined) {
396
- if (target !== task.expectedBase)
577
+ if (target !== task.expectedBase) {
578
+ await clearPending(prNumber);
397
579
  continue;
580
+ }
398
581
  }
399
582
  else {
400
583
  if (defaultBranch === undefined) {
401
- // 收编推迟必须同时冻结水位:否则该 PR 被水位判「已处理」,projectView 恢复后
402
- // 它已落在停止页之外、永不再被扫描。
403
- deferredAdoptions += 1;
584
+ await entry.cursor.markAdoptionPending(task.taskId, prNumber);
585
+ pendingByPr.set(prNumber, task.taskId);
586
+ activeDeferrals.add(prNumber);
404
587
  continue;
405
588
  }
406
589
  if (target !== defaultBranch) {
@@ -410,17 +593,61 @@ export class PlatformPoller {
410
593
  if (entry.baseMismatch.get(prNumber) !== fp) {
411
594
  await this.emit(entry, 'human.intervention', {
412
595
  ...base, reason: 'base-mismatch', targetBranch: target, expectedBase: defaultBranch, taskId: task.taskId,
413
- });
596
+ }, task.taskId);
414
597
  entry.baseMismatch.set(prNumber, fp);
415
598
  }
599
+ await clearPending(prNumber);
416
600
  continue;
417
601
  }
418
602
  }
603
+ const adoptionFingerprint = [
604
+ task.taskId,
605
+ task.status ?? '',
606
+ task.phase ?? '',
607
+ task.signalToken ?? '',
608
+ task.reviewMode,
609
+ base.branch,
610
+ target,
611
+ String(row.headSha),
612
+ ].join('\0');
613
+ const deferred = entry.adoptionDeferrals.get(prNumber);
614
+ if (deferred?.fingerprint === adoptionFingerprint) {
615
+ deferred.cycles += 1;
616
+ activeDeferrals.add(prNumber);
617
+ if (!deferred.interventionDelivered
618
+ && deferred.cycles >= ADOPTION_DEFERRAL_INTERVENTION_CYCLES) {
619
+ await this.emit(entry, 'human.intervention', {
620
+ ...base,
621
+ reason: 'pr-adoption-deferred',
622
+ taskId: task.taskId,
623
+ status: task.status,
624
+ phase: task.phase,
625
+ cycles: deferred.cycles,
626
+ }, task.taskId);
627
+ deferred.interventionDelivered = true;
628
+ }
629
+ continue;
630
+ }
631
+ entry.adoptionDeferrals.delete(prNumber);
419
632
  await this.emit(entry, 'pr.created', {
420
633
  ...base, headSha: String(row.headSha), targetBranch: target,
421
634
  ...(typeof row.prAuthorId === 'string' ? { prAuthorId: row.prAuthorId } : {}),
422
- });
635
+ }, task.taskId);
636
+ const adoptedTask = await this.opts.task(task.taskId);
637
+ if (adoptedTask?.prNumber !== prNumber) {
638
+ await entry.cursor.markAdoptionPending(task.taskId, prNumber);
639
+ pendingByPr.set(prNumber, task.taskId);
640
+ entry.adoptionDeferrals.set(prNumber, {
641
+ fingerprint: adoptionFingerprint,
642
+ cycles: 1,
643
+ interventionDelivered: false,
644
+ });
645
+ activeDeferrals.add(prNumber);
646
+ continue;
647
+ }
648
+ await entry.cursor.markAdoptionDelivered(task.taskId, prNumber);
423
649
  entry.baseMismatch.delete(prNumber);
650
+ entry.adoptionDeferrals.delete(prNumber);
424
651
  }
425
652
  catch (e) {
426
653
  fail(`adopt pr#${prNumber}`, e);
@@ -430,10 +657,15 @@ export class PlatformPoller {
430
657
  if (!seenPrs.has(pr))
431
658
  entry.baseMismatch.delete(pr);
432
659
  }
433
- if (failures.length > failuresBefore || deferredAdoptions > 0)
660
+ for (const pr of entry.adoptionDeferrals.keys()) {
661
+ if (!activeDeferrals.has(pr))
662
+ entry.adoptionDeferrals.delete(pr);
663
+ }
664
+ if (failures.length > failuresBefore)
434
665
  return;
435
666
  try {
436
- await entry.cursor.commitListPrs(prRows, scanStartedAt - entry.driver.visibilityLagMs);
667
+ this.assertEntryActive(entry);
668
+ await entry.cursor.commitListPrs(listedPrRows, scanStartedAt - entry.driver.visibilityLagMs);
437
669
  }
438
670
  catch (e) {
439
671
  fail('listPrs cursor commit', e);
@@ -445,6 +677,7 @@ export class PlatformPoller {
445
677
  let prRow;
446
678
  try {
447
679
  [prRow] = await entry.driver.runOp('prView', { prNumber });
680
+ this.assertEntryActive(entry);
448
681
  }
449
682
  catch (e) {
450
683
  fail(`prView pr#${prNumber}`, e);
@@ -467,7 +700,7 @@ export class PlatformPoller {
467
700
  taskId: task.taskId,
468
701
  targetBranch: String(prRow.targetBranch),
469
702
  ...(task.expectedBase !== undefined ? { expectedBase: task.expectedBase } : {}),
470
- });
703
+ }, task.taskId);
471
704
  observed.bindingMismatch = binding.mismatch;
472
705
  }
473
706
  catch (e) {
@@ -482,7 +715,7 @@ export class PlatformPoller {
482
715
  if (typeof prRow.mergedAt === 'string') {
483
716
  if (observed.merged !== true) {
484
717
  try {
485
- await this.emit(entry, 'pr.merged', base);
718
+ await this.emit(entry, 'pr.merged', base, task.taskId);
486
719
  observed.merged = true;
487
720
  }
488
721
  catch (e) {
@@ -492,7 +725,7 @@ export class PlatformPoller {
492
725
  }
493
726
  else if (observed.closedUnmerged !== true) {
494
727
  try {
495
- await this.emit(entry, 'pr.updated', { ...base, kind: 'closed-unmerged' });
728
+ await this.emit(entry, 'pr.updated', { ...base, kind: 'closed-unmerged' }, task.taskId);
496
729
  observed.closedUnmerged = true;
497
730
  }
498
731
  catch (e) {
@@ -503,10 +736,14 @@ export class PlatformPoller {
503
736
  return;
504
737
  }
505
738
  observed.closedUnmerged = false;
739
+ if (task.reviewMode === 'server') {
740
+ this.engine.dropCandidate(task.taskId, prNumber);
741
+ return;
742
+ }
506
743
  const headSha = String(prRow.headSha);
507
744
  if (task.latestHeadSha !== undefined && headSha !== task.latestHeadSha.toLowerCase() && observed.pushSha !== headSha) {
508
745
  try {
509
- await this.emit(entry, 'pr.updated', { ...base, kind: 'push', headSha });
746
+ await this.emit(entry, 'pr.updated', { ...base, kind: 'push', headSha }, task.taskId);
510
747
  observed.pushSha = headSha;
511
748
  }
512
749
  catch (e) {
@@ -548,6 +785,7 @@ export class PlatformPoller {
548
785
  // 评论源扫描最长可达分钟级,扫描期间 head/状态可被平台改写(TOCTOU):emit 前以
549
786
  // 权威 prView 复核 open/!draft/绑定/锚点,失配清候选走 push/recheck,查询失败按瞬态重试。
550
787
  const [freshRow] = await entry.driver.runOp('prView', { prNumber });
788
+ this.assertEntryActive(entry);
551
789
  const freshEligible = freshRow !== undefined
552
790
  && this.bindingCheck(task, freshRow, defaultBranch) === undefined
553
791
  && freshRow.state === 'open' && freshRow.draft === false
@@ -558,6 +796,7 @@ export class PlatformPoller {
558
796
  }
559
797
  await this.emit(entry, 'review.submitted', {
560
798
  ...base,
799
+ source: 'platform-poller',
561
800
  action: decision.kind === 'pass' ? 'APPROVE' : 'REQUEST_CHANGES',
562
801
  headSha: decision.anchorSha,
563
802
  currentHeadSha: String(freshRow.headSha),
@@ -566,7 +805,7 @@ export class PlatformPoller {
566
805
  verdictToken: decision.token,
567
806
  verdictConflict: decision.conflict,
568
807
  verdictCarrier: decision.carrier,
569
- });
808
+ }, task.taskId);
570
809
  entry.deliveredVerdicts.set(obsKey, fp);
571
810
  }
572
811
  catch (e) {
@@ -648,7 +887,7 @@ export class PlatformPoller {
648
887
  ...(scan.sourceClass === 'threaded' && row.discussionId !== null && row.discussionId !== undefined
649
888
  ? { reviewCommentReply: true }
650
889
  : {}),
651
- });
890
+ }, task.taskId);
652
891
  await entry.cursor.markDelivered(task.taskId, base.prNumber, scan.key, id, digest);
653
892
  }
654
893
  catch (e) {
@@ -658,6 +897,7 @@ export class PlatformPoller {
658
897
  }
659
898
  if (delivered) {
660
899
  try {
900
+ this.assertEntryActive(entry);
661
901
  await entry.cursor.commitSource(task.taskId, base.prNumber, scan.key, scan.rows, cutoff);
662
902
  }
663
903
  catch (e) {
@@ -677,8 +917,13 @@ export class PlatformPoller {
677
917
  }
678
918
  return scans;
679
919
  }
680
- async emit(entry, type, data) {
681
- await this.opts.onEvent(entry.projectId, { type, repo: entry.repoKey, data });
920
+ async emit(entry, type, data, taskId) {
921
+ this.assertEntryActive(entry);
922
+ await this.opts.onEvent(entry.projectId, { type, repo: entry.repoKey, data, taskId });
923
+ }
924
+ assertEntryActive(entry) {
925
+ if (entry.retired || !this.entries.includes(entry))
926
+ throw new EntryRetiredError();
682
927
  }
683
928
  }
684
929
  //# sourceMappingURL=platform-poller.js.map