opencode-auto-resume 1.1.2 → 1.1.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.
Files changed (2) hide show
  1. package/dist/index.js +102 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12505,7 +12505,9 @@ var AutoResumePlugin = async (ctx, options) => {
12505
12505
  toolLoopAttempts: 0,
12506
12506
  isSubagent: false,
12507
12507
  completionSignaled: false,
12508
- todoNudgeAttempts: 0
12508
+ todoNudgeAttempts: 0,
12509
+ pendingTools: 0,
12510
+ pendingCommands: 0
12509
12511
  };
12510
12512
  sessions.set(sid, w);
12511
12513
  }
@@ -12517,6 +12519,9 @@ var AutoResumePlugin = async (ctx, options) => {
12517
12519
  w.lastActivityAt = Date.now();
12518
12520
  }
12519
12521
  }
12522
+ function hasInflightTools(w) {
12523
+ return w.pendingTools > 0 || w.pendingCommands > 0;
12524
+ }
12520
12525
  function busyCount() {
12521
12526
  let count = 0;
12522
12527
  for (const [, w] of sessions) {
@@ -12838,6 +12843,8 @@ var AutoResumePlugin = async (ctx, options) => {
12838
12843
  function resetSessionFlags(w) {
12839
12844
  w.userCancelled = false;
12840
12845
  w.resumeAttempts = 0;
12846
+ w.pendingTools = 0;
12847
+ w.pendingCommands = 0;
12841
12848
  w.gaveUp = false;
12842
12849
  w.orphanWatchStartAt = null;
12843
12850
  w.aborting = false;
@@ -12859,10 +12866,11 @@ var AutoResumePlugin = async (ctx, options) => {
12859
12866
  }
12860
12867
  }
12861
12868
  function resetIdleFlags(w) {
12862
- w.userCancelled = false;
12863
12869
  w.aborting = false;
12864
12870
  w.orphanWatchStartAt = null;
12865
12871
  w.idleSince = Date.now();
12872
+ w.pendingTools = 0;
12873
+ w.pendingCommands = 0;
12866
12874
  }
12867
12875
  function detectPatternLoop(recentTools) {
12868
12876
  if (recentTools.length < 6)
@@ -13107,6 +13115,10 @@ var AutoResumePlugin = async (ctx, options) => {
13107
13115
  return;
13108
13116
  }
13109
13117
  if (isHallucinationLoop(sid)) {
13118
+ if (hasInflightTools(w)) {
13119
+ await log("debug", `Session ${short(sid)} has ${w.pendingTools} tool(s) in-flight, skipping hallucination abort`);
13120
+ return;
13121
+ }
13110
13122
  const hasActiveTool = await checkSessionHasActiveTool(sid);
13111
13123
  if (hasActiveTool) {
13112
13124
  await log("debug", `Session ${short(sid)} has active tool, skipping hallucination abort`);
@@ -13177,6 +13189,11 @@ var AutoResumePlugin = async (ctx, options) => {
13177
13189
  if (w.lastRetryAt > 0 && elapsedSinceRetry < requiredBackoff)
13178
13190
  return false;
13179
13191
  if (isHallucinationLoop(sid)) {
13192
+ if (hasInflightTools(w)) {
13193
+ await log("debug", `Session ${short(sid)} has ${w.pendingTools} tool(s) in-flight, skipping hallucination abort`);
13194
+ w.lastRetryAt = now;
13195
+ return false;
13196
+ }
13180
13197
  const hasActiveTool = await checkSessionHasActiveTool(sid);
13181
13198
  if (hasActiveTool) {
13182
13199
  await log("debug", `Session ${short(sid)} has active tool, skipping hallucination abort`);
@@ -13250,6 +13267,11 @@ var AutoResumePlugin = async (ctx, options) => {
13250
13267
  const orphanIdle = now - w.orphanWatchStartAt;
13251
13268
  if (orphanIdle >= subagentWaitMs + gracePeriodMs) {
13252
13269
  if (w.resumeAttempts < maxRetries) {
13270
+ if (hasInflightTools(w)) {
13271
+ await log("debug", `Parent ${short(sid)} has ${w.pendingTools} tool(s) in-flight, skipping orphan-watch abort`);
13272
+ w.orphanWatchStartAt = now;
13273
+ continue;
13274
+ }
13253
13275
  const hasActiveTool = await checkSessionHasActiveTool(sid);
13254
13276
  if (hasActiveTool) {
13255
13277
  await log("debug", `Parent ${short(sid)} has active tool call, skipping orphan-watch abort`);
@@ -13296,6 +13318,11 @@ var AutoResumePlugin = async (ctx, options) => {
13296
13318
  w.lastSubagentCheckAt = now;
13297
13319
  continue;
13298
13320
  }
13321
+ if (hasInflightTools(w)) {
13322
+ await log("debug", `Session ${short(sid)} has ${w.pendingTools} tool(s) in-flight, skipping abort check`);
13323
+ w.lastSubagentCheckAt = now;
13324
+ continue;
13325
+ }
13299
13326
  const hasActiveTool = await checkSessionHasActiveTool(sid);
13300
13327
  if (hasActiveTool) {
13301
13328
  await log("debug", `Session ${short(sid)} has active tool call, skipping abort check`);
@@ -13320,15 +13347,20 @@ var AutoResumePlugin = async (ctx, options) => {
13320
13347
  }
13321
13348
  const idle = now - w.lastActivityAt;
13322
13349
  if (idle >= chunkTimeoutMs + gracePeriodMs) {
13323
- const hasActiveTool = await checkSessionHasActiveTool(sid);
13324
- if (hasActiveTool) {
13325
- await log("debug", `Session ${short(sid)} has active tool call, skipping stall recovery`);
13350
+ if (hasInflightTools(w)) {
13351
+ await log("debug", `Session ${short(sid)} has ${w.pendingTools} tool(s) in-flight, skipping stall recovery`);
13326
13352
  w.lastSubagentCheckAt = now;
13327
- } else if (w.resumeAttempts < maxRetries) {
13328
- tryResume(sid, w, "Stream stall");
13329
- } else if (!w.gaveUp) {
13330
- w.gaveUp = true;
13331
- log("warn", `${short(sid)} - all ${maxRetries} retries exhausted.`);
13353
+ } else {
13354
+ const hasActiveTool = await checkSessionHasActiveTool(sid);
13355
+ if (hasActiveTool) {
13356
+ await log("debug", `Session ${short(sid)} has active tool call, skipping stall recovery`);
13357
+ w.lastSubagentCheckAt = now;
13358
+ } else if (w.resumeAttempts < maxRetries) {
13359
+ tryResume(sid, w, "Stream stall");
13360
+ } else if (!w.gaveUp) {
13361
+ w.gaveUp = true;
13362
+ log("warn", `${short(sid)} - all ${maxRetries} retries exhausted.`);
13363
+ }
13332
13364
  }
13333
13365
  }
13334
13366
  }
@@ -13362,7 +13394,17 @@ var AutoResumePlugin = async (ctx, options) => {
13362
13394
  resetSessionFlags(w);
13363
13395
  prevBusyCount = busyCount();
13364
13396
  log("debug", `${short(sid)} -> busy (${prevBusyCount})`);
13365
- } else if (statusType === "idle" || statusType === "interrupted") {
13397
+ } else if (statusType === "interrupted") {
13398
+ w.status = "idle";
13399
+ resetIdleFlags(w);
13400
+ w.userCancelled = true;
13401
+ if (w.toolTextTimer) {
13402
+ clearTimeout(w.toolTextTimer);
13403
+ w.toolTextTimer = null;
13404
+ }
13405
+ prevBusyCount = busyCount();
13406
+ log("info", `${short(sid)} -> interrupted by user, backing off`);
13407
+ } else if (statusType === "idle") {
13366
13408
  w.status = "idle";
13367
13409
  resetIdleFlags(w);
13368
13410
  const currentBusy = busyCount();
@@ -13375,11 +13417,11 @@ var AutoResumePlugin = async (ctx, options) => {
13375
13417
  }
13376
13418
  }
13377
13419
  prevBusyCount = currentBusy;
13378
- log("debug", `${short(sid)} -> idle (${currentBusy})${statusType === "interrupted" ? " (interrupted)" : ""}`);
13420
+ log("debug", `${short(sid)} -> idle (${currentBusy})`);
13379
13421
  if (!w.isSubagent) {
13380
13422
  const todos = w.todos || [];
13381
13423
  const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
13382
- if (hasOpenTodos && currentBusy === 0 && !w.completionSignaled) {
13424
+ if (hasOpenTodos && currentBusy === 0 && !w.completionSignaled && !w.userCancelled) {
13383
13425
  const isCelebration = await lastAssistantEndsWithCelebration(sid);
13384
13426
  if (isCelebration) {
13385
13427
  await log("info", `${short(sid)} - \uD83C\uDF89 detected in idle handler, skipping continue`);
@@ -13396,13 +13438,12 @@ var AutoResumePlugin = async (ctx, options) => {
13396
13438
  }
13397
13439
  }
13398
13440
  }
13399
- if (!w.completionSignaled && w.toolTextAttempts < maxRetries) {
13441
+ if (!w.completionSignaled && !w.userCancelled && w.toolTextAttempts < maxRetries) {
13400
13442
  if (w.toolTextTimer)
13401
13443
  clearTimeout(w.toolTextTimer);
13402
- const checkDelay = statusType === "interrupted" ? 500 : TOOL_TEXT_CHECK_DELAY_MS;
13403
13444
  w.toolTextTimer = setTimeout(() => {
13404
13445
  checkForToolCallAsText(sid, w);
13405
- }, checkDelay);
13446
+ }, TOOL_TEXT_CHECK_DELAY_MS);
13406
13447
  }
13407
13448
  } else if (statusType === "retry") {
13408
13449
  touchSession(sid);
@@ -13413,7 +13454,9 @@ var AutoResumePlugin = async (ctx, options) => {
13413
13454
  case "session.created": {
13414
13455
  if (!sid)
13415
13456
  break;
13416
- ensureWatch(sid);
13457
+ const w = ensureWatch(sid);
13458
+ w.pendingTools = 0;
13459
+ w.pendingCommands = 0;
13417
13460
  log("debug", `New session: ${short(sid)} (${sessions.size})`);
13418
13461
  break;
13419
13462
  }
@@ -13444,33 +13487,14 @@ var AutoResumePlugin = async (ctx, options) => {
13444
13487
  break;
13445
13488
  const w = sessions.get(sid);
13446
13489
  if (w) {
13447
- const wasJustContinued = w.continuing || Date.now() - w.lastRetryAt < 2000;
13448
13490
  w.status = "idle";
13449
13491
  resetIdleFlags(w);
13450
- log("debug", `${short(sid)} -> interrupted${wasJustContinued ? " (after continue)" : ""}`);
13451
- if (wasJustContinued && w.interruptedContinueCount < 3 && !w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
13452
- w.interruptedContinueCount++;
13453
- await log("info", `${short(sid)} - continue was interrupted (${w.interruptedContinueCount}/3), retrying...`);
13454
- w.continuing = false;
13455
- try {
13456
- await sendContinuePrompt(sid, "continue", w);
13457
- await log("info", `${short(sid)} - interrupted continue retried`);
13458
- } catch (err) {
13459
- const errMsg = err instanceof Error ? err.message : String(err);
13460
- await log("warn", `${short(sid)} - interrupted continue retry failed: ${errMsg}`);
13461
- }
13462
- return;
13463
- } else if (wasJustContinued && w.interruptedContinueCount >= 3) {
13464
- await log("warn", `${short(sid)} - too many interrupted continues (${w.interruptedContinueCount}), stopping retries`);
13465
- w.interruptedContinueCount = 0;
13466
- }
13467
- if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
13468
- if (w.toolTextTimer)
13469
- clearTimeout(w.toolTextTimer);
13470
- w.toolTextTimer = setTimeout(() => {
13471
- checkForToolCallAsText(sid, w);
13472
- }, 500);
13492
+ w.userCancelled = true;
13493
+ if (w.toolTextTimer) {
13494
+ clearTimeout(w.toolTextTimer);
13495
+ w.toolTextTimer = null;
13473
13496
  }
13497
+ log("info", `${short(sid)} -> interrupted by user, backing off`);
13474
13498
  }
13475
13499
  break;
13476
13500
  }
@@ -13506,11 +13530,25 @@ var AutoResumePlugin = async (ctx, options) => {
13506
13530
  break;
13507
13531
  const errorMessage = errorObj?.data?.message ?? String(errorObj?.data ?? "");
13508
13532
  log("debug", `Session error: ${errorName} - ${errorMessage}`);
13533
+ if (sid) {
13534
+ const w = sessions.get(sid);
13535
+ if (w) {
13536
+ w.pendingTools = 0;
13537
+ w.pendingCommands = 0;
13538
+ }
13539
+ }
13509
13540
  break;
13510
13541
  }
13511
13542
  case "command.executed": {
13512
- for (const [, w] of sessions) {
13513
- resetSessionFlags(w);
13543
+ for (const [, w2] of sessions) {
13544
+ resetSessionFlags(w2);
13545
+ }
13546
+ if (!sid)
13547
+ break;
13548
+ const w = sessions.get(sid);
13549
+ if (w) {
13550
+ w.pendingCommands = Math.max(0, w.pendingCommands - 1);
13551
+ w.lastActivityAt = Date.now();
13514
13552
  }
13515
13553
  break;
13516
13554
  }
@@ -13548,6 +13586,27 @@ var AutoResumePlugin = async (ctx, options) => {
13548
13586
  },
13549
13587
  tool: {
13550
13588
  task_complete: taskCompleteTool
13589
+ },
13590
+ "tool.execute.before": async (input) => {
13591
+ if (!input?.sessionID)
13592
+ return;
13593
+ const w = ensureWatch(input.sessionID);
13594
+ w.pendingTools++;
13595
+ w.lastActivityAt = Date.now();
13596
+ },
13597
+ "command.execute.before": async (input) => {
13598
+ if (!input?.sessionID)
13599
+ return;
13600
+ const w = ensureWatch(input.sessionID);
13601
+ w.pendingCommands++;
13602
+ w.lastActivityAt = Date.now();
13603
+ },
13604
+ "tool.execute.after": async (input) => {
13605
+ if (!input?.sessionID)
13606
+ return;
13607
+ const w = ensureWatch(input.sessionID);
13608
+ w.pendingTools = Math.max(0, w.pendingTools - 1);
13609
+ w.lastActivityAt = Date.now();
13551
13610
  }
13552
13611
  };
13553
13612
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",