@wjarka/cezarion 0.14.9-dev.1009 → 0.14.9-dev.1011

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.
@@ -1313,7 +1313,7 @@ export class RunManager {
1313
1313
  }
1314
1314
  parkCiWait(runId, state) {
1315
1315
  const wait = this.store.getRun(runId)?.ciWait;
1316
- if (!wait || !state.session?.open || state.cancelled || state.pendingHumanAsk || this.ciWakeAdmitted.has(runId))
1316
+ if (!wait || !state.session?.open || state.cancelled || state.pendingHumanAsk)
1317
1317
  return false;
1318
1318
  if (wait.phase === 'registered')
1319
1319
  this.store.commitCiWait(runId, { ...wait, phase: 'parked' });
@@ -1331,10 +1331,21 @@ export class RunManager {
1331
1331
  queueCiWake(runId) {
1332
1332
  const run = this.store.getRun(runId);
1333
1333
  const wait = run?.ciWait;
1334
- if (this.disposed || this.recovering || !run || !wait?.result || !wait.wakeId || this.ciWakeQueuedAt.has(runId) ||
1335
- this.ciWakeAdmitted.has(runId) || !['running', 'waiting', 'queued'].includes(run.status) || run.stopping ||
1334
+ if (this.disposed || this.recovering || !run || !wait?.result || !wait.wakeId ||
1335
+ !['running', 'waiting', 'queued'].includes(run.status) || run.stopping ||
1336
1336
  this.hasPendingHumanAsk(runId) || this.workerExecutionStopped(runId) || this.executionBlockedByRootFinish(run))
1337
1337
  return;
1338
+ // Settlement ends the watcher. Keep a bounded-rate wake source until ACK,
1339
+ // withdrawal or termination, including refusals with no runner-ready callback.
1340
+ this.retryCiAdmission(runId);
1341
+ if (this.ciWakeAdmitted.has(runId)) {
1342
+ this.flushAgentInputs(runId);
1343
+ return;
1344
+ }
1345
+ if (this.ciWakeQueuedAt.has(runId)) {
1346
+ this.releaseSlot();
1347
+ return;
1348
+ }
1338
1349
  const state = this.active.get(runId);
1339
1350
  if (state) {
1340
1351
  if (!this.monitoring.has(runId) || state.atTurnBoundary !== state.session || state.pendingHumanAsk)
@@ -1581,7 +1592,9 @@ export class RunManager {
1581
1592
  */
1582
1593
  busySlots() {
1583
1594
  const ordinaryWaiting = this.waiting.size - this.monitoring.size;
1584
- const exemptMonitoring = Math.min(this.monitoring.size, this.semaphore.maxMonitoringSessions());
1595
+ // An admitted CI wake reserves its slot while remaining visibly parked until ACK.
1596
+ const admittedMonitoring = [...this.ciWakeAdmitted].filter(id => this.monitoring.has(id)).length;
1597
+ const exemptMonitoring = Math.min(this.monitoring.size - admittedMonitoring, this.semaphore.maxMonitoringSessions());
1585
1598
  return this.active.size + this.starting.size - ordinaryWaiting - exemptMonitoring - this.workerWaiting.size;
1586
1599
  }
1587
1600
  /** Epoch ms of this manager's oldest queued run (the semaphore's fairness
@@ -1673,7 +1686,7 @@ export class RunManager {
1673
1686
  const defaultRunner = anyHold ? (await loadConfig(this.repoRoot)).defaultRunner : undefined;
1674
1687
  // Removing one monitor above the exemption cap needs no new slot: it already holds one.
1675
1688
  const chargedCiWake = (id) => this.ciWakeQueuedAt.has(id) && this.monitoring.has(id) &&
1676
- this.monitoring.size > this.semaphore.maxMonitoringSessions();
1689
+ this.monitoring.size - [...this.ciWakeAdmitted].filter(id => this.monitoring.has(id)).length > this.semaphore.maxMonitoringSessions();
1677
1690
  while (this.queue.length > 0) {
1678
1691
  // FIFO among the runs that CAN start; a held one keeps its place in the queue rather
1679
1692
  // than being dequeued and re-queued (which would churn its position and its record).
@@ -1717,10 +1730,16 @@ export class RunManager {
1717
1730
  const state = this.active.get(runId);
1718
1731
  if (!state.pendingHumanAsk && this.store.getRun(runId)?.ciWait?.result && state.atTurnBoundary === state.session) {
1719
1732
  this.ciWakeAdmitted.add(runId);
1720
- this.waiting.delete(runId);
1721
- this.monitoring.delete(runId);
1722
1733
  this.flushAgentInputs(runId);
1723
1734
  }
1735
+ else if (this.store.getRun(runId)?.ciWait?.result) {
1736
+ // The runner may lose its boundary between queueing and admission.
1737
+ // Retain the wake, but do not spin on it in this pump pass.
1738
+ this.ciWakeQueuedAt.set(runId, Date.now());
1739
+ this.queue.push(runId);
1740
+ this.retryCiAdmission(runId);
1741
+ break;
1742
+ }
1724
1743
  continue;
1725
1744
  }
1726
1745
  if (this.ciWakeQueuedAt.delete(runId))
@@ -4208,6 +4227,13 @@ export class RunManager {
4208
4227
  throw new Error('CI observation must be delivered separately');
4209
4228
  this.store.commitCiWaitDelivery(runId, ciWakeId);
4210
4229
  this.ciWakeAdmitted.delete(runId);
4230
+ const retry = this.ciRetries.get(runId);
4231
+ if (retry)
4232
+ clearTimeout(retry);
4233
+ this.ciRetries.delete(runId);
4234
+ this.waiting.delete(runId);
4235
+ this.monitoring.delete(runId);
4236
+ this.store.updateRun(runId, { status: 'running', activity: undefined });
4211
4237
  }
4212
4238
  else {
4213
4239
  const deliveredAt = new Date().toISOString();
@@ -4376,6 +4402,10 @@ export class RunManager {
4376
4402
  this.workerWaiting.delete(runId);
4377
4403
  this.clearIdleTimer(state);
4378
4404
  this.clearMonitoringWakeTimer(state, runId);
4405
+ // A CI command can emit activity before its transport ACK. Keep its park
4406
+ // until the durable delivery checkpoint; admission already reserves capacity.
4407
+ if (this.store.getRun(runId)?.ciWait && this.ciWakeAdmitted.has(runId))
4408
+ return;
4379
4409
  this.waiting.delete(runId);
4380
4410
  this.monitoring.delete(runId);
4381
4411
  this.store.updateRun(runId, { status: 'running', activity: undefined });