opencode-auto-resume 1.0.24 → 1.1.2
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 +87 -27
- 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;
|
|
@@ -12428,6 +12429,18 @@ function containsDoneClaimPattern(text) {
|
|
|
12428
12429
|
`);
|
|
12429
12430
|
return DONE_CLAIM_PATTERNS.some((pat) => pat.test(lastLines));
|
|
12430
12431
|
}
|
|
12432
|
+
function buildOpenTodosReminder(todos) {
|
|
12433
|
+
const open = todos.filter((t) => t.status === "pending" || t.status === "in_progress");
|
|
12434
|
+
if (open.length === 0)
|
|
12435
|
+
return "continue";
|
|
12436
|
+
const list = open.map((t, i) => `${i + 1}. [${t.status}] ${t.content}`).join(`
|
|
12437
|
+
`);
|
|
12438
|
+
const plural = open.length > 1 ? "s" : "";
|
|
12439
|
+
return `You have ${open.length} unfinished task${plural}:
|
|
12440
|
+
${list}
|
|
12441
|
+
|
|
12442
|
+
Please continue working on these task${plural}.`;
|
|
12443
|
+
}
|
|
12431
12444
|
var AutoResumePlugin = async (ctx, options) => {
|
|
12432
12445
|
const chunkTimeoutMs = options?.chunkTimeoutMs ?? DEFAULT_CHUNK_TIMEOUT_MS;
|
|
12433
12446
|
const checkIntervalMs = options?.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
@@ -12490,7 +12503,9 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12490
12503
|
interruptedContinueCount: 0,
|
|
12491
12504
|
recentToolCalls: [],
|
|
12492
12505
|
toolLoopAttempts: 0,
|
|
12493
|
-
isSubagent: false
|
|
12506
|
+
isSubagent: false,
|
|
12507
|
+
completionSignaled: false,
|
|
12508
|
+
todoNudgeAttempts: 0
|
|
12494
12509
|
};
|
|
12495
12510
|
sessions.set(sid, w);
|
|
12496
12511
|
}
|
|
@@ -12679,6 +12694,28 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12679
12694
|
return;
|
|
12680
12695
|
return msg.role ?? msg.info?.role;
|
|
12681
12696
|
}
|
|
12697
|
+
async function lastAssistantEndsWithCelebration(sid) {
|
|
12698
|
+
try {
|
|
12699
|
+
const msgs = await getSessionMessages(sid);
|
|
12700
|
+
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
12701
|
+
const msg = msgs[i];
|
|
12702
|
+
if (roleOf(msg) !== "assistant")
|
|
12703
|
+
continue;
|
|
12704
|
+
const parts = msg.parts;
|
|
12705
|
+
if (!parts)
|
|
12706
|
+
continue;
|
|
12707
|
+
let text = "";
|
|
12708
|
+
for (const part of parts) {
|
|
12709
|
+
if (part.type === "text") {
|
|
12710
|
+
text += part.text ?? "";
|
|
12711
|
+
}
|
|
12712
|
+
}
|
|
12713
|
+
const normalized = text.trim().replace(/[.!?]+$/, "");
|
|
12714
|
+
return normalized.endsWith("\uD83C\uDF89");
|
|
12715
|
+
}
|
|
12716
|
+
} catch {}
|
|
12717
|
+
return false;
|
|
12718
|
+
}
|
|
12682
12719
|
async function getSessionStatusMap() {
|
|
12683
12720
|
try {
|
|
12684
12721
|
const response = await ctx.client.session.status();
|
|
@@ -12804,10 +12841,10 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12804
12841
|
w.gaveUp = false;
|
|
12805
12842
|
w.orphanWatchStartAt = null;
|
|
12806
12843
|
w.aborting = false;
|
|
12807
|
-
|
|
12808
|
-
w.toolTextRecovered = false;
|
|
12809
|
-
}
|
|
12844
|
+
w.toolTextRecovered = false;
|
|
12810
12845
|
w.toolTextAttempts = 0;
|
|
12846
|
+
w.completionSignaled = false;
|
|
12847
|
+
w.todoNudgeAttempts = 0;
|
|
12811
12848
|
w.continueTimestamps = [];
|
|
12812
12849
|
w.idleSince = null;
|
|
12813
12850
|
w.continuing = false;
|
|
@@ -13028,6 +13065,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13028
13065
|
if (normalized.endsWith("\uD83C\uDF89") && (!bestCandidate || bestCandidate.priority > 0)) {
|
|
13029
13066
|
await log("info", `${short(sid)} - \uD83C\uDF89 completion detected, skipping continue`);
|
|
13030
13067
|
w.toolTextRecovered = true;
|
|
13068
|
+
w.completionSignaled = true;
|
|
13031
13069
|
if (w.toolTextTimer) {
|
|
13032
13070
|
clearTimeout(w.toolTextTimer);
|
|
13033
13071
|
w.toolTextTimer = null;
|
|
@@ -13037,24 +13075,34 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13037
13075
|
if (!bestCandidate) {
|
|
13038
13076
|
const todos = w.todos || [];
|
|
13039
13077
|
const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
|
|
13040
|
-
if (hasOpenTodos && busyCount() ===
|
|
13041
|
-
|
|
13078
|
+
if (hasOpenTodos && busyCount() === 0) {
|
|
13079
|
+
const reminder = buildOpenTodosReminder(todos);
|
|
13080
|
+
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 reminder...`);
|
|
13042
13081
|
bestCandidate = {
|
|
13043
|
-
prompt:
|
|
13044
|
-
source: "idle-with-open-todos",
|
|
13082
|
+
prompt: reminder,
|
|
13083
|
+
source: "idle-with-open-todos-reminder",
|
|
13045
13084
|
priority: 2
|
|
13046
13085
|
};
|
|
13047
|
-
} else if (hasOpenTodos && busyCount() >
|
|
13086
|
+
} else if (hasOpenTodos && busyCount() > 0) {
|
|
13048
13087
|
await log("debug", `${short(sid)} - todos remain open but ${busyCount()} sessions busy (subagents running), skipping continue`);
|
|
13049
13088
|
}
|
|
13050
13089
|
}
|
|
13051
13090
|
if (!bestCandidate)
|
|
13052
13091
|
return;
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13092
|
+
const isOpenTodosReminder = bestCandidate.source === "idle-with-open-todos-reminder";
|
|
13093
|
+
if (isOpenTodosReminder) {
|
|
13094
|
+
if (w.todoNudgeAttempts >= maxRetries) {
|
|
13095
|
+
await log("info", `${short(sid)} - max open-todos nudges (${maxRetries}) reached for this idle cycle, waiting for activity`);
|
|
13096
|
+
return;
|
|
13097
|
+
}
|
|
13098
|
+
w.todoNudgeAttempts++;
|
|
13099
|
+
} else {
|
|
13100
|
+
w.toolTextRecovered = true;
|
|
13101
|
+
w.toolTextAttempts++;
|
|
13102
|
+
}
|
|
13103
|
+
await log("info", `${bestCandidate.source} detected on ${short(sid)}! ` + `Attempt ${isOpenTodosReminder ? w.todoNudgeAttempts : w.toolTextAttempts}/${maxRetries}. Sending recovery prompt...`);
|
|
13056
13104
|
const timeSinceActivity = Date.now() - w.lastActivityAt;
|
|
13057
|
-
if (timeSinceActivity <
|
|
13105
|
+
if (timeSinceActivity < MIN_ACTIVITY_GAP_MS) {
|
|
13058
13106
|
await log("info", `${short(sid)} - skipping ${bestCandidate.source}, session was active ${Math.round(timeSinceActivity / 1000)}s ago`);
|
|
13059
13107
|
return;
|
|
13060
13108
|
}
|
|
@@ -13118,7 +13166,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13118
13166
|
return false;
|
|
13119
13167
|
}
|
|
13120
13168
|
}
|
|
13121
|
-
async function tryResume(sid, w, reason) {
|
|
13169
|
+
async function tryResume(sid, w, reason, prompt) {
|
|
13122
13170
|
if (typeof sid !== "string" || !sid) {
|
|
13123
13171
|
await log("warn", `tryResume called with invalid sid: ${sid}`);
|
|
13124
13172
|
return false;
|
|
@@ -13142,7 +13190,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13142
13190
|
const idleSec = Math.round((now - w.lastActivityAt) / 1000);
|
|
13143
13191
|
await log("info", `${reason} on ${short(sid)} (${idleSec}s, retry ${w.resumeAttempts}/${maxRetries})`);
|
|
13144
13192
|
try {
|
|
13145
|
-
await sendContinuePrompt(sid, "continue", w);
|
|
13193
|
+
await sendContinuePrompt(sid, prompt ?? "continue", w);
|
|
13146
13194
|
await log("info", `${short(sid)} - retry sent`);
|
|
13147
13195
|
return true;
|
|
13148
13196
|
} catch (err) {
|
|
@@ -13331,12 +13379,24 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13331
13379
|
if (!w.isSubagent) {
|
|
13332
13380
|
const todos = w.todos || [];
|
|
13333
13381
|
const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
|
|
13334
|
-
if (hasOpenTodos && currentBusy ===
|
|
13335
|
-
|
|
13336
|
-
|
|
13382
|
+
if (hasOpenTodos && currentBusy === 0 && !w.completionSignaled) {
|
|
13383
|
+
const isCelebration = await lastAssistantEndsWithCelebration(sid);
|
|
13384
|
+
if (isCelebration) {
|
|
13385
|
+
await log("info", `${short(sid)} - \uD83C\uDF89 detected in idle handler, skipping continue`);
|
|
13386
|
+
w.toolTextRecovered = true;
|
|
13387
|
+
w.completionSignaled = true;
|
|
13388
|
+
if (w.toolTextTimer) {
|
|
13389
|
+
clearTimeout(w.toolTextTimer);
|
|
13390
|
+
w.toolTextTimer = null;
|
|
13391
|
+
}
|
|
13392
|
+
} else {
|
|
13393
|
+
const reminder = buildOpenTodosReminder(todos);
|
|
13394
|
+
await log("info", `${short(sid)} - idle with ${todos.filter((t) => t.status === "pending" || t.status === "in_progress").length} open todos. Sending reminder...`);
|
|
13395
|
+
tryResume(sid, w, "Idle with open todos", reminder);
|
|
13396
|
+
}
|
|
13337
13397
|
}
|
|
13338
13398
|
}
|
|
13339
|
-
if (!w.
|
|
13399
|
+
if (!w.completionSignaled && w.toolTextAttempts < maxRetries) {
|
|
13340
13400
|
if (w.toolTextTimer)
|
|
13341
13401
|
clearTimeout(w.toolTextTimer);
|
|
13342
13402
|
const checkDelay = statusType === "interrupted" ? 500 : TOOL_TEXT_CHECK_DELAY_MS;
|
|
@@ -13419,14 +13479,12 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13419
13479
|
break;
|
|
13420
13480
|
const props = ev.properties;
|
|
13421
13481
|
const todos = props?.todos ?? [];
|
|
13422
|
-
const w =
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
}));
|
|
13429
|
-
}
|
|
13482
|
+
const w = ensureWatch(sid);
|
|
13483
|
+
w.todos = todos.map((t) => ({
|
|
13484
|
+
content: t.content ?? "",
|
|
13485
|
+
status: t.status ?? "pending",
|
|
13486
|
+
priority: t.priority ?? "medium"
|
|
13487
|
+
}));
|
|
13430
13488
|
break;
|
|
13431
13489
|
}
|
|
13432
13490
|
case "session.error": {
|
|
@@ -13466,6 +13524,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13466
13524
|
if (w) {
|
|
13467
13525
|
if (!w.isSubagent) {
|
|
13468
13526
|
w.toolTextRecovered = true;
|
|
13527
|
+
w.completionSignaled = true;
|
|
13469
13528
|
if (w.toolTextTimer) {
|
|
13470
13529
|
clearTimeout(w.toolTextTimer);
|
|
13471
13530
|
w.toolTextTimer = null;
|
|
@@ -13495,5 +13554,6 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13495
13554
|
var src_default = AutoResumePlugin;
|
|
13496
13555
|
export {
|
|
13497
13556
|
src_default as default,
|
|
13557
|
+
buildOpenTodosReminder,
|
|
13498
13558
|
AutoResumePlugin
|
|
13499
13559
|
};
|
package/package.json
CHANGED