opencode-auto-resume 1.1.20 → 1.1.21

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.
package/README.md CHANGED
@@ -247,10 +247,11 @@ If the assistant claims the task is done ("task done", "finished", "all complete
247
247
 
248
248
  ### User-input awareness
249
249
 
250
- The session is not stalled while the ball is in the user's court. Two gates stand down idle nudges:
250
+ The session is not stalled while the ball is in the user's court. Three gates stand down idle nudges:
251
251
 
252
252
  - **Awaiting input**: the newest assistant message holds a `tool_use` part with `state.status: "pending"` (e.g. an open `question` tool call). All idle checks, the periodic recheck, delayed action-intent callbacks, and the tool-text scan skip prompting until a newer user message clears the gate. Completed tool calls never engage it.
253
253
  - **Recently active user**: any inbound user message within `activeUserWindowMs` (default 5 minutes) means the user is engaged — likely composing a reply, which leaves no pending tool call behind. Open-todos nudges (idle + periodic), the tool-text reminder fallback, and action-intent callbacks stand down until the window expires.
254
+ - **Explicit stop line** (opt-in): when the newest assistant message's text matches one of `stopPatterns`, the agent has deliberately handed the ball to the user — typical for agent setups whose prompts end a turn with a protocol line such as `STOP: NEEDS_DECISION <question>` or `STOP: BLOCKED <reason>`, while the todo for that decision is still open. It counts exactly like the awaiting-input gate. Default: no patterns (unchanged behaviour).
254
255
 
255
256
  ---
256
257
 
@@ -422,6 +423,7 @@ With options:
422
423
  | `busyStallStrategy` | `"continue"` | Busy-stall response: `"continue"`, `"abort"` (abort-first), or `"off"` (disabled) |
423
424
  | `contextSaturationThreshold` | `0.85` | Ratio of used/usable context that routes a saturated parent to magic-context `ctx-wrapup` (only when magic-context is installed) |
424
425
  | `activeUserWindowMs` | `300000` | Inbound-user-message recency window (5 min) during which idle nudges stand down (user likely composing) |
426
+ | `stopPatterns` | `[]` | Array of regex strings (multiline). When the newest assistant message's text matches one, the session counts as awaiting the user and idle nudges stand down — e.g. `["STOP:\\s*(BLOCKED\|NEEDS_DECISION)\\b"]`. Invalid regexes are skipped. |
425
427
  | `subagentNativeCompactionEnabled` | `false` | Opt-in native `session.summarize()` for saturated subagent sessions (no magic-context detection required) |
426
428
 
427
429
  Message patterns are matched case-insensitively. Error names use exact match.
package/dist/index.js CHANGED
@@ -12631,6 +12631,24 @@ function getOpenTodos(todos) {
12631
12631
  return [];
12632
12632
  return todos.filter(isOpenTodo);
12633
12633
  }
12634
+ function parseStopPatterns(raw) {
12635
+ if (!Array.isArray(raw))
12636
+ return [];
12637
+ return raw.map((s) => {
12638
+ try {
12639
+ return new RegExp(String(s), "m");
12640
+ } catch {
12641
+ return null;
12642
+ }
12643
+ }).filter((r) => r !== null);
12644
+ }
12645
+ function endsWithStopLine(parts, patterns) {
12646
+ if (!patterns.length)
12647
+ return false;
12648
+ const text = parts.filter((p) => p.type === "text").map((p) => String(p.text ?? "")).join(`
12649
+ `);
12650
+ return patterns.some((re) => re.test(text));
12651
+ }
12634
12652
  function buildOpenTodosReminder(todos) {
12635
12653
  if (!Array.isArray(todos))
12636
12654
  return "continue";
@@ -12702,6 +12720,7 @@ var AutoResumePlugin = async (ctx, options) => {
12702
12720
  }
12703
12721
  }).filter((r) => r !== null);
12704
12722
  })();
12723
+ const stopPatterns = parseStopPatterns(options?.stopPatterns);
12705
12724
  const dbg = (...args) => {
12706
12725
  if (debug)
12707
12726
  console.log("[debug]", ...args);
@@ -13110,6 +13129,8 @@ var AutoResumePlugin = async (ctx, options) => {
13110
13129
  const parts = msg.parts;
13111
13130
  if (!parts)
13112
13131
  return false;
13132
+ if (endsWithStopLine(parts, stopPatterns))
13133
+ return true;
13113
13134
  for (const part of parts) {
13114
13135
  if (part.type !== "tool_use")
13115
13136
  continue;
@@ -14471,8 +14492,16 @@ var AutoResumePlugin = async (ctx, options) => {
14471
14492
  break;
14472
14493
  }
14473
14494
  case "session.updated": {
14474
- if (sid)
14475
- ensureWatch(sid);
14495
+ if (!sid)
14496
+ break;
14497
+ const updatedInfo = ev.properties?.info;
14498
+ if (updatedInfo?.revert) {
14499
+ const was = sessions.get(sid);
14500
+ dbg(`Revert on ${short(sid)}: dropping watch state (resumeAttempts=${was?.resumeAttempts ?? 0} recoveryAttempts=${was?.recoveryAttempts ?? 0} gaveUp=${was?.gaveUp ?? false})`);
14501
+ sessions.delete(sid);
14502
+ break;
14503
+ }
14504
+ ensureWatch(sid);
14476
14505
  break;
14477
14506
  }
14478
14507
  case "session.idle": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.1.20",
3
+ "version": "1.1.21",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",