kantban-cli 0.1.40 → 0.1.42

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.
@@ -12,7 +12,7 @@ import {
12
12
  generateMcpConfig,
13
13
  parseJsonFromLlmOutput,
14
14
  parseStuckDetectionResponse
15
- } from "./chunk-FOVFBT7C.js";
15
+ } from "./chunk-SGDJZXT6.js";
16
16
  import {
17
17
  LoopCheckpointSchema,
18
18
  VerdictSchema,
@@ -469,6 +469,7 @@ function parseReplannerResponse(raw) {
469
469
  }
470
470
 
471
471
  // src/lib/orchestrator.ts
472
+ var ACTIVE_LOOP_ZOMBIE_TTL_MS = 90 * 60 * 1e3;
472
473
  function classifyTier(input) {
473
474
  if (input.invocationTier === "light") return "light";
474
475
  if (input.invocationTier === "heavy") return "heavy";
@@ -885,6 +886,23 @@ var PipelineOrchestrator = class {
885
886
  }
886
887
  return;
887
888
  }
889
+ const nowForSweep = Date.now();
890
+ for (const [ticketId, loop] of Array.from(this.activeLoops)) {
891
+ const age = nowForSweep - loop.startedAt;
892
+ if (age > ACTIVE_LOOP_ZOMBIE_TTL_MS) {
893
+ console.error(
894
+ ` [watchdog] Evicting zombie active loop for ticket ${ticketId} in column ${loop.columnId} (age ${String(Math.round(age / 6e4))}min)`
895
+ );
896
+ try {
897
+ loop.abort.abort();
898
+ } catch {
899
+ }
900
+ this.activeLoops.delete(ticketId);
901
+ this.spawning.delete(ticketId);
902
+ this.completing.delete(ticketId);
903
+ this.abortedForMove.delete(ticketId);
904
+ }
905
+ }
888
906
  this.knownTickets.clear();
889
907
  for (const ticketId of this.activeLoops.keys()) {
890
908
  this.knownTickets.add(ticketId);
@@ -1147,7 +1165,7 @@ var PipelineOrchestrator = class {
1147
1165
  })
1148
1166
  );
1149
1167
  const abort = new AbortController();
1150
- this.activeLoops.set(ticketId, { columnId, promise, abort });
1168
+ this.activeLoops.set(ticketId, { columnId, promise, abort, startedAt: Date.now() });
1151
1169
  this.lastFiredAt.set(columnId, /* @__PURE__ */ new Date());
1152
1170
  void promise.then(
1153
1171
  (result) => this.onLoopComplete(ticketId, columnId, result).catch((err) => {
@@ -1173,7 +1191,7 @@ var PipelineOrchestrator = class {
1173
1191
  placeholder.catch(() => {
1174
1192
  });
1175
1193
  const abort = new AbortController();
1176
- this.activeLoops.set(ticketId, { columnId, promise: placeholder, abort });
1194
+ this.activeLoops.set(ticketId, { columnId, promise: placeholder, abort, startedAt: Date.now() });
1177
1195
  this.lastFiredAt.set(columnId, /* @__PURE__ */ new Date());
1178
1196
  void readCheckpoint(
1179
1197
  {
@@ -1244,7 +1262,7 @@ var PipelineOrchestrator = class {
1244
1262
  const abort = new AbortController();
1245
1263
  loopConfig.abortSignal = abort.signal;
1246
1264
  const promise = this.deps.startLoop(ticketId, columnId, loopConfig);
1247
- this.activeLoops.set(ticketId, { columnId, promise, abort });
1265
+ this.activeLoops.set(ticketId, { columnId, promise, abort, startedAt: Date.now() });
1248
1266
  this.lastFiredAt.set(columnId, /* @__PURE__ */ new Date());
1249
1267
  void promise.then(
1250
1268
  (result) => this.onLoopComplete(ticketId, columnId, result).catch((err) => {
@@ -2334,6 +2352,14 @@ var PipelineWsClient = class _PipelineWsClient {
2334
2352
  reconnectAttempt = 0;
2335
2353
  lastPong = 0;
2336
2354
  stopped = false;
2355
+ /**
2356
+ * In-flight `connect()` promise. Prevents concurrent callers (the internal
2357
+ * reconnect timer + the orchestrator's rescan-cycle `tryReconnect()`) from
2358
+ * racing to open two WebSockets at once, which leaks the first one's
2359
+ * listeners and ping timer and causes duplicate board events to be
2360
+ * delivered to `onEvent`.
2361
+ */
2362
+ connectInFlight = null;
2337
2363
  sendBuffer = [];
2338
2364
  static CRITICAL_TYPES = /* @__PURE__ */ new Set([
2339
2365
  "pipeline:session-start",
@@ -2357,6 +2383,15 @@ var PipelineWsClient = class _PipelineWsClient {
2357
2383
  return this.ws?.readyState === WebSocket.OPEN;
2358
2384
  }
2359
2385
  async connect() {
2386
+ if (this.connectInFlight) return this.connectInFlight;
2387
+ this.connectInFlight = this.doConnect();
2388
+ this.connectInFlight.finally(() => {
2389
+ this.connectInFlight = null;
2390
+ }).catch(() => {
2391
+ });
2392
+ return this.connectInFlight;
2393
+ }
2394
+ async doConnect() {
2360
2395
  if (this.ws) {
2361
2396
  try {
2362
2397
  this.ws.removeAllListeners();
@@ -2440,6 +2475,7 @@ var PipelineWsClient = class _PipelineWsClient {
2440
2475
  this.sendBuffer = [];
2441
2476
  this.stopped = true;
2442
2477
  this.cleanupTimers();
2478
+ this.connectInFlight = null;
2443
2479
  if (this.ws) {
2444
2480
  try {
2445
2481
  this.ws.removeAllListeners();
@@ -4948,4 +4984,4 @@ export {
4948
4984
  runPipeline,
4949
4985
  stopPipeline
4950
4986
  };
4951
- //# sourceMappingURL=pipeline-ILE7LMGI.js.map
4987
+ //# sourceMappingURL=pipeline-3V6PWWAW.js.map