autodev-cli 1.4.181 → 1.4.183
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/out/core/grokEvents.d.ts +11 -0
- package/out/core/grokEvents.js +20 -0
- package/out/core/grokEvents.js.map +1 -1
- package/out/core/settingsLoader.d.ts +9 -0
- package/out/core/settingsLoader.js +1 -0
- package/out/core/settingsLoader.js.map +1 -1
- package/out/providers/grok/tui.js +91 -11
- package/out/providers/grok/tui.js.map +1 -1
- package/out/taskLoop.d.ts +43 -0
- package/out/taskLoop.js +112 -17
- package/out/taskLoop.js.map +1 -1
- package/package.json +2 -2
package/out/taskLoop.d.ts
CHANGED
|
@@ -14,6 +14,42 @@ export type LoopState = 'idle' | 'running' | 'stopping' | 'paused';
|
|
|
14
14
|
* only a task that TRANSITIONED to blocked during this wait is skipped.
|
|
15
15
|
*/
|
|
16
16
|
export declare function taskHardFailedDuringWait(blockedBeforeWait: boolean, blockedAfterWait: boolean): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Default number of times a stranded `[~]` in-progress task (the CLI exited
|
|
19
|
+
* without completing it) is reset to pending `[ ]` and re-attempted before it
|
|
20
|
+
* is finally flagged BLOCKED / needs-attention. Overridable per-agent via
|
|
21
|
+
* `settings.maxStrandRetries`. A crashed / StopFailure'd task is NEVER marked
|
|
22
|
+
* done — correctness over efficiency: re-doing a task the agent actually
|
|
23
|
+
* finished (safe, wasteful) beats reporting a half-done task as complete
|
|
24
|
+
* (unsafe). The user explicitly prefers never-false-done.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_STRAND_RETRIES = 3;
|
|
27
|
+
/** Action decided for a single stranded `[~]` task by {@link reconcileStrandedTask}. */
|
|
28
|
+
export type StrandAction = 'reset' | 'block';
|
|
29
|
+
/**
|
|
30
|
+
* PURE, side-effect-contained reconciliation for ONE stranded `[~]` task on a
|
|
31
|
+
* non-clean CLI exit. Advances the in-memory strand counter and decides whether
|
|
32
|
+
* to reset the task to pending (`reset`, retry) or promote it to blocked /
|
|
33
|
+
* needs-attention (`block`).
|
|
34
|
+
*
|
|
35
|
+
* CRITICAL flood-safety contract: this mutates ONLY the in-memory `strandCount`
|
|
36
|
+
* map and (on `block`) the `blocked` set — it does NOT touch TODO.md. The caller
|
|
37
|
+
* MUST invoke this and let the bookkeeping settle BEFORE any TODO.md write, so a
|
|
38
|
+
* locked / unwritable TODO.md can never cause a flood: the per-task counter
|
|
39
|
+
* still climbs and the task still lands in `blocked` after MAX regardless of
|
|
40
|
+
* whether the reset/blocked-marker write throws or silently fails. Because the
|
|
41
|
+
* caller's stranded filter excludes `blocked` tasks, a given task is reconciled
|
|
42
|
+
* at most `max + 1` times total (max resets, then one block) — never per-second.
|
|
43
|
+
*
|
|
44
|
+
* @param key Task identity — `t.id ?? t.text` (mirrors _blockedTasks/_pendingRetry keying).
|
|
45
|
+
* @param strandCount Per-task strand-attempt counter map (mutated).
|
|
46
|
+
* @param blocked Blocked / needs-attention task-key set (mutated on `block`).
|
|
47
|
+
* @param max Retry ceiling (default {@link MAX_STRAND_RETRIES}).
|
|
48
|
+
*/
|
|
49
|
+
export declare function reconcileStrandedTask(key: string, strandCount: Map<string, number>, blocked: Set<string>, max?: number): {
|
|
50
|
+
action: StrandAction;
|
|
51
|
+
count: number;
|
|
52
|
+
};
|
|
17
53
|
export interface LoopCallbacks {
|
|
18
54
|
/** Send a prompt to the active AI provider. messageFile is the absolute path of the written .md file for CLI providers. */
|
|
19
55
|
sendToAi: (prompt: string, taskLabel: string, includeProfile?: boolean, messageFile?: string) => Promise<void>;
|
|
@@ -109,6 +145,13 @@ export declare class TaskLoopRunner {
|
|
|
109
145
|
* stranded-[~] cleanup auto-marking them done). Consumed when the loop
|
|
110
146
|
* re-picks the task for re-dispatch. */
|
|
111
147
|
private _pendingRetry;
|
|
148
|
+
/** Per-task STRAND counter (id or text). Incremented each time the CLI exits
|
|
149
|
+
* leaving this task `[~]` in-progress without completing it. While ≤
|
|
150
|
+
* maxStrandRetries the task is reset to pending `[ ]` and re-attempted; past
|
|
151
|
+
* the ceiling it is promoted to _blockedTasks (needs-attention) and NEVER
|
|
152
|
+
* marked done. Advances purely in-memory BEFORE any TODO.md write so a
|
|
153
|
+
* locked/unwritable TODO.md can never flood the stranded sweep. */
|
|
154
|
+
private _strandCount;
|
|
112
155
|
/** Counts completed tasks since the last auto-compact run. */
|
|
113
156
|
private _autoCompactCounter;
|
|
114
157
|
/** Counts completed tasks since the last session reset. */
|
package/out/taskLoop.js
CHANGED
|
@@ -33,8 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.taskLoopRunner = exports.TaskLoopRunner = void 0;
|
|
36
|
+
exports.taskLoopRunner = exports.TaskLoopRunner = exports.MAX_STRAND_RETRIES = void 0;
|
|
37
37
|
exports.taskHardFailedDuringWait = taskHardFailedDuringWait;
|
|
38
|
+
exports.reconcileStrandedTask = reconcileStrandedTask;
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const os = __importStar(require("os"));
|
|
@@ -86,6 +87,46 @@ const version_1 = require("./version");
|
|
|
86
87
|
function taskHardFailedDuringWait(blockedBeforeWait, blockedAfterWait) {
|
|
87
88
|
return blockedAfterWait && !blockedBeforeWait;
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Default number of times a stranded `[~]` in-progress task (the CLI exited
|
|
92
|
+
* without completing it) is reset to pending `[ ]` and re-attempted before it
|
|
93
|
+
* is finally flagged BLOCKED / needs-attention. Overridable per-agent via
|
|
94
|
+
* `settings.maxStrandRetries`. A crashed / StopFailure'd task is NEVER marked
|
|
95
|
+
* done — correctness over efficiency: re-doing a task the agent actually
|
|
96
|
+
* finished (safe, wasteful) beats reporting a half-done task as complete
|
|
97
|
+
* (unsafe). The user explicitly prefers never-false-done.
|
|
98
|
+
*/
|
|
99
|
+
exports.MAX_STRAND_RETRIES = 3;
|
|
100
|
+
/**
|
|
101
|
+
* PURE, side-effect-contained reconciliation for ONE stranded `[~]` task on a
|
|
102
|
+
* non-clean CLI exit. Advances the in-memory strand counter and decides whether
|
|
103
|
+
* to reset the task to pending (`reset`, retry) or promote it to blocked /
|
|
104
|
+
* needs-attention (`block`).
|
|
105
|
+
*
|
|
106
|
+
* CRITICAL flood-safety contract: this mutates ONLY the in-memory `strandCount`
|
|
107
|
+
* map and (on `block`) the `blocked` set — it does NOT touch TODO.md. The caller
|
|
108
|
+
* MUST invoke this and let the bookkeeping settle BEFORE any TODO.md write, so a
|
|
109
|
+
* locked / unwritable TODO.md can never cause a flood: the per-task counter
|
|
110
|
+
* still climbs and the task still lands in `blocked` after MAX regardless of
|
|
111
|
+
* whether the reset/blocked-marker write throws or silently fails. Because the
|
|
112
|
+
* caller's stranded filter excludes `blocked` tasks, a given task is reconciled
|
|
113
|
+
* at most `max + 1` times total (max resets, then one block) — never per-second.
|
|
114
|
+
*
|
|
115
|
+
* @param key Task identity — `t.id ?? t.text` (mirrors _blockedTasks/_pendingRetry keying).
|
|
116
|
+
* @param strandCount Per-task strand-attempt counter map (mutated).
|
|
117
|
+
* @param blocked Blocked / needs-attention task-key set (mutated on `block`).
|
|
118
|
+
* @param max Retry ceiling (default {@link MAX_STRAND_RETRIES}).
|
|
119
|
+
*/
|
|
120
|
+
function reconcileStrandedTask(key, strandCount, blocked, max = exports.MAX_STRAND_RETRIES) {
|
|
121
|
+
const count = (strandCount.get(key) ?? 0) + 1;
|
|
122
|
+
strandCount.set(key, count);
|
|
123
|
+
if (count > max) {
|
|
124
|
+
blocked.add(key);
|
|
125
|
+
strandCount.delete(key); // done escalating — it is blocked now; free the counter
|
|
126
|
+
return { action: 'block', count };
|
|
127
|
+
}
|
|
128
|
+
return { action: 'reset', count };
|
|
129
|
+
}
|
|
89
130
|
// ---------------------------------------------------------------------------
|
|
90
131
|
// Rate-limit + context-length errors
|
|
91
132
|
// ---------------------------------------------------------------------------
|
|
@@ -244,6 +285,13 @@ class TaskLoopRunner {
|
|
|
244
285
|
* stranded-[~] cleanup auto-marking them done). Consumed when the loop
|
|
245
286
|
* re-picks the task for re-dispatch. */
|
|
246
287
|
_pendingRetry = new Set();
|
|
288
|
+
/** Per-task STRAND counter (id or text). Incremented each time the CLI exits
|
|
289
|
+
* leaving this task `[~]` in-progress without completing it. While ≤
|
|
290
|
+
* maxStrandRetries the task is reset to pending `[ ]` and re-attempted; past
|
|
291
|
+
* the ceiling it is promoted to _blockedTasks (needs-attention) and NEVER
|
|
292
|
+
* marked done. Advances purely in-memory BEFORE any TODO.md write so a
|
|
293
|
+
* locked/unwritable TODO.md can never flood the stranded sweep. */
|
|
294
|
+
_strandCount = new Map();
|
|
247
295
|
/** Counts completed tasks since the last auto-compact run. */
|
|
248
296
|
_autoCompactCounter = 0;
|
|
249
297
|
/** Counts completed tasks since the last session reset. */
|
|
@@ -448,6 +496,7 @@ class TaskLoopRunner {
|
|
|
448
496
|
this._blockedTasks.clear();
|
|
449
497
|
this._hardFailAttempts.clear();
|
|
450
498
|
this._pendingRetry.clear();
|
|
499
|
+
this._strandCount.clear();
|
|
451
500
|
this._autoCompactCounter = 0;
|
|
452
501
|
this._resetSessionCounter = 0;
|
|
453
502
|
this._profileSentCounter = 0;
|
|
@@ -1650,27 +1699,73 @@ class TaskLoopRunner {
|
|
|
1650
1699
|
this._cb?.log(`⏳ opencode-cli exited cleanly with [~] task — waiting for external response…`);
|
|
1651
1700
|
}
|
|
1652
1701
|
else {
|
|
1653
|
-
//
|
|
1654
|
-
// [~]
|
|
1655
|
-
//
|
|
1656
|
-
//
|
|
1657
|
-
//
|
|
1658
|
-
//
|
|
1702
|
+
// TRUTHFUL stranded-[~] reconciliation. The CLI exited leaving a
|
|
1703
|
+
// task [~] in-progress without completing it (crash, StopFailure,
|
|
1704
|
+
// resume stale-read, any provider hard-exit). We must NEVER mark it
|
|
1705
|
+
// [x] done — that reports a half-done task as SUCCESS (false-green,
|
|
1706
|
+
// the exact data-integrity bug the user hit). Instead: bounded
|
|
1707
|
+
// reset+retry, then BLOCKED / needs-attention.
|
|
1708
|
+
//
|
|
1709
|
+
// Exclusions preserved: tasks already flagged blocked (_blockedTasks
|
|
1710
|
+
// — left [~] on purpose) and tasks queued for a provider-retry
|
|
1711
|
+
// re-dispatch (_pendingRetry, re-sent by the retry hook above).
|
|
1712
|
+
//
|
|
1713
|
+
// FLOOD SAFETY: reconcileStrandedTask advances the in-memory strand
|
|
1714
|
+
// counter (and promotes to _blockedTasks past MAX) BEFORE and
|
|
1715
|
+
// INDEPENDENTLY of the TODO.md write below. So even if every
|
|
1716
|
+
// resetToTodo write silently fails on a locked/unwritable TODO.md
|
|
1717
|
+
// (the Windows file-lock case that produced the ~40×/second flood),
|
|
1718
|
+
// the counter still climbs and the task still lands blocked after
|
|
1719
|
+
// MAX — and once blocked it is excluded from this sweep, so a given
|
|
1720
|
+
// task is reconciled at most MAX+1 times TOTAL, never per-second.
|
|
1721
|
+
// We also do NOT tight-`continue`: we fall through to the
|
|
1722
|
+
// _sleepOrWake(loopInterval) below, so nothing can spin faster than
|
|
1723
|
+
// the loop interval regardless of write success. ≤1 pass/iteration.
|
|
1724
|
+
const maxStrand = settings.maxStrandRetries ?? exports.MAX_STRAND_RETRIES;
|
|
1659
1725
|
const stranded = tasks.filter(t => t.status === 'in-progress'
|
|
1660
1726
|
&& !this._blockedTasks.has(t.id ?? t.text)
|
|
1661
1727
|
&& !this._pendingRetry.has(t.id ?? t.text));
|
|
1662
1728
|
for (const t of stranded) {
|
|
1663
|
-
|
|
1664
|
-
//
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1729
|
+
const key = t.id ?? t.text;
|
|
1730
|
+
// In-memory bookkeeping FIRST (write-independent, flood-proof).
|
|
1731
|
+
const { action, count } = reconcileStrandedTask(key, this._strandCount, this._blockedTasks, maxStrand);
|
|
1732
|
+
if (action === 'reset') {
|
|
1733
|
+
this._cb?.log(`↺ Reset stranded [~] task to pending — retry ${count}/${maxStrand}: ${discordLabel(t.text)}`);
|
|
1734
|
+
// Best-effort reset; a failure here cannot flood (counter already
|
|
1735
|
+
// advanced above, and we fall through to the loopInterval wait).
|
|
1736
|
+
await todoWriteManager_1.todoWriter.resetToTodo(todoPath, t).catch(() => { });
|
|
1737
|
+
}
|
|
1738
|
+
else {
|
|
1739
|
+
// count > MAX → BLOCKED / needs-attention. NEVER done, NEVER
|
|
1740
|
+
// reset again. Surface it the SAME way a provider hard-failure
|
|
1741
|
+
// does (task_fail → FAILED + StopFailure hook), then keep polling
|
|
1742
|
+
// for OTHER work (it's now excluded from the stranded sweep).
|
|
1743
|
+
this._failedCount++;
|
|
1744
|
+
this._cb?.log(`⚠ Task stranded after ${maxStrand} attempts — marking blocked (needs attention), NOT done: ${discordLabel(t.text)}`);
|
|
1745
|
+
const errText = `task stranded (CLI exited without completing it) after ${maxStrand} reset attempts — needs attention`;
|
|
1746
|
+
this._notifyWebhook('task_fail', {
|
|
1747
|
+
iteration: this._iterations,
|
|
1748
|
+
task: { text: t.text, id: t.id },
|
|
1749
|
+
duration: 0,
|
|
1750
|
+
error: errText,
|
|
1751
|
+
reason: 'stranded',
|
|
1752
|
+
workDir: this._workspaceRoot,
|
|
1753
|
+
gitRepo: this._gitRepo,
|
|
1754
|
+
gitBranch: this._gitBranch,
|
|
1755
|
+
});
|
|
1756
|
+
this._notifyDiscord(`⚠ **Task stranded — blocked (not done):**\n${discordLabel(t.text)}`);
|
|
1757
|
+
if (this._workspaceRoot) {
|
|
1758
|
+
(0, liveNarration_1.appendHookEventLine)(this._workspaceRoot, {
|
|
1759
|
+
hook_event_name: 'StopFailure', event_type: 'stop_failure',
|
|
1760
|
+
provider: provider ?? '', cwd: this._workspaceRoot, error: 'stranded',
|
|
1761
|
+
title: 'Task stranded', message: errText, tool_name: '',
|
|
1762
|
+
timestamp: new Date().toISOString(),
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1673
1766
|
}
|
|
1767
|
+
// NOTE: NO tight-`continue` — fall through to the loopInterval sleep
|
|
1768
|
+
// below (do NOT re-pick instantly). This is the flood guard.
|
|
1674
1769
|
}
|
|
1675
1770
|
}
|
|
1676
1771
|
this._cb?.log(`No pending tasks — waiting ${settings.loopInterval}s…`);
|