opencode-auto-resume 1.1.15 → 1.1.17
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/README.md +11 -1
- package/dist/index.js +131 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,7 +225,16 @@ When the assistant ends a line with `:` ("Next, I will edit the file:") — anno
|
|
|
225
225
|
|
|
226
226
|
### Done-claim verification
|
|
227
227
|
|
|
228
|
-
If the assistant claims the task is done ("task done", "finished", "all complete") but open todos remain, the plugin sends `DONE_WITHOUT_WORK_PROMPT` asking the agent to verify and finish remaining work. If it claims done with **no** open todos
|
|
228
|
+
If the assistant claims the task is done ("task done", "finished", "all complete") but open todos remain, the plugin sends `DONE_WITHOUT_WORK_PROMPT` asking the agent to verify and finish remaining work. If it claims done with **no** open todos and the response carries no work description, the plugin sends `DONE_WITHOUT_DETAILS_PROMPT` — an imperative prompt demanding a concrete report (files changed, commands run, results). A response that already contains file paths, verification output, or result sections satisfies the demand on its own and never triggers the prompt. The budget is capped at `maxRetries` across busy cycles (going busy no longer re-arms it); only a genuinely new inbound user message re-arms it. Both use the real `todo.updated` event state — not regex on the message text.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### User-input awareness
|
|
233
|
+
|
|
234
|
+
The session is not stalled while the ball is in the user's court. Two gates stand down idle nudges:
|
|
235
|
+
|
|
236
|
+
- **Awaiting input**: the newest assistant message holds a `tool_use` part with `state.status: "pending"` (e.g. an open `question` tool call). All idle checks, the periodic recheck, delayed action-intent callbacks, and the tool-text scan skip prompting until a newer user message clears the gate. Completed tool calls never engage it.
|
|
237
|
+
- **Recently active user**: any inbound user message within `activeUserWindowMs` (default 15 minutes) means the user is engaged — likely composing a reply, which leaves no pending tool call behind. Open-todos nudges (idle + periodic), the tool-text reminder fallback, and action-intent callbacks stand down until the window expires.
|
|
229
238
|
|
|
230
239
|
---
|
|
231
240
|
|
|
@@ -389,6 +398,7 @@ With options:
|
|
|
389
398
|
| `silentDeadStreamMinTokens` | `200` | Min output tokens to treat a textless `finish:"unknown"` message as a dead stream |
|
|
390
399
|
| `busyStallStrategy` | `"continue"` | Busy-stall response: `"continue"`, `"abort"` (abort-first), or `"off"` (disabled) |
|
|
391
400
|
| `contextSaturationThreshold` | `0.85` | Ratio of used/usable context that routes a saturated parent to magic-context `ctx-wrapup` (only when magic-context is installed) |
|
|
401
|
+
| `activeUserWindowMs` | `900000` | Inbound-user-message recency window (15 min) during which idle nudges stand down (user likely composing) |
|
|
392
402
|
| `subagentNativeCompactionEnabled` | `false` | Opt-in native `session.summarize()` for saturated subagent sessions (no magic-context detection required) |
|
|
393
403
|
|
|
394
404
|
Message patterns are matched case-insensitively. Error names use exact match.
|
package/dist/index.js
CHANGED
|
@@ -12337,6 +12337,7 @@ tool.schema = exports_external;
|
|
|
12337
12337
|
// src/index.ts
|
|
12338
12338
|
var DEFAULT_CHUNK_TIMEOUT_MS = 45000;
|
|
12339
12339
|
var DEFAULT_CHECK_INTERVAL_MS = 5000;
|
|
12340
|
+
var DEFAULT_ACTIVE_USER_WINDOW_MS = 15 * 60000;
|
|
12340
12341
|
var DEFAULT_GRACE_PERIOD_MS = 3000;
|
|
12341
12342
|
var DEFAULT_MAX_RETRIES = 3;
|
|
12342
12343
|
var DEFAULT_MAX_BACKOFF_MS = 8000;
|
|
@@ -12456,6 +12457,15 @@ function containsDoneClaimPattern(text) {
|
|
|
12456
12457
|
`);
|
|
12457
12458
|
return DONE_CLAIM_PATTERNS.some((pat) => pat.test(lastLines));
|
|
12458
12459
|
}
|
|
12460
|
+
function containsWorkDescription(text) {
|
|
12461
|
+
if (/`[^`\n]*\.[a-zA-Z0-9]{1,8}[^`\n]*`/.test(text))
|
|
12462
|
+
return true;
|
|
12463
|
+
if (/[\w\-~.][\w\-.~\/]*\/[\w\-.~]*\.[a-zA-Z]{1,8}\b/.test(text))
|
|
12464
|
+
return true;
|
|
12465
|
+
if (/^(changed|modified|deleted|created|updated|renamed|moved|files?\s+changed|verification|verified|tests?(?:\s+run|\s+passing|\s+pass)?|results?|outcome|commands?\s+(?:run|executed))/im.test(text))
|
|
12466
|
+
return true;
|
|
12467
|
+
return false;
|
|
12468
|
+
}
|
|
12459
12469
|
function isStreamingFailure(errorName, errorMessage, errorNames = DEFAULT_STREAMING_FAILURE_ERROR_NAMES, messagePatterns = DEFAULT_STREAMING_FAILURE_MESSAGE_PATTERNS) {
|
|
12460
12470
|
if (!errorName && !errorMessage)
|
|
12461
12471
|
return false;
|
|
@@ -12622,6 +12632,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12622
12632
|
const busyStallStrategy = rawBusyStallStrategy === "abort" || rawBusyStallStrategy === "off" ? rawBusyStallStrategy : "continue";
|
|
12623
12633
|
const contextSaturationThreshold = options?.contextSaturationThreshold ?? 0.85;
|
|
12624
12634
|
const subagentNativeCompactionEnabled = options?.subagentNativeCompactionEnabled ?? false;
|
|
12635
|
+
const activeUserWindowMs = options?.activeUserWindowMs ?? DEFAULT_ACTIVE_USER_WINDOW_MS;
|
|
12625
12636
|
const dbg = (...args) => {
|
|
12626
12637
|
if (debug)
|
|
12627
12638
|
console.log("[debug]", ...args);
|
|
@@ -12717,6 +12728,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12717
12728
|
todoNudgeAttempts: 0,
|
|
12718
12729
|
taskCompleteOverrides: 0,
|
|
12719
12730
|
doneClaimNoTodosAttempts: 0,
|
|
12731
|
+
lastUserMessageAt: 0,
|
|
12720
12732
|
pendingTools: 0,
|
|
12721
12733
|
pendingCommands: 0,
|
|
12722
12734
|
pendingRecovery: false,
|
|
@@ -12897,6 +12909,8 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12897
12909
|
}
|
|
12898
12910
|
}
|
|
12899
12911
|
dbg(`Recovery prompt sent to ${short(sid)}: prompt="${text.length > 80 ? `${text.slice(0, 80)}...` : text}", agent=${agent ?? "(default)"}, model=${model ? `${model.providerID}/${model.modelID}` : "(default)"}`);
|
|
12912
|
+
if (w.userCancelled || w.completionSignaled)
|
|
12913
|
+
return;
|
|
12900
12914
|
const response = await ctx.client.session.prompt({
|
|
12901
12915
|
path: { id: sid },
|
|
12902
12916
|
body: {
|
|
@@ -12938,7 +12952,12 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12938
12952
|
setTimeout(async () => {
|
|
12939
12953
|
if (w.status !== "busy") {
|
|
12940
12954
|
if (w.pendingRecovery) {
|
|
12941
|
-
if (w.
|
|
12955
|
+
if (w.userCancelled || w.completionSignaled) {
|
|
12956
|
+
w.pendingRecovery = false;
|
|
12957
|
+
w.pendingRecoveryReason = null;
|
|
12958
|
+
w.recoveryAttempts = 0;
|
|
12959
|
+
await log("info", `${short(sid)} - recovery disarmed: session cancelled/completed while awaiting watchdog`);
|
|
12960
|
+
} else if (w.recoveryAttempts < maxRecoveryRetries) {
|
|
12942
12961
|
w.recoveryAttempts++;
|
|
12943
12962
|
dbg(`State transition on ${short(sid)}: recoveryAttempts=${w.recoveryAttempts - 1} -> ${w.recoveryAttempts}`);
|
|
12944
12963
|
w.watchdogRetryGuard = true;
|
|
@@ -12994,6 +13013,31 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12994
13013
|
return response.messages;
|
|
12995
13014
|
return [];
|
|
12996
13015
|
}
|
|
13016
|
+
function hasPendingUserInput(messages) {
|
|
13017
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
13018
|
+
const msg = messages[i];
|
|
13019
|
+
const rawRole = msg.role ?? msg.info?.role;
|
|
13020
|
+
if (rawRole === "user")
|
|
13021
|
+
return false;
|
|
13022
|
+
if (rawRole !== "assistant")
|
|
13023
|
+
continue;
|
|
13024
|
+
const parts = msg.parts;
|
|
13025
|
+
if (!parts)
|
|
13026
|
+
return false;
|
|
13027
|
+
for (const part of parts) {
|
|
13028
|
+
if (part.type !== "tool_use")
|
|
13029
|
+
continue;
|
|
13030
|
+
const state = part.state;
|
|
13031
|
+
if (state?.status === "pending")
|
|
13032
|
+
return true;
|
|
13033
|
+
}
|
|
13034
|
+
return false;
|
|
13035
|
+
}
|
|
13036
|
+
return false;
|
|
13037
|
+
}
|
|
13038
|
+
function userRecentlyActive(w) {
|
|
13039
|
+
return w.lastUserMessageAt > 0 && Date.now() - w.lastUserMessageAt < activeUserWindowMs;
|
|
13040
|
+
}
|
|
12997
13041
|
const messagesInflight = new Map;
|
|
12998
13042
|
async function getSessionMessages(sid) {
|
|
12999
13043
|
const inflight = messagesInflight.get(sid);
|
|
@@ -13311,7 +13355,6 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13311
13355
|
w.toolTextTimer = null;
|
|
13312
13356
|
}
|
|
13313
13357
|
w.todoNudgeAttempts = 0;
|
|
13314
|
-
w.doneClaimNoTodosAttempts = 0;
|
|
13315
13358
|
w.continueTimestamps = [];
|
|
13316
13359
|
}
|
|
13317
13360
|
function resetIdleFlags(w) {
|
|
@@ -13388,6 +13431,16 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13388
13431
|
await log("debug", `${short(sid)} - checking for tool-call-as-text (attempt ${w.toolTextAttempts + 1})`);
|
|
13389
13432
|
try {
|
|
13390
13433
|
const messages = await getSessionMessages(sid);
|
|
13434
|
+
if (hasPendingUserInput(messages)) {
|
|
13435
|
+
w.checkingToolText = false;
|
|
13436
|
+
await log("info", `${short(sid)} - awaiting user input (pending tool_use), skipping tool-text check`);
|
|
13437
|
+
return;
|
|
13438
|
+
}
|
|
13439
|
+
if (userRecentlyActive(w)) {
|
|
13440
|
+
w.checkingToolText = false;
|
|
13441
|
+
await log("info", `${short(sid)} - user recently active, skipping tool-text check`);
|
|
13442
|
+
return;
|
|
13443
|
+
}
|
|
13391
13444
|
const recent = messages.slice(-3);
|
|
13392
13445
|
let bestCandidate = null;
|
|
13393
13446
|
let allAssistantText = "";
|
|
@@ -13524,6 +13577,8 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13524
13577
|
source: "done-claim-no-emoji",
|
|
13525
13578
|
priority: 1
|
|
13526
13579
|
};
|
|
13580
|
+
} else if (containsWorkDescription(allAssistantText)) {
|
|
13581
|
+
await log("info", `${short(sid)} - model claims done with no open todos, but the response already contains a work description. Skipping details prompt...`);
|
|
13527
13582
|
} else if (w.doneClaimNoTodosAttempts < maxRetries) {
|
|
13528
13583
|
await log("info", `${short(sid)} - model claims done with no open todos. Sending details prompt (attempt ${w.doneClaimNoTodosAttempts + 1}/${maxRetries})...`);
|
|
13529
13584
|
bestCandidate = {
|
|
@@ -13666,6 +13721,11 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13666
13721
|
await new Promise((resolve) => setTimeout(resolve, ABORT_CONTINUE_DELAY_MS));
|
|
13667
13722
|
if (w.status === "busy")
|
|
13668
13723
|
w.status = "idle";
|
|
13724
|
+
if (w.userCancelled || w.completionSignaled) {
|
|
13725
|
+
w.aborting = false;
|
|
13726
|
+
await log("info", `${short(sid)} - abort+resume stood down: session cancelled/completed during abort delay`);
|
|
13727
|
+
return false;
|
|
13728
|
+
}
|
|
13669
13729
|
try {
|
|
13670
13730
|
await sendContinuePrompt(sid, continuePrompt, w);
|
|
13671
13731
|
await log("info", `${short(sid)} - abort+continue done`);
|
|
@@ -13918,6 +13978,18 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13918
13978
|
continue;
|
|
13919
13979
|
if (busyCount() !== 0)
|
|
13920
13980
|
continue;
|
|
13981
|
+
try {
|
|
13982
|
+
if (hasPendingUserInput(await getSessionMessages(sid))) {
|
|
13983
|
+
await log("info", `${short(sid)} - awaiting user input (pending tool_use), skipping periodic open-todos nudge`);
|
|
13984
|
+
continue;
|
|
13985
|
+
}
|
|
13986
|
+
} catch (e) {
|
|
13987
|
+
dbg(`periodic recheck sid=${short(sid)}: awaiting-input check error: ${e}`);
|
|
13988
|
+
}
|
|
13989
|
+
if (userRecentlyActive(w)) {
|
|
13990
|
+
await log("info", `${short(sid)} - user recently active, skipping periodic open-todos nudge`);
|
|
13991
|
+
continue;
|
|
13992
|
+
}
|
|
13921
13993
|
if ((w.todos || []).length === 0) {
|
|
13922
13994
|
const fetched = await fetchSessionTodos(sid);
|
|
13923
13995
|
if (fetched.length > 0) {
|
|
@@ -14011,12 +14083,24 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14011
14083
|
log("debug", `${short(sid)} -> idle (${currentBusy})`);
|
|
14012
14084
|
if (w.isSubagent) {
|
|
14013
14085
|
try {
|
|
14014
|
-
|
|
14086
|
+
let subAwaitingInput = false;
|
|
14087
|
+
try {
|
|
14088
|
+
subAwaitingInput = hasPendingUserInput(await getSessionMessages(sid));
|
|
14089
|
+
} catch (e) {
|
|
14090
|
+
dbg(`session.idle sid=${short(sid)}: awaiting-input check error: ${e}`);
|
|
14091
|
+
}
|
|
14092
|
+
if (subAwaitingInput) {
|
|
14093
|
+
await log("info", `${short(sid)} - awaiting user input (pending tool_use), skipping subagent saturation check`);
|
|
14094
|
+
} else if (userRecentlyActive(w)) {
|
|
14095
|
+
await log("info", `${short(sid)} - user recently active, skipping subagent saturation check`);
|
|
14096
|
+
} else if (w.lastTokenTotal > 0 && w.contextWrapupAttempts < 1 && !w.userCancelled && !w.completionSignaled && !w.aborting) {
|
|
14015
14097
|
const usable = await getUsableContextLimit(sid);
|
|
14016
14098
|
if (usable && w.lastTokenTotal / usable >= contextSaturationThreshold) {
|
|
14017
14099
|
if (subagentNativeCompactionEnabled) {
|
|
14018
14100
|
w.contextWrapupAttempts++;
|
|
14019
14101
|
await log("warn", `${short(sid)} - context saturation (subagent): ${w.lastTokenTotal}/${usable} tokens (${Math.round(w.lastTokenTotal / usable * 100)}% of usable); triggering native compaction`);
|
|
14102
|
+
if (w.userCancelled || w.completionSignaled)
|
|
14103
|
+
break;
|
|
14020
14104
|
await ctx.client.session.summarize({ path: { id: sid } });
|
|
14021
14105
|
}
|
|
14022
14106
|
}
|
|
@@ -14027,7 +14111,16 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14027
14111
|
}
|
|
14028
14112
|
}
|
|
14029
14113
|
if (!w.isSubagent) {
|
|
14030
|
-
|
|
14114
|
+
let awaitingUserInput = false;
|
|
14115
|
+
try {
|
|
14116
|
+
awaitingUserInput = hasPendingUserInput(await getSessionMessages(sid));
|
|
14117
|
+
} catch (e) {
|
|
14118
|
+
dbg(`session.idle sid=${short(sid)}: awaiting-input check error: ${e}`);
|
|
14119
|
+
}
|
|
14120
|
+
if (awaitingUserInput) {
|
|
14121
|
+
await log("info", `${short(sid)} - awaiting user input (pending tool_use), standing down all idle checks/nudges`);
|
|
14122
|
+
}
|
|
14123
|
+
if (!awaitingUserInput && !w.pendingRecovery && !w.completionSignaled && !w.userCancelled && !w.aborting) {
|
|
14031
14124
|
try {
|
|
14032
14125
|
const errInfo = getLastAssistantError(await getSessionMessages(sid));
|
|
14033
14126
|
if (errInfo && isStreamingFailure(errInfo.name, errInfo.message, streamingFailureErrorNames, streamingFailureMessagePatterns)) {
|
|
@@ -14067,7 +14160,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14067
14160
|
dbg(`session.idle sid=${short(sid)}: silent-dead-stream check error: ${errMsg}`);
|
|
14068
14161
|
}
|
|
14069
14162
|
try {
|
|
14070
|
-
if (w.lastTokenTotal > 0 && w.contextWrapupAttempts < 1 && !w.userCancelled && !w.completionSignaled) {
|
|
14163
|
+
if (w.lastTokenTotal > 0 && w.contextWrapupAttempts < 1 && !w.userCancelled && !w.completionSignaled && !userRecentlyActive(w)) {
|
|
14071
14164
|
const usable = await getUsableContextLimit(sid);
|
|
14072
14165
|
if (usable && w.lastTokenTotal / usable >= contextSaturationThreshold) {
|
|
14073
14166
|
const installed = await isMagicContextInstalled();
|
|
@@ -14075,6 +14168,8 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14075
14168
|
break;
|
|
14076
14169
|
w.contextWrapupAttempts++;
|
|
14077
14170
|
await log("warn", `${short(sid)} - context saturation: ${w.lastTokenTotal}/${usable} tokens (${Math.round(w.lastTokenTotal / usable * 100)}% of usable); sending magic-context wrapup command`);
|
|
14171
|
+
if (w.userCancelled || w.completionSignaled)
|
|
14172
|
+
break;
|
|
14078
14173
|
await ctx.client.session.command({
|
|
14079
14174
|
path: { id: sid },
|
|
14080
14175
|
body: { command: CTX_WRAPUP_TRIGGER, arguments: "" }
|
|
@@ -14095,7 +14190,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14095
14190
|
}
|
|
14096
14191
|
}
|
|
14097
14192
|
const open = getOpenTodos(todos);
|
|
14098
|
-
if (open.length > 0 && currentBusy === 0 && !w.completionSignaled && !w.userCancelled && w.todoNudgeAttempts < maxRetries) {
|
|
14193
|
+
if (open.length > 0 && currentBusy === 0 && !awaitingUserInput && !userRecentlyActive(w) && !w.completionSignaled && !w.userCancelled && w.todoNudgeAttempts < maxRetries) {
|
|
14099
14194
|
const isCelebration = await lastAssistantEndsWithCelebration(sid);
|
|
14100
14195
|
await log("info", `${short(sid)} - open todos=${open.length}, isCelebration=${isCelebration}, currentBusy=${currentBusy}`);
|
|
14101
14196
|
if (isCelebration) {
|
|
@@ -14121,6 +14216,14 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14121
14216
|
return;
|
|
14122
14217
|
}
|
|
14123
14218
|
const msgs = await getSessionMessages(idleSid);
|
|
14219
|
+
if (hasPendingUserInput(msgs)) {
|
|
14220
|
+
dbg(`session.idle sid=${short(idleSid)}: awaiting user input, skipping action-intent prompt`);
|
|
14221
|
+
return;
|
|
14222
|
+
}
|
|
14223
|
+
if (userRecentlyActive(idleW)) {
|
|
14224
|
+
dbg(`session.idle sid=${short(idleSid)}: user recently active, skipping action-intent prompt`);
|
|
14225
|
+
return;
|
|
14226
|
+
}
|
|
14124
14227
|
const lastAssistantMsg = msgs.slice().reverse().find((m) => (m.role ?? m.info?.role) === "assistant");
|
|
14125
14228
|
if (lastAssistantMsg) {
|
|
14126
14229
|
let lastText = "";
|
|
@@ -14188,6 +14291,15 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14188
14291
|
return;
|
|
14189
14292
|
}
|
|
14190
14293
|
const msgs = await getSessionMessages(sid);
|
|
14294
|
+
if (hasPendingUserInput(msgs)) {
|
|
14295
|
+
dbg(`session.idle sid=${short(sid)}: awaiting user input, skipping action-intent prompt`);
|
|
14296
|
+
return;
|
|
14297
|
+
}
|
|
14298
|
+
const w2pre = sessions.get(sid);
|
|
14299
|
+
if (w2pre && userRecentlyActive(w2pre)) {
|
|
14300
|
+
dbg(`session.idle sid=${short(sid)}: user recently active, skipping action-intent prompt`);
|
|
14301
|
+
return;
|
|
14302
|
+
}
|
|
14191
14303
|
const lastAssistantMsg = msgs.slice().reverse().find((m) => (m.role ?? m.info?.role) === "assistant");
|
|
14192
14304
|
if (lastAssistantMsg) {
|
|
14193
14305
|
let lastText = "";
|
|
@@ -14243,6 +14355,14 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14243
14355
|
const props = ev.properties;
|
|
14244
14356
|
const info = props?.info;
|
|
14245
14357
|
const role = info?.role ?? props?.role;
|
|
14358
|
+
if (role === "user") {
|
|
14359
|
+
const w = sessions.get(sid);
|
|
14360
|
+
if (w) {
|
|
14361
|
+
w.doneClaimNoTodosAttempts = 0;
|
|
14362
|
+
w.lastUserMessageAt = Date.now();
|
|
14363
|
+
}
|
|
14364
|
+
break;
|
|
14365
|
+
}
|
|
14246
14366
|
if (role !== "assistant")
|
|
14247
14367
|
break;
|
|
14248
14368
|
const tokens = info?.tokens ?? props?.tokens;
|
|
@@ -14341,8 +14461,12 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14341
14461
|
const openTodos = (w.todos || []).filter((t) => t.status === "pending" || t.status === "in_progress");
|
|
14342
14462
|
if (openTodos.length > 0 && w.taskCompleteOverrides < maxRetries) {
|
|
14343
14463
|
w.taskCompleteOverrides++;
|
|
14464
|
+
const reminder = buildOpenTodosReminder(w.todos);
|
|
14465
|
+
const blockMsg = `Mark any finished todos complete and do not redo completed work.
|
|
14466
|
+
${reminder}`;
|
|
14344
14467
|
await log("info", `${short(ctx.sessionID)} - task_complete blocked: ${openTodos.length} open todos remain (override ${w.taskCompleteOverrides}/${maxRetries})`);
|
|
14345
|
-
|
|
14468
|
+
await sendContinuePrompt(ctx.sessionID, blockMsg, w);
|
|
14469
|
+
return blockMsg;
|
|
14346
14470
|
}
|
|
14347
14471
|
}
|
|
14348
14472
|
w.toolTextRecovered = true;
|
package/package.json
CHANGED