autodev-cli 1.4.32 → 1.4.34

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/rateLimit.js CHANGED
@@ -62,7 +62,12 @@ const AUTH_PHRASES = [
62
62
  /credit balance is too low/i, // Anthropic out-of-credit banner
63
63
  /please run\s+\/login/i, // explicit re-login instruction
64
64
  /oauth token[^\n]{0,60}(expired|revoked|invalid)/i, // token lifecycle failure
65
- /authentication_error/i, // Anthropic API error type
65
+ // "authentication_error" ONLY inside the provider's API-error framing or a JSON
66
+ // error body. A bare occurrence appears in ordinary code/prose the agent writes
67
+ // ("handle the authentication_error case", a pasted Stripe/Anthropic 401
68
+ // snippet), which previously bricked the loop into a false reauth pause.
69
+ /api error[^\n]{0,160}authentication_error/i, // "API Error: 401 ... authentication_error"
70
+ /"type"\s*:\s*"authentication_error"/i, // {"type":"authentication_error"}
66
71
  // "not logged in / authenticated" only when paired with a re-auth instruction
67
72
  // (a bare mention appears in ordinary code the agent writes).
68
73
  /not (logged in|authenticated)[^\n]{0,40}(log ?in|authenticate|run|sign in|\/login)/i,
@@ -1 +1 @@
1
- {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"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"}
package/out/taskLoop.d.ts CHANGED
@@ -52,6 +52,13 @@ export declare class TaskLoopRunner {
52
52
  /** Resolves the idle no-task sleep early when a poller appends a new task. */
53
53
  private _idleSleepWake;
54
54
  private _resumeAt;
55
+ /** When the loop is paused for reauth/rate-limit, the event+payload to re-emit
56
+ * on a WS reconnect INSTEAD of agent_online/task_start. Reconnects are routine
57
+ * (heartbeat, NAT blips, server restart); without this a reconnect during a
58
+ * pause flips the server status back to active/working and erases the
59
+ * operator's 'needs reauth' / 'rate limited' badge, stranding the agent.
60
+ * Cleared on retry()/resume. */
61
+ private _pauseReason;
55
62
  /** When fallback is active: the saved main provider and when to switch back. */
56
63
  private _mainProviderBeforeFallback;
57
64
  private _mainProviderResumeAt;
package/out/taskLoop.js CHANGED
@@ -174,6 +174,13 @@ class TaskLoopRunner {
174
174
  /** Resolves the idle no-task sleep early when a poller appends a new task. */
175
175
  _idleSleepWake = null;
176
176
  _resumeAt;
177
+ /** When the loop is paused for reauth/rate-limit, the event+payload to re-emit
178
+ * on a WS reconnect INSTEAD of agent_online/task_start. Reconnects are routine
179
+ * (heartbeat, NAT blips, server restart); without this a reconnect during a
180
+ * pause flips the server status back to active/working and erases the
181
+ * operator's 'needs reauth' / 'rate limited' badge, stranding the agent.
182
+ * Cleared on retry()/resume. */
183
+ _pauseReason = null;
177
184
  /** When fallback is active: the saved main provider and when to switch back. */
178
185
  _mainProviderBeforeFallback = null;
179
186
  _mainProviderResumeAt;
@@ -317,6 +324,7 @@ class TaskLoopRunner {
317
324
  }
318
325
  this._retryScheduler.clear();
319
326
  this._resumeAt = undefined;
327
+ this._pauseReason = null;
320
328
  this._mainProviderBeforeFallback = null;
321
329
  this._mainProviderResumeAt = undefined;
322
330
  this._setState('running');
@@ -403,6 +411,17 @@ class TaskLoopRunner {
403
411
  });
404
412
  return;
405
413
  }
414
+ // If the loop is paused (reauth / rate-limit), a routine reconnect must
415
+ // NOT re-emit agent_online + task_start — that flips the server status
416
+ // back to active/working and erases the reauth/rate-limit alert, so a
417
+ // stranded agent looks like it is busy working. Re-assert the pause
418
+ // reason instead and never send task_start while paused.
419
+ if (this._state === 'paused') {
420
+ if (this._pauseReason) {
421
+ this._notifyWebhook(this._pauseReason.event, this._pauseReason.payload);
422
+ }
423
+ return;
424
+ }
406
425
  this._notifyWebhook('agent_online', {
407
426
  hostname: this._hostname,
408
427
  workDir: this._workspaceRoot ?? '',
@@ -452,7 +471,7 @@ class TaskLoopRunner {
452
471
  this._webhookPoller.setRdpEnabled(settings.rdpEnabled ?? false);
453
472
  // Wake the idle no-task sleep instantly when a WS-pushed task arrives.
454
473
  this._webhookPoller.setOnTaskAppend(() => this._wakeIdleSleep());
455
- this._webhookPoller.setOnSteer((text) => this._handleSteer(text));
474
+ this._webhookPoller.setOnSteer((text, onDelivered) => this._handleSteer(text, onDelivered));
456
475
  this._webhookPoller.setOnCommand((cmd) => this._handleCommand(cmd));
457
476
  this._webhookPoller.setOnMcpUpdate((entries) => this._handleMcpUpdate(entries));
458
477
  this._webhookPoller.setOnExportRequest((agentId) => void this._handleExportRequest(agentId));
@@ -592,6 +611,7 @@ class TaskLoopRunner {
592
611
  this._setState('stopping');
593
612
  this._retryScheduler.clear();
594
613
  this._resumeAt = undefined;
614
+ this._pauseReason = null;
595
615
  this._mainProviderBeforeFallback = null;
596
616
  this._mainProviderResumeAt = undefined;
597
617
  // Unblock _pause() if we're currently suspended
@@ -791,6 +811,17 @@ class TaskLoopRunner {
791
811
  void this.restart();
792
812
  return;
793
813
  }
814
+ if (c === '/retry' || c === '/resume') {
815
+ if (this._state === 'paused') {
816
+ this._cb?.log(`▶️ ${c} received — resuming paused loop…`);
817
+ this._notifyDiscord(`▶️ Resuming loop (\`${c}\` received)`);
818
+ this.retry();
819
+ }
820
+ else {
821
+ this._cb?.log(`▶️ ${c} received but loop is not paused (state=${this._state}) — ignoring.`);
822
+ }
823
+ return;
824
+ }
794
825
  if (c === '/clear') {
795
826
  this._cb?.log('🗑 /clear received — clearing Claude session…');
796
827
  this._notifyDiscord('🗑 `/clear` received — clearing Claude session');
@@ -840,9 +871,12 @@ class TaskLoopRunner {
840
871
  * and wake the idle sleep so it runs at the very next turn boundary rather
841
872
  * than waiting for a poll interval. This is the documented fallback.
842
873
  */
843
- _handleSteer(text) {
874
+ _handleSteer(text, onDelivered) {
844
875
  const clean = text.trim();
876
+ // Malformed/empty steer: nothing to deliver — treat as terminally handled so
877
+ // the server doesn't keep replaying it.
845
878
  if (!clean) {
879
+ onDelivered?.();
846
880
  return;
847
881
  }
848
882
  const root = this._workspaceRoot;
@@ -852,6 +886,7 @@ class TaskLoopRunner {
852
886
  if (root && provider === 'claude-tui' && (0, claudeTuiProvider_1.isClaudeTuiBusy)(root)) {
853
887
  if ((0, claudeTuiProvider_1.steerClaudeTui)(root, clean, msg => this._cb?.log(msg))) {
854
888
  this._cb?.log(`⚡ Steered mid-turn: "${clean.slice(0, 80)}"`);
889
+ onDelivered?.(); // durably injected into the live turn
855
890
  return;
856
891
  }
857
892
  }
@@ -859,12 +894,21 @@ class TaskLoopRunner {
859
894
  // idle sleep so an idle loop starts immediately instead of waiting a poll.
860
895
  const todoPath = this._settings?.todoPath || (root ? path.join(root, 'TODO.md') : '');
861
896
  if (!todoPath) {
897
+ // No TODO path is a terminal config error, not a transient failure —
898
+ // redelivery cannot help, so mark it handled to avoid an infinite replay.
862
899
  this._cb?.log(`⚡ Steer dropped — no TODO path (text: "${clean.slice(0, 80)}")`);
900
+ onDelivered?.();
863
901
  return;
864
902
  }
865
903
  this._cb?.log(`⚡ Steer queued for next turn boundary: "${clean.slice(0, 80)}"`);
866
904
  todoWriteManager_1.todoWriter.append(todoPath, clean, (0, todo_1.shortId)())
867
- .then(() => this._wakeIdleSleep())
905
+ .then(() => {
906
+ // At-least-once: only mark the delivery seen AFTER the durable append
907
+ // succeeds, mirroring the user_message path. Left unmarked on failure so
908
+ // the server's reconnect replay re-delivers instead of losing the steer.
909
+ onDelivered?.();
910
+ this._wakeIdleSleep();
911
+ })
868
912
  .catch(err => this._cb?.log(`⚡ Steer append failed: ${err instanceof Error ? err.message : String(err)}`));
869
913
  }
870
914
  // -------------------------------------------------------------------------
@@ -891,6 +935,13 @@ class TaskLoopRunner {
891
935
  const verify = String(env.MCP_EMAIL_SERVER_IMAP_VERIFY_SSL ?? 'true').toLowerCase() !== 'false';
892
936
  const allowed = (env.AUTODEV_EMAIL_ALLOWED_SENDERS || '')
893
937
  .split(',').map(s => s.trim()).filter(Boolean);
938
+ // Fail closed: an empty allowlist would otherwise ingest autonomous,
939
+ // full-tool-access instructions from ANY sender who guesses the mailbox.
940
+ // Require an explicit allowlist before enabling inbound email tasks.
941
+ if (allowed.length === 0) {
942
+ this._cb?.log('🔒 Inbound email tasks disabled — set AUTODEV_EMAIL_ALLOWED_SENDERS to an explicit sender allowlist to enable them.');
943
+ return null;
944
+ }
894
945
  return new emailPoller_1.EmailTaskPoller({ host, port, secure, user, pass, allowedSenders: allowed, rejectUnauthorized: verify });
895
946
  }
896
947
  /**
@@ -1732,6 +1783,43 @@ class TaskLoopRunner {
1732
1783
  });
1733
1784
  // Never markDone — restore the task so it is retried after re-auth.
1734
1785
  await todoWriteManager_1.todoWriter.resetToTodo(todoPath, task).catch(() => { });
1786
+ // Evict the cached persistent provider process. claude-tui / opencode-sdk
1787
+ // / copilot-sdk cache one live process per root that is still holding the
1788
+ // dead token, so re-login on disk would otherwise never be picked up and
1789
+ // Retry would spawn straight back into the same auth failure. Closing it
1790
+ // forces a fresh process (reading the new credentials) on resume.
1791
+ const evictRoot = this._workspaceRoot;
1792
+ if (evictRoot) {
1793
+ try {
1794
+ const evictLog = (m) => this._cb?.log(m);
1795
+ if (currentProvider === 'claude-tui') {
1796
+ (0, claudeTuiProvider_1.closeClaudeTuiClient)(evictRoot, evictLog);
1797
+ }
1798
+ else if (currentProvider === 'opencode-sdk') {
1799
+ (0, opencodeSdkProvider_1.closeOpencodeSdkClient)(evictRoot, evictLog);
1800
+ }
1801
+ else if (currentProvider === 'copilot-sdk') {
1802
+ (0, copilotSdkProvider_1.closeCopilotSdkSession)(evictRoot, evictLog);
1803
+ }
1804
+ }
1805
+ catch (evictErr) {
1806
+ this._cb?.log(`⚠️ Failed to evict ${currentProvider} client before reauth pause: ${evictErr instanceof Error ? evictErr.message : String(evictErr)}`);
1807
+ }
1808
+ }
1809
+ // Remember the pause reason so a WS reconnect during the pause re-emits
1810
+ // reauth_required instead of clobbering it with agent_online/task_start.
1811
+ this._pauseReason = {
1812
+ event: 'reauth_required',
1813
+ payload: {
1814
+ iteration: this._iterations,
1815
+ task: { text: task.text },
1816
+ message: rawMsg,
1817
+ provider: currentProvider,
1818
+ workDir: this._workspaceRoot,
1819
+ gitRepo: this._gitRepo,
1820
+ gitBranch: this._gitBranch,
1821
+ },
1822
+ };
1735
1823
  // Pause indefinitely (no auto-resume) — operator must re-auth + Retry.
1736
1824
  await this._pauseLoop();
1737
1825
  if (this._state !== 'running') {
@@ -1797,6 +1885,21 @@ class TaskLoopRunner {
1797
1885
  });
1798
1886
  // Reset task so it gets picked up again after resume
1799
1887
  await todoWriteManager_1.todoWriter.resetToTodo(todoPath, task).catch(() => { });
1888
+ // Remember the pause reason so a WS reconnect during the pause re-emits
1889
+ // rate_limit instead of clobbering it with agent_online/task_start.
1890
+ this._pauseReason = {
1891
+ event: 'rate_limit',
1892
+ payload: {
1893
+ iteration: this._iterations,
1894
+ task: { text: task.text },
1895
+ message: rawMsg,
1896
+ resumeAt: resumeAt.toISOString(),
1897
+ provider: currentProvider,
1898
+ workDir: this._workspaceRoot,
1899
+ gitRepo: this._gitRepo,
1900
+ gitBranch: this._gitBranch,
1901
+ },
1902
+ };
1800
1903
  // Block here until resumed (timer or user clicks Retry Now)
1801
1904
  this._resumeAt = resumeAt;
1802
1905
  await this._pauseLoop(resumeMs);
@@ -1946,6 +2049,21 @@ class TaskLoopRunner {
1946
2049
  gitBranch: this._gitBranch,
1947
2050
  });
1948
2051
  await todoWriteManager_1.todoWriter.resetToTodo(todoPath, task).catch(() => { });
2052
+ // Remember the pause reason so a WS reconnect during the pause re-emits
2053
+ // it instead of clobbering it with agent_online/task_start.
2054
+ this._pauseReason = {
2055
+ event: 'rate_limit',
2056
+ payload: {
2057
+ iteration: this._iterations,
2058
+ task: { text: task.text },
2059
+ message: rawMsg,
2060
+ resumeAt: new Date(Date.now() + 60 * 60_000).toISOString(),
2061
+ provider,
2062
+ workDir: this._workspaceRoot,
2063
+ gitRepo: this._gitRepo,
2064
+ gitBranch: this._gitBranch,
2065
+ },
2066
+ };
1949
2067
  // No auto-resume time — user must click Retry manually
1950
2068
  this._resumeAt = undefined;
1951
2069
  await this._pauseLoop(); // pause indefinitely
@@ -2204,17 +2322,14 @@ class TaskLoopRunner {
2204
2322
  else {
2205
2323
  lastStdoutLen = content.length; // keep cursor up to date
2206
2324
  }
2207
- // Auth failure detection highest priority. A logged-out / out-of-credit
2208
- // CLI exits fast with no rate-limit or context signal, so without this it
2209
- // was walked remind->give_up and the whole backlog was falsely marked [x].
2210
- {
2211
- const authErr = rateLimit_1.AuthDetector.detect(content);
2212
- if (authErr) {
2213
- cleanup();
2214
- reject(authErr);
2215
- return;
2216
- }
2217
- }
2325
+ // Auth-failure detection is intentionally NOT run here mid-stream. For
2326
+ // claude-tui (and other teeing providers) this growing capture is the
2327
+ // model's own assistant transcript, so an ordinary task that merely
2328
+ // mentions auth/login ("handle the authentication_error case") would
2329
+ // trip AuthDetector and brick the running turn into a false reauth
2330
+ // pause. Auth is detected only on the process-EXIT buffer (see onCliExit
2331
+ // below), which is the provider's real failure surface — a logged-out /
2332
+ // out-of-credit CLI exits fast and is caught there.
2218
2333
  // Rate limit detection — run for every stdout-teeing provider. The
2219
2334
  // RateLimitDetector phrases are scoped to error/banner forms, so this
2220
2335
  // is safe to apply provider-agnostically (grok/copilot were previously
@@ -2295,12 +2410,33 @@ class TaskLoopRunner {
2295
2410
  // every stdout-teeing provider so a throttled grok/copilot exit pauses
2296
2411
  // instead of being force-marked done.
2297
2412
  const exitStdout = readStdoutFile();
2413
+ // Read the process exit code. Auth-failure detection must be gated on a
2414
+ // NON-ZERO exit: for claude-tui the stdout capture IS the model's own
2415
+ // assistant transcript, so a *successful* task (exit 0) whose output
2416
+ // legitimately contains {"type":"authentication_error"} — e.g. code
2417
+ // working on Anthropic API handling, or a pasted 401 body — would trip
2418
+ // AuthDetector and pause the loop indefinitely over benign output. Real
2419
+ // auth/credit failures exit non-zero (the provider appends `[Error: …]`),
2420
+ // so gating on the exit code loses nothing. Rate-limit detection stays
2421
+ // ungated — its phrases are error-scoped and a throttle can surface on a
2422
+ // zero exit for some providers.
2423
+ const exitCodeRaw = this._workspaceRoot
2424
+ ? (() => { try {
2425
+ return fs.readFileSync((0, sessionState_1.exitFilePath)(this._workspaceRoot, activeProvider), 'utf8').trim();
2426
+ }
2427
+ catch {
2428
+ return '';
2429
+ } })()
2430
+ : '';
2431
+ const exitedNonZero = exitCodeRaw !== '' && exitCodeRaw !== '0';
2298
2432
  if (teesStdout) {
2299
- const authFromStdout = rateLimit_1.AuthDetector.detect(exitStdout);
2300
- if (authFromStdout) {
2301
- cleanup();
2302
- reject(authFromStdout);
2303
- return;
2433
+ if (exitedNonZero) {
2434
+ const authFromStdout = rateLimit_1.AuthDetector.detect(exitStdout);
2435
+ if (authFromStdout) {
2436
+ cleanup();
2437
+ reject(authFromStdout);
2438
+ return;
2439
+ }
2304
2440
  }
2305
2441
  const rlFromStdout = rateLimit_1.RateLimitDetector.detect(exitStdout);
2306
2442
  if (rlFromStdout) {