opencode-auto-resume 1.0.4 → 1.0.5

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 +58 -104
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -119,7 +119,8 @@ var AutoResumePlugin = async (ctx, options) => {
119
119
  toolTextRecovered: false,
120
120
  toolTextAttempts: 0,
121
121
  continueTimestamps: [],
122
- idleSince: null
122
+ idleSince: null,
123
+ continuing: false
123
124
  };
124
125
  sessions.set(sid, w);
125
126
  }
@@ -202,6 +203,50 @@ var AutoResumePlugin = async (ctx, options) => {
202
203
  log("debug", `Cleaned up ${toDelete.length} idle session(s). Map size: ${sessions.size}`);
203
204
  }
204
205
  }
206
+ async function sendContinuePrompt(sid, text, w) {
207
+ if (w.continuing) {
208
+ await log("debug", `${short(sid)} - continue already in progress, skipping`);
209
+ return;
210
+ }
211
+ w.continuing = true;
212
+ try {
213
+ await ctx.client.session.prompt({
214
+ path: { id: sid },
215
+ body: { parts: [{ type: "text", text }] }
216
+ });
217
+ recordContinue(sid);
218
+ w.lastRetryAt = Date.now();
219
+ } finally {
220
+ w.continuing = false;
221
+ }
222
+ }
223
+ function extractMessages(response) {
224
+ if (Array.isArray(response))
225
+ return response;
226
+ if (Array.isArray(response.data))
227
+ return response.data;
228
+ if (Array.isArray(response.messages))
229
+ return response.messages;
230
+ return [];
231
+ }
232
+ function resetSessionFlags(w) {
233
+ w.userCancelled = false;
234
+ w.resumeAttempts = 0;
235
+ w.gaveUp = false;
236
+ w.orphanWatchStartAt = null;
237
+ w.aborting = false;
238
+ w.toolTextRecovered = false;
239
+ w.toolTextAttempts = 0;
240
+ w.continueTimestamps = [];
241
+ w.idleSince = null;
242
+ w.continuing = false;
243
+ }
244
+ function resetIdleFlags(w) {
245
+ w.userCancelled = false;
246
+ w.aborting = false;
247
+ w.orphanWatchStartAt = null;
248
+ w.idleSince = Date.now();
249
+ }
205
250
  async function checkForToolCallAsText(sid, w) {
206
251
  if (typeof sid !== "string" || !sid)
207
252
  return;
@@ -220,15 +265,7 @@ var AutoResumePlugin = async (ctx, options) => {
220
265
  const response = await ctx.client.session.messages({
221
266
  path: { id: sid }
222
267
  });
223
- const data = response;
224
- let messages = [];
225
- if (Array.isArray(data)) {
226
- messages = data;
227
- } else if (Array.isArray(data.data)) {
228
- messages = data.data;
229
- } else if (Array.isArray(data.messages)) {
230
- messages = data.messages;
231
- }
268
+ const messages = extractMessages(response);
232
269
  const recent = messages.slice(-3);
233
270
  for (const msg of recent) {
234
271
  const role = msg.role;
@@ -250,12 +287,7 @@ var AutoResumePlugin = async (ctx, options) => {
250
287
  await tryAbortAndResume(sid, w);
251
288
  } else {
252
289
  try {
253
- await ctx.client.session.prompt({
254
- path: { id: sid },
255
- body: { agent: typeof w.agent === "string" ? w.agent : undefined, parts: [{ type: "text", text: TOOL_TEXT_RECOVERY_PROMPT }] }
256
- });
257
- recordContinue(sid);
258
- w.lastRetryAt = Date.now();
290
+ await sendContinuePrompt(sid, TOOL_TEXT_RECOVERY_PROMPT, w);
259
291
  await log("info", `${short(sid)} - tool-call-as-text recovery sent (attempt ${w.toolTextAttempts})`);
260
292
  } catch (err) {
261
293
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -273,12 +305,7 @@ var AutoResumePlugin = async (ctx, options) => {
273
305
  await tryAbortAndResume(sid, w);
274
306
  } else {
275
307
  try {
276
- await ctx.client.session.prompt({
277
- path: { id: sid },
278
- body: { agent: typeof w.agent === "string" ? w.agent : undefined, parts: [{ type: "text", text: "continue" }] }
279
- });
280
- recordContinue(sid);
281
- w.lastRetryAt = Date.now();
308
+ await sendContinuePrompt(sid, "continue", w);
282
309
  await log("info", `${short(sid)} - ready-to-continue recovery sent (attempt ${w.toolTextAttempts})`);
283
310
  } catch (err) {
284
311
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -315,13 +342,8 @@ var AutoResumePlugin = async (ctx, options) => {
315
342
  if (w.status === "busy")
316
343
  w.status = "idle";
317
344
  try {
318
- await ctx.client.session.prompt({
319
- path: { id: sid },
320
- body: { agent: typeof w.agent === "string" ? w.agent : undefined, parts: [{ type: "text", text: "continue" }] }
321
- });
322
- recordContinue(sid);
345
+ await sendContinuePrompt(sid, "continue", w);
323
346
  await log("info", `${short(sid)} - abort+continue done`);
324
- w.lastRetryAt = Date.now();
325
347
  w.orphanWatchStartAt = null;
326
348
  w.resumeAttempts++;
327
349
  w.aborting = false;
@@ -350,15 +372,9 @@ var AutoResumePlugin = async (ctx, options) => {
350
372
  w.resumeAttempts++;
351
373
  const idleSec = Math.round((now - w.lastActivityAt) / 1000);
352
374
  await log("info", `${reason} on ${short(sid)} (${idleSec}s, retry ${w.resumeAttempts}/${maxRetries})`);
353
- const agent = typeof w.agent === "string" ? w.agent : undefined;
354
375
  try {
355
- await ctx.client.session.prompt({
356
- path: { id: sid },
357
- body: { agent, model: true, parts: [{ type: "text", text: "continue" }] }
358
- });
359
- recordContinue(sid);
376
+ await sendContinuePrompt(sid, "continue", w);
360
377
  await log("info", `${short(sid)} - retry sent`);
361
- w.lastRetryAt = now;
362
378
  return true;
363
379
  } catch (err) {
364
380
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -367,44 +383,10 @@ var AutoResumePlugin = async (ctx, options) => {
367
383
  return false;
368
384
  }
369
385
  }
370
- async function getSessionAgent(sid) {
371
- if (typeof sid !== "string" || !sid)
372
- return;
373
- try {
374
- const response = await ctx.client.session.messages({
375
- path: { id: sid }
376
- });
377
- const data = response;
378
- let messages = [];
379
- if (Array.isArray(data)) {
380
- messages = data;
381
- } else if (Array.isArray(data.data)) {
382
- messages = data.data;
383
- } else if (Array.isArray(data.messages)) {
384
- messages = data.messages;
385
- }
386
- for (let i = messages.length - 1;i >= 0; i--) {
387
- const msg = messages[i];
388
- const role = msg.role;
389
- if (role === "assistant") {
390
- const agent = msg.agent;
391
- if (agent)
392
- return agent;
393
- }
394
- }
395
- } catch {}
396
- return;
397
- }
398
386
  async function discoverSessions() {
399
387
  try {
400
388
  const response = await ctx.client.session.list();
401
- const data = response;
402
- let list = [];
403
- if (Array.isArray(data)) {
404
- list = data;
405
- } else if (Array.isArray(data.data)) {
406
- list = data.data;
407
- }
389
+ const list = extractMessages(response);
408
390
  for (const s of list) {
409
391
  const sid = s.id;
410
392
  if (sid) {
@@ -418,14 +400,7 @@ var AutoResumePlugin = async (ctx, options) => {
418
400
  w.idleSince = Date.now();
419
401
  }
420
402
  if (isNew) {
421
- const agent = await getSessionAgent(sid);
422
- if (agent) {
423
- const w = sessions.get(sid);
424
- w.agent = agent;
425
- log("debug", `Discovered session ${short(sid)} with agent: ${agent}`);
426
- } else {
427
- log("debug", `Discovered session ${short(sid)} via list()`);
428
- }
403
+ log("debug", `Discovered session ${short(sid)} via list()`);
429
404
  }
430
405
  }
431
406
  }
@@ -501,21 +476,11 @@ var AutoResumePlugin = async (ctx, options) => {
501
476
  w.status = statusType;
502
477
  if (statusType === "busy") {
503
478
  w.lastActivityAt = Date.now();
504
- w.userCancelled = false;
505
- w.resumeAttempts = 0;
506
- w.gaveUp = false;
507
- w.orphanWatchStartAt = null;
508
- w.aborting = false;
509
- w.toolTextRecovered = false;
510
- w.toolTextAttempts = 0;
511
- w.continueTimestamps = [];
512
- w.idleSince = null;
479
+ resetSessionFlags(w);
513
480
  log("debug", `${short(sid)} -> busy (${busyCount()})`);
514
481
  } else if (statusType === "idle") {
515
482
  w.status = "idle";
516
- w.userCancelled = false;
517
- w.aborting = false;
518
- w.idleSince = Date.now();
483
+ resetIdleFlags(w);
519
484
  const currentBusy = busyCount();
520
485
  if (prevBusyCount > 1 && currentBusy === 1) {
521
486
  const lone = getLoneBusySession();
@@ -555,10 +520,7 @@ var AutoResumePlugin = async (ctx, options) => {
555
520
  const w = sessions.get(sid);
556
521
  if (w) {
557
522
  w.status = "idle";
558
- w.userCancelled = false;
559
- w.orphanWatchStartAt = null;
560
- w.aborting = false;
561
- w.idleSince = Date.now();
523
+ resetIdleFlags(w);
562
524
  if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
563
525
  setTimeout(() => {
564
526
  checkForToolCallAsText(sid, w);
@@ -576,9 +538,7 @@ var AutoResumePlugin = async (ctx, options) => {
576
538
  if (w.status === "busy") {
577
539
  w.userCancelled = true;
578
540
  w.status = "idle";
579
- w.orphanWatchStartAt = null;
580
- w.aborting = false;
581
- w.idleSince = Date.now();
541
+ resetIdleFlags(w);
582
542
  }
583
543
  }
584
544
  log("info", "User abort (ESC)");
@@ -592,13 +552,7 @@ var AutoResumePlugin = async (ctx, options) => {
592
552
  }
593
553
  case "command.executed": {
594
554
  for (const [, w] of sessions) {
595
- w.userCancelled = false;
596
- w.resumeAttempts = 0;
597
- w.gaveUp = false;
598
- w.orphanWatchStartAt = null;
599
- w.aborting = false;
600
- w.toolTextRecovered = false;
601
- w.toolTextAttempts = 0;
555
+ resetSessionFlags(w);
602
556
  }
603
557
  break;
604
558
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-auto-resume",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "OpenCode plugin that automatically resumes stalled LLM sessions when thinking/streaming freezes mid-generation.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -21,12 +21,12 @@
21
21
  "author": "Daniele \"mte90\" Scasciafratte",
22
22
  "type": "module",
23
23
  "main": "dist/index.js",
24
- "scripts": {
24
+ "scripts": {
25
25
  "build": "bun build src/index.ts --outdir dist --target bun",
26
26
  "dev": "bun build src/index.ts --outdir dist --target bun --watch",
27
+ "test": "bun test",
27
28
  "prepublishOnly": "bun run build"
28
- },
29
- "files": [
29
+ }, "files": [
30
30
  "dist/index.js",
31
31
  "README.md",
32
32
  "LICENSE"