opencode-auto-resume 1.0.14 → 1.0.16

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 +92 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12364,12 +12364,18 @@ var TOOL_TEXT_PATTERNS = [
12364
12364
  /<invoke\s+/i,
12365
12365
  /<func(?:t|ti|tio|tion)?$/im,
12366
12366
  /<par(?:a|am|ame|amet|amete|ameter)?$/im,
12367
- /<(?:edit|write|read|bash|grep|glob|search|replace|execute|run)\s*(?:\s[^>]*)?\s*(?:\/>|>)/i
12367
+ /<(?:edit|write|read|bash|grep|glob|search|replace|execute|run)\s*(?:\s[^>]*)?\s*(?:\/>|>)/i,
12368
+ /{"type":\s*"function"/i,
12369
+ /{"name":\s*"[a-zA-Z_]/i,
12370
+ /\{\s*"type"\s*:?$/im,
12371
+ /\{\s*"name"\s*:?$/im
12368
12372
  ];
12369
12373
  var TRUNCATED_XML_PATTERNS = [
12370
12374
  { open: /<function[^>]*>/i, close: /<\/function>/i },
12371
12375
  { open: /<parameter[^>]*>/i, close: /<\/parameter>/i },
12372
- { open: /<tool_call[^>]*>/i, close: /<\/tool_call>/i }
12376
+ { open: /<tool_call[^>]*>/i, close: /<\/tool_call>/i },
12377
+ { open: /\{\s*"type"\s*:/i, close: /}/ },
12378
+ { open: /\{\s*"name"\s*:/i, close: /}/ }
12373
12379
  ];
12374
12380
  var READY_TO_CONTINUE_PATTERNS = [
12375
12381
  /ready to continue with task/i,
@@ -12408,7 +12414,9 @@ var DONE_CLAIM_PATTERNS = [
12408
12414
  /^finished[.!]*$/im,
12409
12415
  /^complete[.!]*$/im,
12410
12416
  /^task\s+complete[.!]*$/im,
12417
+ /^task\s+completed[.!]*$/im,
12411
12418
  /^all\s+tasks?\s+complete[.!]*$/im,
12419
+ /^all\s+tasks?\s+completed[.!]*$/im,
12412
12420
  /^(?:i['']?m\s+)?done\s+with\s+task/im
12413
12421
  ];
12414
12422
  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.";
@@ -12479,7 +12487,8 @@ var AutoResumePlugin = async (ctx, options) => {
12479
12487
  todoCheckAttempts: 0,
12480
12488
  toolTextTimer: null,
12481
12489
  checkingToolText: false,
12482
- lastSubagentCheckAt: 0
12490
+ lastSubagentCheckAt: 0,
12491
+ interruptedContinueCount: 0
12483
12492
  };
12484
12493
  sessions.set(sid, w);
12485
12494
  }
@@ -12721,6 +12730,7 @@ var AutoResumePlugin = async (ctx, options) => {
12721
12730
  w.continuing = false;
12722
12731
  w.todoCheckAttempts = 0;
12723
12732
  w.checkingToolText = false;
12733
+ w.interruptedContinueCount = 0;
12724
12734
  if (w.toolTextTimer) {
12725
12735
  clearTimeout(w.toolTextTimer);
12726
12736
  w.toolTextTimer = null;
@@ -12770,16 +12780,31 @@ var AutoResumePlugin = async (ctx, options) => {
12770
12780
  const partType = part.type;
12771
12781
  let text = "";
12772
12782
  let isReasoning = false;
12783
+ let isToolUse = false;
12773
12784
  if (partType === "text") {
12774
12785
  text = part.text ?? "";
12775
12786
  } else if (partType === "reasoning") {
12776
12787
  text = part.text ?? "";
12777
12788
  isReasoning = true;
12789
+ } else if (partType === "tool_use") {
12790
+ isToolUse = true;
12791
+ const toolName = part.name ?? "unknown";
12792
+ text = `tool_use: ${toolName}`;
12778
12793
  } else {
12779
12794
  continue;
12780
12795
  }
12781
12796
  allAssistantText += text + `
12782
12797
  `;
12798
+ if (isToolUse) {
12799
+ const candidate = {
12800
+ prompt: "continue",
12801
+ source: "tool-use",
12802
+ priority: 1
12803
+ };
12804
+ if (!bestCandidate || candidate.priority < bestCandidate.priority) {
12805
+ bestCandidate = candidate;
12806
+ }
12807
+ }
12783
12808
  if (containsToolCallAsText(text)) {
12784
12809
  const candidate = {
12785
12810
  prompt: isReasoning ? THINKING_TOOL_RECOVERY_PROMPT : TOOL_TEXT_RECOVERY_PROMPT,
@@ -12819,6 +12844,18 @@ var AutoResumePlugin = async (ctx, options) => {
12819
12844
  bestCandidate = candidate;
12820
12845
  }
12821
12846
  }
12847
+ if (!bestCandidate && containsDoneClaimPattern(text)) {
12848
+ const todos = w.todos || [];
12849
+ const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
12850
+ if (hasOpenTodos) {
12851
+ await log("info", `${short(sid)} - model claims done but todos remain open. Sending recovery prompt...`);
12852
+ bestCandidate = {
12853
+ prompt: DONE_WITHOUT_WORK_PROMPT,
12854
+ source: "done-claim-no-emoji",
12855
+ priority: 1
12856
+ };
12857
+ }
12858
+ }
12822
12859
  }
12823
12860
  }
12824
12861
  const trimmedText = allAssistantText.trim();
@@ -12832,6 +12869,18 @@ var AutoResumePlugin = async (ctx, options) => {
12832
12869
  }
12833
12870
  return;
12834
12871
  }
12872
+ if (!bestCandidate) {
12873
+ const todos = w.todos || [];
12874
+ const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
12875
+ if (hasOpenTodos) {
12876
+ await log("info", `${short(sid)} - no activity detected but todos remain open (${todos.filter((t) => t.status === "pending" || t.status === "in_progress").length} tasks). Sending continue...`);
12877
+ bestCandidate = {
12878
+ prompt: "continue",
12879
+ source: "idle-with-open-todos",
12880
+ priority: 2
12881
+ };
12882
+ }
12883
+ }
12835
12884
  if (!bestCandidate)
12836
12885
  return;
12837
12886
  w.toolTextRecovered = true;
@@ -13036,7 +13085,7 @@ var AutoResumePlugin = async (ctx, options) => {
13036
13085
  setTimeout(discoverSessions, 5000);
13037
13086
  }
13038
13087
  startTimer();
13039
- function handleEvent(ev) {
13088
+ async function handleEvent(ev) {
13040
13089
  const type = ev.type;
13041
13090
  const sid = getSid(ev);
13042
13091
  if (sid) {
@@ -13054,7 +13103,7 @@ var AutoResumePlugin = async (ctx, options) => {
13054
13103
  w.lastActivityAt = Date.now();
13055
13104
  resetSessionFlags(w);
13056
13105
  log("debug", `${short(sid)} -> busy (${busyCount()})`);
13057
- } else if (statusType === "idle") {
13106
+ } else if (statusType === "idle" || statusType === "interrupted") {
13058
13107
  w.status = "idle";
13059
13108
  resetIdleFlags(w);
13060
13109
  const currentBusy = busyCount();
@@ -13066,13 +13115,14 @@ var AutoResumePlugin = async (ctx, options) => {
13066
13115
  }
13067
13116
  }
13068
13117
  prevBusyCount = currentBusy;
13069
- log("debug", `${short(sid)} -> idle (${currentBusy})`);
13118
+ log("debug", `${short(sid)} -> idle (${currentBusy})${statusType === "interrupted" ? " (interrupted)" : ""}`);
13070
13119
  if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
13071
13120
  if (w.toolTextTimer)
13072
13121
  clearTimeout(w.toolTextTimer);
13122
+ const checkDelay = statusType === "interrupted" ? 500 : TOOL_TEXT_CHECK_DELAY_MS;
13073
13123
  w.toolTextTimer = setTimeout(() => {
13074
13124
  checkForToolCallAsText(sid, w);
13075
- }, TOOL_TEXT_CHECK_DELAY_MS);
13125
+ }, checkDelay);
13076
13126
  }
13077
13127
  } else if (statusType === "retry") {
13078
13128
  touchSession(sid);
@@ -13109,6 +13159,41 @@ var AutoResumePlugin = async (ctx, options) => {
13109
13159
  }
13110
13160
  break;
13111
13161
  }
13162
+ case "session.interrupted": {
13163
+ if (!sid)
13164
+ break;
13165
+ const w = sessions.get(sid);
13166
+ if (w) {
13167
+ const wasJustContinued = w.continuing || Date.now() - w.lastRetryAt < 2000;
13168
+ w.status = "idle";
13169
+ resetIdleFlags(w);
13170
+ log("debug", `${short(sid)} -> interrupted${wasJustContinued ? " (after continue)" : ""}`);
13171
+ if (wasJustContinued && w.interruptedContinueCount < 3 && !w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
13172
+ w.interruptedContinueCount++;
13173
+ await log("info", `${short(sid)} - continue was interrupted (${w.interruptedContinueCount}/3), retrying...`);
13174
+ w.continuing = false;
13175
+ try {
13176
+ await sendContinuePrompt(sid, "continue", w);
13177
+ await log("info", `${short(sid)} - interrupted continue retried`);
13178
+ } catch (err) {
13179
+ const errMsg = err instanceof Error ? err.message : String(err);
13180
+ await log("warn", `${short(sid)} - interrupted continue retry failed: ${errMsg}`);
13181
+ }
13182
+ return;
13183
+ } else if (wasJustContinued && w.interruptedContinueCount >= 3) {
13184
+ await log("warn", `${short(sid)} - too many interrupted continues (${w.interruptedContinueCount}), stopping retries`);
13185
+ w.interruptedContinueCount = 0;
13186
+ }
13187
+ if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
13188
+ if (w.toolTextTimer)
13189
+ clearTimeout(w.toolTextTimer);
13190
+ w.toolTextTimer = setTimeout(() => {
13191
+ checkForToolCallAsText(sid, w);
13192
+ }, 500);
13193
+ }
13194
+ }
13195
+ break;
13196
+ }
13112
13197
  case "todo.updated": {
13113
13198
  if (!sid)
13114
13199
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",