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.
- package/dist/index.js +58 -104
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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"
|