opencode-auto-resume 1.0.24 → 1.1.1

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 +45 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12346,6 +12346,7 @@ var ABORT_CONTINUE_DELAY_MS = 2000;
12346
12346
  var DEFAULT_LOOP_MAX_CONTINUES = 3;
12347
12347
  var DEFAULT_LOOP_WINDOW_MS = 10 * 60000;
12348
12348
  var TOOL_TEXT_CHECK_DELAY_MS = 3000;
12349
+ var MIN_ACTIVITY_GAP_MS = 1000;
12349
12350
  var MAX_IDLE_SESSIONS = 50;
12350
12351
  var IDLE_CLEANUP_MS = 10 * 60000;
12351
12352
  var SESSION_DISCOVERY_INTERVAL_MS = 60000;
@@ -12679,6 +12680,28 @@ var AutoResumePlugin = async (ctx, options) => {
12679
12680
  return;
12680
12681
  return msg.role ?? msg.info?.role;
12681
12682
  }
12683
+ async function lastAssistantEndsWithCelebration(sid) {
12684
+ try {
12685
+ const msgs = await getSessionMessages(sid);
12686
+ for (let i = msgs.length - 1;i >= 0; i--) {
12687
+ const msg = msgs[i];
12688
+ if (roleOf(msg) !== "assistant")
12689
+ continue;
12690
+ const parts = msg.parts;
12691
+ if (!parts)
12692
+ continue;
12693
+ let text = "";
12694
+ for (const part of parts) {
12695
+ if (part.type === "text") {
12696
+ text += part.text ?? "";
12697
+ }
12698
+ }
12699
+ const normalized = text.trim().replace(/[.!?]+$/, "");
12700
+ return normalized.endsWith("\uD83C\uDF89");
12701
+ }
12702
+ } catch {}
12703
+ return false;
12704
+ }
12682
12705
  async function getSessionStatusMap() {
12683
12706
  try {
12684
12707
  const response = await ctx.client.session.status();
@@ -13037,14 +13060,14 @@ var AutoResumePlugin = async (ctx, options) => {
13037
13060
  if (!bestCandidate) {
13038
13061
  const todos = w.todos || [];
13039
13062
  const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
13040
- if (hasOpenTodos && busyCount() === 1) {
13063
+ if (hasOpenTodos && busyCount() === 0) {
13041
13064
  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...`);
13042
13065
  bestCandidate = {
13043
13066
  prompt: "continue",
13044
13067
  source: "idle-with-open-todos",
13045
13068
  priority: 2
13046
13069
  };
13047
- } else if (hasOpenTodos && busyCount() > 1) {
13070
+ } else if (hasOpenTodos && busyCount() > 0) {
13048
13071
  await log("debug", `${short(sid)} - todos remain open but ${busyCount()} sessions busy (subagents running), skipping continue`);
13049
13072
  }
13050
13073
  }
@@ -13054,7 +13077,7 @@ var AutoResumePlugin = async (ctx, options) => {
13054
13077
  w.toolTextAttempts++;
13055
13078
  await log("info", `${bestCandidate.source} detected on ${short(sid)}! ` + `Attempt ${w.toolTextAttempts}/${maxRetries}. Sending recovery prompt...`);
13056
13079
  const timeSinceActivity = Date.now() - w.lastActivityAt;
13057
- if (timeSinceActivity < TOOL_TEXT_CHECK_DELAY_MS) {
13080
+ if (timeSinceActivity < MIN_ACTIVITY_GAP_MS) {
13058
13081
  await log("info", `${short(sid)} - skipping ${bestCandidate.source}, session was active ${Math.round(timeSinceActivity / 1000)}s ago`);
13059
13082
  return;
13060
13083
  }
@@ -13331,9 +13354,19 @@ var AutoResumePlugin = async (ctx, options) => {
13331
13354
  if (!w.isSubagent) {
13332
13355
  const todos = w.todos || [];
13333
13356
  const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
13334
- if (hasOpenTodos && currentBusy === 1 && !w.toolTextRecovered) {
13335
- await log("info", `${short(sid)} - idle with ${todos.filter((t) => t.status === "pending" || t.status === "in_progress").length} open todos. Sending continue...`);
13336
- tryResume(sid, w, "Idle with open todos");
13357
+ if (hasOpenTodos && currentBusy === 0 && !w.toolTextRecovered) {
13358
+ const isCelebration = await lastAssistantEndsWithCelebration(sid);
13359
+ if (isCelebration) {
13360
+ await log("info", `${short(sid)} - \uD83C\uDF89 detected in idle handler, skipping continue`);
13361
+ w.toolTextRecovered = true;
13362
+ if (w.toolTextTimer) {
13363
+ clearTimeout(w.toolTextTimer);
13364
+ w.toolTextTimer = null;
13365
+ }
13366
+ } else {
13367
+ await log("info", `${short(sid)} - idle with ${todos.filter((t) => t.status === "pending" || t.status === "in_progress").length} open todos. Sending continue...`);
13368
+ tryResume(sid, w, "Idle with open todos");
13369
+ }
13337
13370
  }
13338
13371
  }
13339
13372
  if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
@@ -13419,14 +13452,12 @@ var AutoResumePlugin = async (ctx, options) => {
13419
13452
  break;
13420
13453
  const props = ev.properties;
13421
13454
  const todos = props?.todos ?? [];
13422
- const w = sessions.get(sid);
13423
- if (w) {
13424
- w.todos = todos.map((t) => ({
13425
- content: t.content ?? "",
13426
- status: t.status ?? "pending",
13427
- priority: t.priority ?? "medium"
13428
- }));
13429
- }
13455
+ const w = ensureWatch(sid);
13456
+ w.todos = todos.map((t) => ({
13457
+ content: t.content ?? "",
13458
+ status: t.status ?? "pending",
13459
+ priority: t.priority ?? "medium"
13460
+ }));
13430
13461
  break;
13431
13462
  }
13432
13463
  case "session.error": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.0.24",
3
+ "version": "1.1.1",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",