opencode-auto-resume 1.0.16 → 1.0.18
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 +105 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12351,6 +12351,7 @@ var IDLE_CLEANUP_MS = 10 * 60000;
|
|
|
12351
12351
|
var SESSION_DISCOVERY_INTERVAL_MS = 60000;
|
|
12352
12352
|
var TOOL_TEXT_RECOVERY_PROMPT = "Your last message contained a raw tool call printed as text instead of being executed. " + "Please use the proper tool calling mechanism to execute it.";
|
|
12353
12353
|
var THINKING_TOOL_RECOVERY_PROMPT = "I noticed you have a tool call generated in your thinking/reasoning. " + "Please execute it using the proper tool calling mechanism instead of keeping it in reasoning.";
|
|
12354
|
+
var TOOL_LOOP_RECOVERY_PROMPT = "I notice you've been calling the same tool multiple times in a row without making progress. " + "Please step back and reassess your approach. Consider: " + "1) Are you stuck in a loop? 2) Do you need different information first? " + "3) Should you try a different tool or break the task into smaller steps? " + "Take a moment to think about what's blocking you and propose a different strategy.";
|
|
12354
12355
|
var TOOL_TEXT_PATTERNS = [
|
|
12355
12356
|
/<function\s*=/i,
|
|
12356
12357
|
/<function>/i,
|
|
@@ -12461,9 +12462,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12461
12462
|
}
|
|
12462
12463
|
async function log(level, msg) {
|
|
12463
12464
|
try {
|
|
12464
|
-
await ctx.client.app.log({
|
|
12465
|
-
body: { service: "auto-resume", level, message: msg }
|
|
12466
|
-
});
|
|
12465
|
+
await ctx.client.app.log({ body: { service: "auto-resume", level, message: msg } });
|
|
12467
12466
|
} catch {}
|
|
12468
12467
|
}
|
|
12469
12468
|
function ensureWatch(sid) {
|
|
@@ -12488,7 +12487,10 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12488
12487
|
toolTextTimer: null,
|
|
12489
12488
|
checkingToolText: false,
|
|
12490
12489
|
lastSubagentCheckAt: 0,
|
|
12491
|
-
interruptedContinueCount: 0
|
|
12490
|
+
interruptedContinueCount: 0,
|
|
12491
|
+
recentToolCalls: [],
|
|
12492
|
+
toolLoopAttempts: 0,
|
|
12493
|
+
isSubagent: false
|
|
12492
12494
|
};
|
|
12493
12495
|
sessions.set(sid, w);
|
|
12494
12496
|
}
|
|
@@ -12723,7 +12725,9 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12723
12725
|
w.gaveUp = false;
|
|
12724
12726
|
w.orphanWatchStartAt = null;
|
|
12725
12727
|
w.aborting = false;
|
|
12726
|
-
w.
|
|
12728
|
+
if (w.isSubagent) {
|
|
12729
|
+
w.toolTextRecovered = false;
|
|
12730
|
+
}
|
|
12727
12731
|
w.toolTextAttempts = 0;
|
|
12728
12732
|
w.continueTimestamps = [];
|
|
12729
12733
|
w.idleSince = null;
|
|
@@ -12731,6 +12735,8 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12731
12735
|
w.todoCheckAttempts = 0;
|
|
12732
12736
|
w.checkingToolText = false;
|
|
12733
12737
|
w.interruptedContinueCount = 0;
|
|
12738
|
+
w.recentToolCalls = [];
|
|
12739
|
+
w.toolLoopAttempts = 0;
|
|
12734
12740
|
if (w.toolTextTimer) {
|
|
12735
12741
|
clearTimeout(w.toolTextTimer);
|
|
12736
12742
|
w.toolTextTimer = null;
|
|
@@ -12742,6 +12748,41 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12742
12748
|
w.orphanWatchStartAt = null;
|
|
12743
12749
|
w.idleSince = Date.now();
|
|
12744
12750
|
}
|
|
12751
|
+
function detectPatternLoop(recentTools) {
|
|
12752
|
+
if (recentTools.length < 6)
|
|
12753
|
+
return false;
|
|
12754
|
+
for (const patternLen of [2, 3, 4, 5]) {
|
|
12755
|
+
if (recentTools.length < patternLen * 3)
|
|
12756
|
+
continue;
|
|
12757
|
+
const pattern = recentTools.slice(-patternLen);
|
|
12758
|
+
let matches = 0;
|
|
12759
|
+
for (let i = recentTools.length - patternLen * 2;i >= 0; i -= patternLen) {
|
|
12760
|
+
const slice = recentTools.slice(i, i + patternLen);
|
|
12761
|
+
if (slice.length !== patternLen)
|
|
12762
|
+
break;
|
|
12763
|
+
const isMatch = slice.every((tool3, idx) => tool3 === pattern[idx]);
|
|
12764
|
+
if (!isMatch)
|
|
12765
|
+
break;
|
|
12766
|
+
matches++;
|
|
12767
|
+
}
|
|
12768
|
+
if (matches >= 2)
|
|
12769
|
+
return true;
|
|
12770
|
+
}
|
|
12771
|
+
return false;
|
|
12772
|
+
}
|
|
12773
|
+
function trackToolCall(w, toolName2) {
|
|
12774
|
+
const now = Date.now();
|
|
12775
|
+
w.recentToolCalls = w.recentToolCalls.filter((call) => now - call.at < 120000);
|
|
12776
|
+
w.recentToolCalls.push({ toolName: toolName2, at: now });
|
|
12777
|
+
const recentTools = w.recentToolCalls.slice(-15).map((call) => call.toolName);
|
|
12778
|
+
if (recentTools.length < 6)
|
|
12779
|
+
return false;
|
|
12780
|
+
const lastTool = recentTools[recentTools.length - 1];
|
|
12781
|
+
const consecutiveSame = recentTools.slice(-5).filter((tool3) => tool3 === lastTool).length;
|
|
12782
|
+
if (consecutiveSame >= 3)
|
|
12783
|
+
return true;
|
|
12784
|
+
return detectPatternLoop(recentTools);
|
|
12785
|
+
}
|
|
12745
12786
|
async function checkForToolCallAsText(sid, w) {
|
|
12746
12787
|
if (typeof sid !== "string" || !sid)
|
|
12747
12788
|
return;
|
|
@@ -12773,6 +12814,24 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12773
12814
|
const rawRole = msg.role ?? msg.info?.role;
|
|
12774
12815
|
if (rawRole !== "assistant")
|
|
12775
12816
|
continue;
|
|
12817
|
+
const toolCall = msg.toolCall;
|
|
12818
|
+
if (toolCall && typeof toolCall === "object" && "name" in toolCall) {
|
|
12819
|
+
const toolName2 = toolCall.name;
|
|
12820
|
+
if (toolName2) {
|
|
12821
|
+
trackToolCall(w, toolName2);
|
|
12822
|
+
}
|
|
12823
|
+
}
|
|
12824
|
+
const toolCalls = msg.tool_calls;
|
|
12825
|
+
if (toolCalls) {
|
|
12826
|
+
for (const tc of toolCalls) {
|
|
12827
|
+
if (typeof tc === "object" && "name" in tc) {
|
|
12828
|
+
const toolName2 = tc.name;
|
|
12829
|
+
if (toolName2) {
|
|
12830
|
+
trackToolCall(w, toolName2);
|
|
12831
|
+
}
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
}
|
|
12776
12835
|
const parts = msg.parts;
|
|
12777
12836
|
if (!parts)
|
|
12778
12837
|
continue;
|
|
@@ -12788,21 +12847,34 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12788
12847
|
isReasoning = true;
|
|
12789
12848
|
} else if (partType === "tool_use") {
|
|
12790
12849
|
isToolUse = true;
|
|
12791
|
-
const
|
|
12792
|
-
text = `tool_use: ${
|
|
12850
|
+
const toolName2 = part.name ?? "unknown";
|
|
12851
|
+
text = `tool_use: ${toolName2}`;
|
|
12793
12852
|
} else {
|
|
12794
12853
|
continue;
|
|
12795
12854
|
}
|
|
12796
12855
|
allAssistantText += text + `
|
|
12797
12856
|
`;
|
|
12798
12857
|
if (isToolUse) {
|
|
12799
|
-
const
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12858
|
+
const isLoop = trackToolCall(w, toolName);
|
|
12859
|
+
if (isLoop && w.toolLoopAttempts < 2) {
|
|
12860
|
+
w.toolLoopAttempts++;
|
|
12861
|
+
const candidate = {
|
|
12862
|
+
prompt: TOOL_LOOP_RECOVERY_PROMPT,
|
|
12863
|
+
source: "tool-loop",
|
|
12864
|
+
priority: 0
|
|
12865
|
+
};
|
|
12866
|
+
if (!bestCandidate || candidate.priority < bestCandidate.priority) {
|
|
12867
|
+
bestCandidate = candidate;
|
|
12868
|
+
}
|
|
12869
|
+
} else {
|
|
12870
|
+
const candidate = {
|
|
12871
|
+
prompt: "continue",
|
|
12872
|
+
source: "tool-use",
|
|
12873
|
+
priority: 1
|
|
12874
|
+
};
|
|
12875
|
+
if (!bestCandidate || candidate.priority < bestCandidate.priority) {
|
|
12876
|
+
bestCandidate = candidate;
|
|
12877
|
+
}
|
|
12806
12878
|
}
|
|
12807
12879
|
}
|
|
12808
12880
|
if (containsToolCallAsText(text)) {
|
|
@@ -12872,13 +12944,15 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12872
12944
|
if (!bestCandidate) {
|
|
12873
12945
|
const todos = w.todos || [];
|
|
12874
12946
|
const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
|
|
12875
|
-
if (hasOpenTodos) {
|
|
12947
|
+
if (hasOpenTodos && busyCount() === 1) {
|
|
12876
12948
|
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...`);
|
|
12877
12949
|
bestCandidate = {
|
|
12878
12950
|
prompt: "continue",
|
|
12879
12951
|
source: "idle-with-open-todos",
|
|
12880
12952
|
priority: 2
|
|
12881
12953
|
};
|
|
12954
|
+
} else if (hasOpenTodos && busyCount() > 1) {
|
|
12955
|
+
await log("debug", `${short(sid)} - todos remain open but ${busyCount()} sessions busy (subagents running), skipping continue`);
|
|
12882
12956
|
}
|
|
12883
12957
|
}
|
|
12884
12958
|
if (!bestCandidate)
|
|
@@ -13111,11 +13185,20 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13111
13185
|
const lone = getLoneBusySession();
|
|
13112
13186
|
if (lone && lone.w.orphanWatchStartAt === null) {
|
|
13113
13187
|
lone.w.orphanWatchStartAt = Date.now();
|
|
13188
|
+
w.isSubagent = true;
|
|
13114
13189
|
log("info", `Subagent finished, parent ${short(lone.sid)} stuck. Orphan watch (${subagentWaitMs / 1000}s).`);
|
|
13115
13190
|
}
|
|
13116
13191
|
}
|
|
13117
13192
|
prevBusyCount = currentBusy;
|
|
13118
13193
|
log("debug", `${short(sid)} -> idle (${currentBusy})${statusType === "interrupted" ? " (interrupted)" : ""}`);
|
|
13194
|
+
if (!w.isSubagent) {
|
|
13195
|
+
const todos = w.todos || [];
|
|
13196
|
+
const hasOpenTodos = todos.some((t) => t.status === "pending" || t.status === "in_progress");
|
|
13197
|
+
if (hasOpenTodos && currentBusy === 1 && !w.toolTextRecovered) {
|
|
13198
|
+
await log("info", `${short(sid)} - idle with ${todos.filter((t) => t.status === "pending" || t.status === "in_progress").length} open todos. Sending continue...`);
|
|
13199
|
+
tryResume(sid, w, "Idle with open todos");
|
|
13200
|
+
}
|
|
13201
|
+
}
|
|
13119
13202
|
if (!w.toolTextRecovered && w.toolTextAttempts < maxRetries) {
|
|
13120
13203
|
if (w.toolTextTimer)
|
|
13121
13204
|
clearTimeout(w.toolTextTimer);
|
|
@@ -13244,12 +13327,14 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13244
13327
|
execute: async (_args, ctx2) => {
|
|
13245
13328
|
const w = sessions.get(ctx2.sessionID);
|
|
13246
13329
|
if (w) {
|
|
13247
|
-
w.
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13330
|
+
if (!w.isSubagent) {
|
|
13331
|
+
w.toolTextRecovered = true;
|
|
13332
|
+
if (w.toolTextTimer) {
|
|
13333
|
+
clearTimeout(w.toolTextTimer);
|
|
13334
|
+
w.toolTextTimer = null;
|
|
13335
|
+
}
|
|
13251
13336
|
}
|
|
13252
|
-
log("info", `${short(ctx2.sessionID)} - task_complete called, agent done`);
|
|
13337
|
+
log("info", `${short(ctx2.sessionID)} - task_complete called, ${w.isSubagent ? "subagent" : "agent"} done`);
|
|
13253
13338
|
}
|
|
13254
13339
|
return "Task completion acknowledged. No further continuation will be sent.";
|
|
13255
13340
|
}
|
package/package.json
CHANGED