opencode-auto-resume 1.1.18 → 1.1.20
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 +10 -7
- package/dist/index.js +126 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ LLM sessions fail in predictable ways. This plugin monitors all sessions and aut
|
|
|
10
10
|
|
|
11
11
|
### Stall recovery
|
|
12
12
|
|
|
13
|
-
The stream goes silent but the session stays "busy". The UI shows a blinking cursor with no progress. If no events arrive for
|
|
13
|
+
The stream goes silent but the session stays "busy". The UI shows a blinking cursor with no progress. If no events arrive for ~3 minutes (`chunkTimeoutMs` + `gracePeriodMs`), the plugin sends `"continue"` with exponential backoff. After 3 failed attempts it gives up.
|
|
14
14
|
|
|
15
15
|
The `busyStallStrategy` option controls this path: `"continue"` (default), `"abort"` (abort-first), or `"off"` — see [Recovery model](#recovery-model) for what a busy prompt can and cannot do while the runner is live.
|
|
16
16
|
|
|
@@ -250,7 +250,7 @@ If the assistant claims the task is done ("task done", "finished", "all complete
|
|
|
250
250
|
The session is not stalled while the ball is in the user's court. Two gates stand down idle nudges:
|
|
251
251
|
|
|
252
252
|
- **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.
|
|
253
|
-
- **Recently active user**: any inbound user message within `activeUserWindowMs` (default
|
|
253
|
+
- **Recently active user**: any inbound user message within `activeUserWindowMs` (default 5 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.
|
|
254
254
|
|
|
255
255
|
---
|
|
256
256
|
|
|
@@ -367,7 +367,7 @@ With options:
|
|
|
367
367
|
{
|
|
368
368
|
"plugin": [
|
|
369
369
|
["opencode-auto-resume", {
|
|
370
|
-
"chunkTimeoutMs":
|
|
370
|
+
"chunkTimeoutMs": 180000,
|
|
371
371
|
"gracePeriodMs": 3000,
|
|
372
372
|
"maxRetries": 3
|
|
373
373
|
}]
|
|
@@ -382,7 +382,7 @@ With options:
|
|
|
382
382
|
"plugin": [
|
|
383
383
|
[
|
|
384
384
|
"file:///home/YOURUSER/.config/opencode/plugins/opencode-auto-resume/dist/index.js",
|
|
385
|
-
{ "chunkTimeoutMs":
|
|
385
|
+
{ "chunkTimeoutMs": 180000, "maxRetries": 3 }
|
|
386
386
|
]
|
|
387
387
|
]
|
|
388
388
|
}
|
|
@@ -392,7 +392,7 @@ With options:
|
|
|
392
392
|
|
|
393
393
|
| Option | Default | Description |
|
|
394
394
|
|---|---|---|
|
|
395
|
-
| `chunkTimeoutMs` | `
|
|
395
|
+
| `chunkTimeoutMs` | `180000` | Inactivity timeout before considering stream stalled |
|
|
396
396
|
| `gracePeriodMs` | `3000` | Extra wait before acting (lets ESC/status events arrive) |
|
|
397
397
|
| `checkIntervalMs` | `5000` | Timer poll interval |
|
|
398
398
|
| `maxRetries` | `3` | Max auto-resume attempts before giving up |
|
|
@@ -407,6 +407,7 @@ With options:
|
|
|
407
407
|
| `toolTextCheckDelayMs` | `3000` | Delay before scanning an idle session for tool-as-text; also the recovery watchdog delay |
|
|
408
408
|
| `minActivityGapMs` | `1000` | Skip recovery if the session was active within this gap |
|
|
409
409
|
| `warmupMs` | `15000` | Action-intent detection disabled while a session is younger than this |
|
|
410
|
+
| `discoveryDelayMs` | `5000` | Delay before the initial session discovery run after plugin attach |
|
|
410
411
|
| `debug` | `false` | Enable `[debug]` console diagnostics |
|
|
411
412
|
| `resumeOnActionIntent` | `true` | Enable action-intent (`:`-terminated line) nudges |
|
|
412
413
|
| `continuePrompt` | `"continue"` | Prompt text for stall/streaming/dead-stream recovery |
|
|
@@ -415,10 +416,12 @@ With options:
|
|
|
415
416
|
| `thinkingToolRecoveryPrompt` | `THINKING_TOOL_RECOVERY_PROMPT` | Override the thinking-tool recovery prompt |
|
|
416
417
|
| `doneWithoutWorkPrompt` | `DONE_WITHOUT_WORK_PROMPT` | Override the done-claim-with-open-todos prompt |
|
|
417
418
|
| `doneWithoutDetailsPrompt` | `DONE_WITHOUT_DETAILS_PROMPT` | Override the done-claim-with-no-todos report prompt |
|
|
419
|
+
| `doneClaimPatterns` | `DONE_CLAIM_PATTERNS` | Array of regex strings overriding the default done-claim detection patterns (case-insensitive, multiline). Invalid regexes are skipped. Empty array falls back to defaults. |
|
|
420
|
+
| `readyToContinuePatterns` | `READY_TO_CONTINUE_PATTERNS` | Array of regex strings overriding the default ready-to-continue detection patterns (case-insensitive). Invalid regexes are skipped. Empty array falls back to defaults. |
|
|
418
421
|
| `silentDeadStreamMinTokens` | `200` | Min output tokens to treat a textless `finish:"unknown"` message as a dead stream |
|
|
419
422
|
| `busyStallStrategy` | `"continue"` | Busy-stall response: `"continue"`, `"abort"` (abort-first), or `"off"` (disabled) |
|
|
420
423
|
| `contextSaturationThreshold` | `0.85` | Ratio of used/usable context that routes a saturated parent to magic-context `ctx-wrapup` (only when magic-context is installed) |
|
|
421
|
-
| `activeUserWindowMs` | `
|
|
424
|
+
| `activeUserWindowMs` | `300000` | Inbound-user-message recency window (5 min) during which idle nudges stand down (user likely composing) |
|
|
422
425
|
| `subagentNativeCompactionEnabled` | `false` | Opt-in native `session.summarize()` for saturated subagent sessions (no magic-context detection required) |
|
|
423
426
|
|
|
424
427
|
Message patterns are matched case-insensitively. Error names use exact match.
|
|
@@ -443,7 +446,7 @@ The plugin handles all recovery automatically — no manual intervention needed.
|
|
|
443
446
|
| Problem | Solution |
|
|
444
447
|
|---|---|
|
|
445
448
|
| Resumes after ESC | Increase `gracePeriodMs` to `5000` |
|
|
446
|
-
|
|
|
449
|
+
| Stall recovery too aggressive | Decrease `chunkTimeoutMs` (e.g. `45000`) |
|
|
447
450
|
| Too slow to react | Decrease `checkIntervalMs` to `2000` |
|
|
448
451
|
| Orphan parent not detected | Increase `subagentWaitMs` to `20000` |
|
|
449
452
|
| Hallucination loop not caught | Decrease `loopMaxContinues` to `2` |
|
package/dist/index.js
CHANGED
|
@@ -12335,9 +12335,10 @@ function tool(input) {
|
|
|
12335
12335
|
}
|
|
12336
12336
|
tool.schema = exports_external;
|
|
12337
12337
|
// src/index.ts
|
|
12338
|
-
var DEFAULT_CHUNK_TIMEOUT_MS =
|
|
12338
|
+
var DEFAULT_CHUNK_TIMEOUT_MS = 180000;
|
|
12339
12339
|
var DEFAULT_CHECK_INTERVAL_MS = 5000;
|
|
12340
|
-
var
|
|
12340
|
+
var DEFAULT_DISCOVERY_DELAY_MS = 5000;
|
|
12341
|
+
var DEFAULT_ACTIVE_USER_WINDOW_MS = 5 * 60000;
|
|
12341
12342
|
var DEFAULT_GRACE_PERIOD_MS = 3000;
|
|
12342
12343
|
var DEFAULT_MAX_RETRIES = 3;
|
|
12343
12344
|
var DEFAULT_MAX_BACKOFF_MS = 8000;
|
|
@@ -12352,6 +12353,7 @@ var DEFAULT_MIN_ACTIVITY_GAP_MS = 1000;
|
|
|
12352
12353
|
var DEFAULT_WARMUP_MS = 15000;
|
|
12353
12354
|
var DEFAULT_SILENT_DEAD_STREAM_MIN_TOKENS = 200;
|
|
12354
12355
|
var DEFAULT_DEBUG = false;
|
|
12356
|
+
var OOC_ERROR_RE = /exceeds the available context size|context size \(\d+\)|too large to compact|too many tokens|prompt is too long/i;
|
|
12355
12357
|
var DEFAULT_STREAMING_FAILURE_ERROR_NAMES = [
|
|
12356
12358
|
"ProviderError",
|
|
12357
12359
|
"APIError",
|
|
@@ -12428,7 +12430,7 @@ function containsToolCallAsText(text) {
|
|
|
12428
12430
|
}
|
|
12429
12431
|
return false;
|
|
12430
12432
|
}
|
|
12431
|
-
function containsReadyToContinuePattern(text) {
|
|
12433
|
+
function containsReadyToContinuePattern(text, patterns = READY_TO_CONTINUE_PATTERNS) {
|
|
12432
12434
|
const lines = text.split(`
|
|
12433
12435
|
`);
|
|
12434
12436
|
const lastLine = lines[lines.length - 1]?.trim();
|
|
@@ -12436,7 +12438,7 @@ function containsReadyToContinuePattern(text) {
|
|
|
12436
12438
|
return false;
|
|
12437
12439
|
const lastLines = lines.slice(-3).join(`
|
|
12438
12440
|
`);
|
|
12439
|
-
return
|
|
12441
|
+
return patterns.some((pat) => pat.test(lastLines));
|
|
12440
12442
|
}
|
|
12441
12443
|
var DONE_CLAIM_PATTERNS = [
|
|
12442
12444
|
/^task\s+done[.!]*$/im,
|
|
@@ -12456,12 +12458,12 @@ var DONE_CLAIM_PATTERNS = [
|
|
|
12456
12458
|
];
|
|
12457
12459
|
var DONE_WITHOUT_WORK_PROMPT = "I need you to verify more carefully that you have actually completed all the required tasks. " + "Your response indicated you're done, but no work was detected. Please check your todo list " + "and complete any remaining work.";
|
|
12458
12460
|
var DONE_WITHOUT_DETAILS_PROMPT = "Your last response claimed the task is complete but contained no work description. This is not acceptable. " + "You MUST respond now with a full, detailed report of everything you did: " + "for each file you modified, state the full path and the exact changes; " + "list every command you ran to verify and its result; state the final outcome. " + "Do NOT reply with 'done', 'task completed', or any short acknowledgment \u2014 " + "your ONLY acceptable response right now is this detailed report. Write it now.";
|
|
12459
|
-
function containsDoneClaimPattern(text) {
|
|
12461
|
+
function containsDoneClaimPattern(text, patterns = DONE_CLAIM_PATTERNS) {
|
|
12460
12462
|
const lines = text.split(`
|
|
12461
12463
|
`);
|
|
12462
12464
|
const lastLines = lines.slice(-5).join(`
|
|
12463
12465
|
`);
|
|
12464
|
-
return
|
|
12466
|
+
return patterns.some((pat) => pat.test(lastLines));
|
|
12465
12467
|
}
|
|
12466
12468
|
function containsWorkDescription(text) {
|
|
12467
12469
|
if (/`[^`\n]*\.[a-zA-Z0-9]{1,8}[^`\n]*`/.test(text))
|
|
@@ -12659,6 +12661,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12659
12661
|
const maxRecoveryRetries = options?.maxRecoveryRetries ?? DEFAULT_MAX_RECOVERY_RETRIES;
|
|
12660
12662
|
const minActivityGapMs = options?.minActivityGapMs ?? DEFAULT_MIN_ACTIVITY_GAP_MS;
|
|
12661
12663
|
const warmupMs = options?.warmupMs ?? DEFAULT_WARMUP_MS;
|
|
12664
|
+
const discoveryDelayMs = options?.discoveryDelayMs ?? DEFAULT_DISCOVERY_DELAY_MS;
|
|
12662
12665
|
const debug = options?.debug ?? DEFAULT_DEBUG;
|
|
12663
12666
|
const streamingFailureErrorNames = options?.streamingFailureErrorNames ?? DEFAULT_STREAMING_FAILURE_ERROR_NAMES;
|
|
12664
12667
|
const streamingFailureMessagePatterns = options?.streamingFailureMessagePatterns ?? DEFAULT_STREAMING_FAILURE_MESSAGE_PATTERNS;
|
|
@@ -12675,6 +12678,30 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12675
12678
|
const contextSaturationThreshold = options?.contextSaturationThreshold ?? 0.85;
|
|
12676
12679
|
const subagentNativeCompactionEnabled = options?.subagentNativeCompactionEnabled ?? false;
|
|
12677
12680
|
const activeUserWindowMs = options?.activeUserWindowMs ?? DEFAULT_ACTIVE_USER_WINDOW_MS;
|
|
12681
|
+
const doneClaimPatterns = (() => {
|
|
12682
|
+
const raw = options?.doneClaimPatterns;
|
|
12683
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
12684
|
+
return DONE_CLAIM_PATTERNS;
|
|
12685
|
+
return raw.map((s) => {
|
|
12686
|
+
try {
|
|
12687
|
+
return new RegExp(s, "im");
|
|
12688
|
+
} catch {
|
|
12689
|
+
return null;
|
|
12690
|
+
}
|
|
12691
|
+
}).filter((r) => r !== null);
|
|
12692
|
+
})();
|
|
12693
|
+
const readyToContinuePatterns = (() => {
|
|
12694
|
+
const raw = options?.readyToContinuePatterns;
|
|
12695
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
12696
|
+
return READY_TO_CONTINUE_PATTERNS;
|
|
12697
|
+
return raw.map((s) => {
|
|
12698
|
+
try {
|
|
12699
|
+
return new RegExp(s, "i");
|
|
12700
|
+
} catch {
|
|
12701
|
+
return null;
|
|
12702
|
+
}
|
|
12703
|
+
}).filter((r) => r !== null);
|
|
12704
|
+
})();
|
|
12678
12705
|
const dbg = (...args) => {
|
|
12679
12706
|
if (debug)
|
|
12680
12707
|
console.log("[debug]", ...args);
|
|
@@ -12746,9 +12773,12 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12746
12773
|
resumeAttempts: 0,
|
|
12747
12774
|
lastRetryAt: 0,
|
|
12748
12775
|
gaveUp: false,
|
|
12776
|
+
oocLocked: false,
|
|
12777
|
+
oocLockReason: null,
|
|
12749
12778
|
orphanWatchStartAt: null,
|
|
12750
12779
|
aborting: false,
|
|
12751
12780
|
pluginAbortInFlight: false,
|
|
12781
|
+
pluginAbortAt: 0,
|
|
12752
12782
|
toolTextRecovered: false,
|
|
12753
12783
|
toolTextAttempts: 0,
|
|
12754
12784
|
continueTimestamps: [],
|
|
@@ -12917,6 +12947,14 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
12917
12947
|
}
|
|
12918
12948
|
if (w.userCancelled || w.completionSignaled)
|
|
12919
12949
|
return;
|
|
12950
|
+
if (w.gaveUp) {
|
|
12951
|
+
await log("debug", `${short(sid)} - gaveUp latched, refusing further continue prompts`);
|
|
12952
|
+
return;
|
|
12953
|
+
}
|
|
12954
|
+
if (w.oocLocked) {
|
|
12955
|
+
await log("debug", `${short(sid)} - oocLocked latched, refusing further continue prompts (reason: ${w.oocLockReason ?? "n/a"})`);
|
|
12956
|
+
return;
|
|
12957
|
+
}
|
|
12920
12958
|
if (!w.continuing)
|
|
12921
12959
|
dbg(`State transition on ${short(sid)}: continuing=false -> true`);
|
|
12922
12960
|
w.continuing = true;
|
|
@@ -13018,7 +13056,6 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13018
13056
|
} catch (err) {
|
|
13019
13057
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13020
13058
|
await log("warn", `${short(sid)} - recovery retry failed: ${errMsg}`);
|
|
13021
|
-
w.recoveryAttempts = 0;
|
|
13022
13059
|
}
|
|
13023
13060
|
w.watchdogRetryGuard = false;
|
|
13024
13061
|
} else {
|
|
@@ -13030,8 +13067,11 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13030
13067
|
dbg(`Watchdog check on ${short(sid)}: status=${w.status}, recoveryAttempts=${w.recoveryAttempts}, maxRetries=${maxRecoveryRetries}, watchdogLatencyMs=${Date.now() - w.lastRetryAt} -> ABORT_RESUME`);
|
|
13031
13068
|
const resumed = await tryAbortAndResume(sid, w);
|
|
13032
13069
|
if (!resumed && !w.aborting) {
|
|
13033
|
-
|
|
13034
|
-
|
|
13070
|
+
w.gaveUp = true;
|
|
13071
|
+
w.pendingRecovery = false;
|
|
13072
|
+
w.pendingRecoveryReason = null;
|
|
13073
|
+
await log("warn", `Recovery exhausted on ${short(sid)}: attempts=${w.recoveryAttempts}, lastError=abort+resume failed \u2014 GAVE UP (re-arms on next user message)`);
|
|
13074
|
+
dbg(`Watchdog check on ${short(sid)}: status=${w.status} -> GAVE_UP (latched)`);
|
|
13035
13075
|
if (w.pendingRecoveryAt > 0) {
|
|
13036
13076
|
dbg(`Total recovery cycle on ${short(sid)} (failed): totalCycleMs=${Date.now() - w.pendingRecoveryAt}`);
|
|
13037
13077
|
}
|
|
@@ -13660,7 +13700,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13660
13700
|
bestCandidate = candidate;
|
|
13661
13701
|
}
|
|
13662
13702
|
}
|
|
13663
|
-
if (containsReadyToContinuePattern(text)) {
|
|
13703
|
+
if (containsReadyToContinuePattern(text, readyToContinuePatterns)) {
|
|
13664
13704
|
const todos = w.todos || [];
|
|
13665
13705
|
const hasOpenTodos = todos.some(isOpenTodo);
|
|
13666
13706
|
if (!hasOpenTodos && todos.length > 0) {
|
|
@@ -13680,16 +13720,42 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13680
13720
|
await log("info", `${short(sid)} - skipping continue, todos appear completed (attempt ${w.todoCheckAttempts}/2)`);
|
|
13681
13721
|
continue;
|
|
13682
13722
|
}
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13723
|
+
if (containsDoneClaimPattern(text, doneClaimPatterns)) {
|
|
13724
|
+
const todos = w.todos || [];
|
|
13725
|
+
const hasOpenTodos = todos.some(isOpenTodo);
|
|
13726
|
+
if (hasOpenTodos) {
|
|
13727
|
+
const candidate = {
|
|
13728
|
+
prompt: doneWithoutWorkPrompt,
|
|
13729
|
+
source: "done-claim",
|
|
13730
|
+
priority: 1
|
|
13731
|
+
};
|
|
13732
|
+
if (!bestCandidate || candidate.priority < bestCandidate.priority) {
|
|
13733
|
+
bestCandidate = candidate;
|
|
13734
|
+
}
|
|
13735
|
+
} else if (!containsWorkDescription(allAssistantText)) {
|
|
13736
|
+
if (w.doneClaimNoTodosAttempts < maxRetries) {
|
|
13737
|
+
const candidate = {
|
|
13738
|
+
prompt: doneWithoutDetailsPrompt,
|
|
13739
|
+
source: "done-claim-no-todos",
|
|
13740
|
+
priority: 1
|
|
13741
|
+
};
|
|
13742
|
+
if (!bestCandidate || candidate.priority < bestCandidate.priority) {
|
|
13743
|
+
bestCandidate = candidate;
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13746
|
+
}
|
|
13747
|
+
} else {
|
|
13748
|
+
const candidate = {
|
|
13749
|
+
prompt: continuePrompt,
|
|
13750
|
+
source: "ready-to-continue",
|
|
13751
|
+
priority: 1
|
|
13752
|
+
};
|
|
13753
|
+
if (!bestCandidate || candidate.priority < bestCandidate.priority) {
|
|
13754
|
+
bestCandidate = candidate;
|
|
13755
|
+
}
|
|
13690
13756
|
}
|
|
13691
13757
|
}
|
|
13692
|
-
if (!bestCandidate && containsDoneClaimPattern(text)) {
|
|
13758
|
+
if (!bestCandidate && containsDoneClaimPattern(text, doneClaimPatterns)) {
|
|
13693
13759
|
const todos = w.todos || [];
|
|
13694
13760
|
const hasOpenTodos = todos.some(isOpenTodo);
|
|
13695
13761
|
if (hasOpenTodos) {
|
|
@@ -13780,7 +13846,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13780
13846
|
w.toolTextRecovered = true;
|
|
13781
13847
|
w.toolTextAttempts++;
|
|
13782
13848
|
}
|
|
13783
|
-
const attemptNum = isOpenTodosReminder ? w.todoNudgeAttempts : isDoneClaimNoTodos ? w.doneClaimNoTodosAttempts : w.toolTextAttempts;
|
|
13849
|
+
const attemptNum = isOpenTodosReminder ? w.todoNudgeAttempts + 1 : isDoneClaimNoTodos ? w.doneClaimNoTodosAttempts : w.toolTextAttempts;
|
|
13784
13850
|
await log("info", `${bestCandidate.source} detected on ${short(sid)}! ` + `Attempt ${attemptNum}/${maxRetries}. Sending recovery prompt...`);
|
|
13785
13851
|
const timeSinceActivity = Date.now() - w.lastActivityAt;
|
|
13786
13852
|
if (timeSinceActivity < minActivityGapMs) {
|
|
@@ -13826,9 +13892,12 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13826
13892
|
return false;
|
|
13827
13893
|
if (w.aborting)
|
|
13828
13894
|
return false;
|
|
13895
|
+
if (w.oocLocked)
|
|
13896
|
+
return false;
|
|
13829
13897
|
const idleSec = Math.round((Date.now() - (w.orphanWatchStartAt ?? w.lastActivityAt)) / 1000);
|
|
13830
13898
|
await log("info", `Abort+Resume on ${short(sid)} (${idleSec}s idle). Aborting...`);
|
|
13831
13899
|
w.pluginAbortInFlight = true;
|
|
13900
|
+
w.pluginAbortAt = Date.now();
|
|
13832
13901
|
try {
|
|
13833
13902
|
await ctx.client.session.abort({ path: { id: sid } });
|
|
13834
13903
|
await log("info", `${short(sid)} - abort OK`);
|
|
@@ -13838,6 +13907,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13838
13907
|
await log("warn", `${short(sid)} - abort failed: ${errMsg}`);
|
|
13839
13908
|
w.aborting = false;
|
|
13840
13909
|
w.pluginAbortInFlight = false;
|
|
13910
|
+
w.pluginAbortAt = 0;
|
|
13841
13911
|
return false;
|
|
13842
13912
|
}
|
|
13843
13913
|
await new Promise((resolve) => setTimeout(resolve, ABORT_CONTINUE_DELAY_MS));
|
|
@@ -13862,6 +13932,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13862
13932
|
return false;
|
|
13863
13933
|
} finally {
|
|
13864
13934
|
w.pluginAbortInFlight = false;
|
|
13935
|
+
w.pluginAbortAt = 0;
|
|
13865
13936
|
}
|
|
13866
13937
|
}
|
|
13867
13938
|
async function tryResume(sid, w, reason, prompt) {
|
|
@@ -13907,12 +13978,13 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
13907
13978
|
try {
|
|
13908
13979
|
const response = await ctx.client.session.list();
|
|
13909
13980
|
const list = extractMessages(response);
|
|
13981
|
+
const statusMap = await getSessionStatusMap();
|
|
13910
13982
|
for (const s of list) {
|
|
13911
13983
|
const sid = s.id;
|
|
13912
13984
|
if (sid && typeof sid === "string" && sid.startsWith("ses_")) {
|
|
13913
13985
|
const isNew = !sessions.has(sid);
|
|
13914
13986
|
ensureWatch(sid);
|
|
13915
|
-
const status = s.status;
|
|
13987
|
+
const status = s.status ?? statusMap[sid];
|
|
13916
13988
|
if (status) {
|
|
13917
13989
|
const w = sessions.get(sid);
|
|
13918
13990
|
w.status = status;
|
|
@@ -14155,7 +14227,7 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14155
14227
|
}, SESSION_DISCOVERY_INTERVAL_MS);
|
|
14156
14228
|
if (discoveryTimer.unref)
|
|
14157
14229
|
discoveryTimer.unref();
|
|
14158
|
-
setTimeout(discoverSessions,
|
|
14230
|
+
setTimeout(discoverSessions, discoveryDelayMs);
|
|
14159
14231
|
}
|
|
14160
14232
|
startTimer();
|
|
14161
14233
|
async function handleEvent(ev) {
|
|
@@ -14176,7 +14248,11 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14176
14248
|
if (w.pendingRecovery) {
|
|
14177
14249
|
dbg(`Pending recovery cleared on ${short(sid)}: reason=session-busy`);
|
|
14178
14250
|
}
|
|
14179
|
-
|
|
14251
|
+
if (w.continuing) {
|
|
14252
|
+
dbg(`busy on ${short(sid)} via recovery prompt - preserving resumeAttempts=${w.resumeAttempts} gaveUp=${w.gaveUp}`);
|
|
14253
|
+
} else {
|
|
14254
|
+
resetBusyFlags(w);
|
|
14255
|
+
}
|
|
14180
14256
|
prevBusyCount = busyCount();
|
|
14181
14257
|
log("debug", `${short(sid)} -> busy (${prevBusyCount})`);
|
|
14182
14258
|
} else if (statusType === "interrupted") {
|
|
@@ -14318,12 +14394,14 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14318
14394
|
if (isCelebration) {
|
|
14319
14395
|
await log("info", `${short(sid)} - \uD83C\uDF89 detected but ${open.length} open todos remain, sending nudge`);
|
|
14320
14396
|
const reminder = buildOpenTodosReminder(todos);
|
|
14321
|
-
await tryResume(sid, w, "Idle with open todos (celebration false positive)", reminder);
|
|
14322
|
-
|
|
14397
|
+
const sent = await tryResume(sid, w, "Idle with open todos (celebration false positive)", reminder);
|
|
14398
|
+
if (sent)
|
|
14399
|
+
w.todoNudgeAttempts++;
|
|
14323
14400
|
} else {
|
|
14324
14401
|
const reminder = buildOpenTodosReminder(todos);
|
|
14325
|
-
await tryResume(sid, w, "Idle with open todos", reminder);
|
|
14326
|
-
|
|
14402
|
+
const sent = await tryResume(sid, w, "Idle with open todos", reminder);
|
|
14403
|
+
if (sent)
|
|
14404
|
+
w.todoNudgeAttempts++;
|
|
14327
14405
|
}
|
|
14328
14406
|
}
|
|
14329
14407
|
}
|
|
@@ -14526,10 +14604,16 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14526
14604
|
const isMessageAborted = errorName === "MessageAbortedError";
|
|
14527
14605
|
if (isMessageAborted) {
|
|
14528
14606
|
for (const [wSid, w] of sessions) {
|
|
14529
|
-
|
|
14607
|
+
const PLUGIN_ABORT_GRACE_MS = 1000;
|
|
14608
|
+
const isOwnAbort = w.pluginAbortInFlight && Date.now() - (w.pluginAbortAt || 0) < PLUGIN_ABORT_GRACE_MS;
|
|
14609
|
+
if (!isOwnAbort) {
|
|
14530
14610
|
w.userCancelled = true;
|
|
14531
14611
|
w.status = "idle";
|
|
14532
14612
|
resetIdleFlags(w);
|
|
14613
|
+
if (w.toolTextTimer) {
|
|
14614
|
+
clearTimeout(w.toolTextTimer);
|
|
14615
|
+
w.toolTextTimer = null;
|
|
14616
|
+
}
|
|
14533
14617
|
}
|
|
14534
14618
|
}
|
|
14535
14619
|
log("info", "User abort (ESC)");
|
|
@@ -14559,6 +14643,11 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14559
14643
|
if (w) {
|
|
14560
14644
|
w.pendingTools = 0;
|
|
14561
14645
|
w.pendingCommands = 0;
|
|
14646
|
+
if (!w.oocLocked && OOC_ERROR_RE.test(errorMessage ?? "")) {
|
|
14647
|
+
w.oocLocked = true;
|
|
14648
|
+
w.oocLockReason = (errorMessage ?? "").slice(0, 200);
|
|
14649
|
+
await log("warn", `${short(sid)} - OOC error latched, halting recovery (reason: ${w.oocLockReason})`);
|
|
14650
|
+
}
|
|
14562
14651
|
}
|
|
14563
14652
|
}
|
|
14564
14653
|
break;
|
|
@@ -14571,7 +14660,11 @@ var AutoResumePlugin = async (ctx, options) => {
|
|
|
14571
14660
|
if (w.pendingRecovery) {
|
|
14572
14661
|
dbg(`Pending recovery cleared on ${short(sid)}: reason=user-command`);
|
|
14573
14662
|
}
|
|
14574
|
-
|
|
14663
|
+
if (w.continuing) {
|
|
14664
|
+
dbg(`command on ${short(sid)} via recovery prompt - preserving resumeAttempts=${w.resumeAttempts} gaveUp=${w.gaveUp}`);
|
|
14665
|
+
} else {
|
|
14666
|
+
resetBusyFlags(w);
|
|
14667
|
+
}
|
|
14575
14668
|
w.pendingCommands = Math.max(0, w.pendingCommands - 1);
|
|
14576
14669
|
w.lastActivityAt = Date.now();
|
|
14577
14670
|
}
|
|
@@ -14659,6 +14752,11 @@ ${reminder}`;
|
|
|
14659
14752
|
w.lastActivityAt = Date.now();
|
|
14660
14753
|
if (w.continuing)
|
|
14661
14754
|
return;
|
|
14755
|
+
if (w.oocLocked) {
|
|
14756
|
+
w.oocLocked = false;
|
|
14757
|
+
w.oocLockReason = null;
|
|
14758
|
+
await log("info", `${short(sid)} - genuine user message, clearing oocLocked`);
|
|
14759
|
+
}
|
|
14662
14760
|
if (w.userCancelled || w.completionSignaled) {
|
|
14663
14761
|
w.userCancelled = false;
|
|
14664
14762
|
w.completionSignaled = false;
|
package/package.json
CHANGED