opencode-auto-resume 1.1.18 → 1.1.19

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +84 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -415,6 +415,8 @@ With options:
415
415
  | `thinkingToolRecoveryPrompt` | `THINKING_TOOL_RECOVERY_PROMPT` | Override the thinking-tool recovery prompt |
416
416
  | `doneWithoutWorkPrompt` | `DONE_WITHOUT_WORK_PROMPT` | Override the done-claim-with-open-todos prompt |
417
417
  | `doneWithoutDetailsPrompt` | `DONE_WITHOUT_DETAILS_PROMPT` | Override the done-claim-with-no-todos report prompt |
418
+ | `doneClaimPatterns` | `DONE_CLAIM_PATTERNS` | Array of regex strings overriding the default done-claim detection patterns (case-insensitive, multiline). Invalid regexes are skipped. Empty array falls back to defaults. |
419
+ | `readyToContinuePatterns` | `READY_TO_CONTINUE_PATTERNS` | Array of regex strings overriding the default ready-to-continue detection patterns (case-insensitive). Invalid regexes are skipped. Empty array falls back to defaults. |
418
420
  | `silentDeadStreamMinTokens` | `200` | Min output tokens to treat a textless `finish:"unknown"` message as a dead stream |
419
421
  | `busyStallStrategy` | `"continue"` | Busy-stall response: `"continue"`, `"abort"` (abort-first), or `"off"` (disabled) |
420
422
  | `contextSaturationThreshold` | `0.85` | Ratio of used/usable context that routes a saturated parent to magic-context `ctx-wrapup` (only when magic-context is installed) |
package/dist/index.js CHANGED
@@ -12428,7 +12428,7 @@ function containsToolCallAsText(text) {
12428
12428
  }
12429
12429
  return false;
12430
12430
  }
12431
- function containsReadyToContinuePattern(text) {
12431
+ function containsReadyToContinuePattern(text, patterns = READY_TO_CONTINUE_PATTERNS) {
12432
12432
  const lines = text.split(`
12433
12433
  `);
12434
12434
  const lastLine = lines[lines.length - 1]?.trim();
@@ -12436,7 +12436,7 @@ function containsReadyToContinuePattern(text) {
12436
12436
  return false;
12437
12437
  const lastLines = lines.slice(-3).join(`
12438
12438
  `);
12439
- return READY_TO_CONTINUE_PATTERNS.some((pat) => pat.test(lastLines));
12439
+ return patterns.some((pat) => pat.test(lastLines));
12440
12440
  }
12441
12441
  var DONE_CLAIM_PATTERNS = [
12442
12442
  /^task\s+done[.!]*$/im,
@@ -12456,12 +12456,12 @@ var DONE_CLAIM_PATTERNS = [
12456
12456
  ];
12457
12457
  var DONE_WITHOUT_WORK_PROMPT = "I need you to verify more carefully that you have actually completed all the required tasks. " + "Your response indicated you're done, but no work was detected. Please check your todo list " + "and complete any remaining work.";
12458
12458
  var DONE_WITHOUT_DETAILS_PROMPT = "Your last response claimed the task is complete but contained no work description. This is not acceptable. " + "You MUST respond now with a full, detailed report of everything you did: " + "for each file you modified, state the full path and the exact changes; " + "list every command you ran to verify and its result; state the final outcome. " + "Do NOT reply with 'done', 'task completed', or any short acknowledgment \u2014 " + "your ONLY acceptable response right now is this detailed report. Write it now.";
12459
- function containsDoneClaimPattern(text) {
12459
+ function containsDoneClaimPattern(text, patterns = DONE_CLAIM_PATTERNS) {
12460
12460
  const lines = text.split(`
12461
12461
  `);
12462
12462
  const lastLines = lines.slice(-5).join(`
12463
12463
  `);
12464
- return DONE_CLAIM_PATTERNS.some((pat) => pat.test(lastLines));
12464
+ return patterns.some((pat) => pat.test(lastLines));
12465
12465
  }
12466
12466
  function containsWorkDescription(text) {
12467
12467
  if (/`[^`\n]*\.[a-zA-Z0-9]{1,8}[^`\n]*`/.test(text))
@@ -12675,6 +12675,30 @@ var AutoResumePlugin = async (ctx, options) => {
12675
12675
  const contextSaturationThreshold = options?.contextSaturationThreshold ?? 0.85;
12676
12676
  const subagentNativeCompactionEnabled = options?.subagentNativeCompactionEnabled ?? false;
12677
12677
  const activeUserWindowMs = options?.activeUserWindowMs ?? DEFAULT_ACTIVE_USER_WINDOW_MS;
12678
+ const doneClaimPatterns = (() => {
12679
+ const raw = options?.doneClaimPatterns;
12680
+ if (!Array.isArray(raw) || raw.length === 0)
12681
+ return DONE_CLAIM_PATTERNS;
12682
+ return raw.map((s) => {
12683
+ try {
12684
+ return new RegExp(s, "im");
12685
+ } catch {
12686
+ return null;
12687
+ }
12688
+ }).filter((r) => r !== null);
12689
+ })();
12690
+ const readyToContinuePatterns = (() => {
12691
+ const raw = options?.readyToContinuePatterns;
12692
+ if (!Array.isArray(raw) || raw.length === 0)
12693
+ return READY_TO_CONTINUE_PATTERNS;
12694
+ return raw.map((s) => {
12695
+ try {
12696
+ return new RegExp(s, "i");
12697
+ } catch {
12698
+ return null;
12699
+ }
12700
+ }).filter((r) => r !== null);
12701
+ })();
12678
12702
  const dbg = (...args) => {
12679
12703
  if (debug)
12680
12704
  console.log("[debug]", ...args);
@@ -12749,6 +12773,7 @@ var AutoResumePlugin = async (ctx, options) => {
12749
12773
  orphanWatchStartAt: null,
12750
12774
  aborting: false,
12751
12775
  pluginAbortInFlight: false,
12776
+ pluginAbortAt: 0,
12752
12777
  toolTextRecovered: false,
12753
12778
  toolTextAttempts: 0,
12754
12779
  continueTimestamps: [],
@@ -12917,6 +12942,10 @@ var AutoResumePlugin = async (ctx, options) => {
12917
12942
  }
12918
12943
  if (w.userCancelled || w.completionSignaled)
12919
12944
  return;
12945
+ if (w.gaveUp) {
12946
+ await log("debug", `${short(sid)} - gaveUp latched, refusing further continue prompts`);
12947
+ return;
12948
+ }
12920
12949
  if (!w.continuing)
12921
12950
  dbg(`State transition on ${short(sid)}: continuing=false -> true`);
12922
12951
  w.continuing = true;
@@ -13018,7 +13047,6 @@ var AutoResumePlugin = async (ctx, options) => {
13018
13047
  } catch (err) {
13019
13048
  const errMsg = err instanceof Error ? err.message : String(err);
13020
13049
  await log("warn", `${short(sid)} - recovery retry failed: ${errMsg}`);
13021
- w.recoveryAttempts = 0;
13022
13050
  }
13023
13051
  w.watchdogRetryGuard = false;
13024
13052
  } else {
@@ -13030,8 +13058,11 @@ var AutoResumePlugin = async (ctx, options) => {
13030
13058
  dbg(`Watchdog check on ${short(sid)}: status=${w.status}, recoveryAttempts=${w.recoveryAttempts}, maxRetries=${maxRecoveryRetries}, watchdogLatencyMs=${Date.now() - w.lastRetryAt} -> ABORT_RESUME`);
13031
13059
  const resumed = await tryAbortAndResume(sid, w);
13032
13060
  if (!resumed && !w.aborting) {
13033
- await log("warn", `Recovery exhausted on ${short(sid)}: attempts=${w.recoveryAttempts}, lastError=abort+resume failed`);
13034
- dbg(`Watchdog check on ${short(sid)}: status=${w.status} -> GAVE_UP`);
13061
+ w.gaveUp = true;
13062
+ w.pendingRecovery = false;
13063
+ w.pendingRecoveryReason = null;
13064
+ await log("warn", `Recovery exhausted on ${short(sid)}: attempts=${w.recoveryAttempts}, lastError=abort+resume failed \u2014 GAVE UP (re-arms on next user message)`);
13065
+ dbg(`Watchdog check on ${short(sid)}: status=${w.status} -> GAVE_UP (latched)`);
13035
13066
  if (w.pendingRecoveryAt > 0) {
13036
13067
  dbg(`Total recovery cycle on ${short(sid)} (failed): totalCycleMs=${Date.now() - w.pendingRecoveryAt}`);
13037
13068
  }
@@ -13660,7 +13691,7 @@ var AutoResumePlugin = async (ctx, options) => {
13660
13691
  bestCandidate = candidate;
13661
13692
  }
13662
13693
  }
13663
- if (containsReadyToContinuePattern(text)) {
13694
+ if (containsReadyToContinuePattern(text, readyToContinuePatterns)) {
13664
13695
  const todos = w.todos || [];
13665
13696
  const hasOpenTodos = todos.some(isOpenTodo);
13666
13697
  if (!hasOpenTodos && todos.length > 0) {
@@ -13680,16 +13711,42 @@ var AutoResumePlugin = async (ctx, options) => {
13680
13711
  await log("info", `${short(sid)} - skipping continue, todos appear completed (attempt ${w.todoCheckAttempts}/2)`);
13681
13712
  continue;
13682
13713
  }
13683
- const candidate = {
13684
- prompt: containsDoneClaimPattern(text) ? doneWithoutWorkPrompt : continuePrompt,
13685
- source: containsDoneClaimPattern(text) ? "done-claim" : "ready-to-continue",
13686
- priority: 1
13687
- };
13688
- if (!bestCandidate || candidate.priority < bestCandidate.priority) {
13689
- bestCandidate = candidate;
13714
+ if (containsDoneClaimPattern(text, doneClaimPatterns)) {
13715
+ const todos = w.todos || [];
13716
+ const hasOpenTodos = todos.some(isOpenTodo);
13717
+ if (hasOpenTodos) {
13718
+ const candidate = {
13719
+ prompt: doneWithoutWorkPrompt,
13720
+ source: "done-claim",
13721
+ priority: 1
13722
+ };
13723
+ if (!bestCandidate || candidate.priority < bestCandidate.priority) {
13724
+ bestCandidate = candidate;
13725
+ }
13726
+ } else if (!containsWorkDescription(allAssistantText)) {
13727
+ if (w.doneClaimNoTodosAttempts < maxRetries) {
13728
+ const candidate = {
13729
+ prompt: doneWithoutDetailsPrompt,
13730
+ source: "done-claim-no-todos",
13731
+ priority: 1
13732
+ };
13733
+ if (!bestCandidate || candidate.priority < bestCandidate.priority) {
13734
+ bestCandidate = candidate;
13735
+ }
13736
+ }
13737
+ }
13738
+ } else {
13739
+ const candidate = {
13740
+ prompt: continuePrompt,
13741
+ source: "ready-to-continue",
13742
+ priority: 1
13743
+ };
13744
+ if (!bestCandidate || candidate.priority < bestCandidate.priority) {
13745
+ bestCandidate = candidate;
13746
+ }
13690
13747
  }
13691
13748
  }
13692
- if (!bestCandidate && containsDoneClaimPattern(text)) {
13749
+ if (!bestCandidate && containsDoneClaimPattern(text, doneClaimPatterns)) {
13693
13750
  const todos = w.todos || [];
13694
13751
  const hasOpenTodos = todos.some(isOpenTodo);
13695
13752
  if (hasOpenTodos) {
@@ -13774,6 +13831,7 @@ var AutoResumePlugin = async (ctx, options) => {
13774
13831
  await log("info", `${short(sid)} - max open-todos nudges (${maxRetries}) reached, waiting for activity`);
13775
13832
  return;
13776
13833
  }
13834
+ w.todoNudgeAttempts++;
13777
13835
  } else if (isDoneClaimNoTodos) {
13778
13836
  w.doneClaimNoTodosAttempts++;
13779
13837
  } else {
@@ -13829,6 +13887,7 @@ var AutoResumePlugin = async (ctx, options) => {
13829
13887
  const idleSec = Math.round((Date.now() - (w.orphanWatchStartAt ?? w.lastActivityAt)) / 1000);
13830
13888
  await log("info", `Abort+Resume on ${short(sid)} (${idleSec}s idle). Aborting...`);
13831
13889
  w.pluginAbortInFlight = true;
13890
+ w.pluginAbortAt = Date.now();
13832
13891
  try {
13833
13892
  await ctx.client.session.abort({ path: { id: sid } });
13834
13893
  await log("info", `${short(sid)} - abort OK`);
@@ -13838,6 +13897,7 @@ var AutoResumePlugin = async (ctx, options) => {
13838
13897
  await log("warn", `${short(sid)} - abort failed: ${errMsg}`);
13839
13898
  w.aborting = false;
13840
13899
  w.pluginAbortInFlight = false;
13900
+ w.pluginAbortAt = 0;
13841
13901
  return false;
13842
13902
  }
13843
13903
  await new Promise((resolve) => setTimeout(resolve, ABORT_CONTINUE_DELAY_MS));
@@ -13862,6 +13922,7 @@ var AutoResumePlugin = async (ctx, options) => {
13862
13922
  return false;
13863
13923
  } finally {
13864
13924
  w.pluginAbortInFlight = false;
13925
+ w.pluginAbortAt = 0;
13865
13926
  }
13866
13927
  }
13867
13928
  async function tryResume(sid, w, reason, prompt) {
@@ -14526,10 +14587,16 @@ var AutoResumePlugin = async (ctx, options) => {
14526
14587
  const isMessageAborted = errorName === "MessageAbortedError";
14527
14588
  if (isMessageAborted) {
14528
14589
  for (const [wSid, w] of sessions) {
14529
- if (!w.pluginAbortInFlight) {
14590
+ const PLUGIN_ABORT_GRACE_MS = 1000;
14591
+ const isOwnAbort = w.pluginAbortInFlight && Date.now() - (w.pluginAbortAt || 0) < PLUGIN_ABORT_GRACE_MS;
14592
+ if (!isOwnAbort) {
14530
14593
  w.userCancelled = true;
14531
14594
  w.status = "idle";
14532
14595
  resetIdleFlags(w);
14596
+ if (w.toolTextTimer) {
14597
+ clearTimeout(w.toolTextTimer);
14598
+ w.toolTextTimer = null;
14599
+ }
14533
14600
  }
14534
14601
  }
14535
14602
  log("info", "User abort (ESC)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",