autodev-cli 1.4.31 → 1.4.33

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
@@ -5,7 +5,7 @@
5
5
  // (RateLimitDetector.PHRASES) and every call site picks it up.
6
6
  // ---------------------------------------------------------------------------
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.RateLimitDetector = exports.RateLimitError = void 0;
8
+ exports.RateLimitDetector = exports.AuthDetector = exports.AuthError = exports.RateLimitError = void 0;
9
9
  class RateLimitError extends Error {
10
10
  rawMessage;
11
11
  resetAt;
@@ -36,6 +36,59 @@ const PHRASES = [
36
36
  /api error[^\n]{0,120}rate limit/i, // "API Error: 429 ... rate limit(ed)"
37
37
  /·\s*rate limited/i, // "... · Rate limited" suffix banner
38
38
  ];
39
+ /**
40
+ * Raised when the provider CLI reports it is logged out / unauthenticated /
41
+ * out of credit — a state the loop must NOT treat as task failure. Unlike a
42
+ * rate limit there is no reset time: the loop pauses indefinitely and asks the
43
+ * operator to re-authenticate.
44
+ */
45
+ class AuthError extends Error {
46
+ rawMessage;
47
+ constructor(rawMessage) {
48
+ super(rawMessage);
49
+ this.rawMessage = rawMessage;
50
+ this.name = 'AuthError';
51
+ }
52
+ }
53
+ exports.AuthError = AuthError;
54
+ /**
55
+ * Phrases that mean "the CLI is not authenticated / out of credit". These are
56
+ * high-signal provider error strings (invalid key, expired/revoked token,
57
+ * /login prompt, low balance) that never appear in ordinary assistant prose,
58
+ * so they are safe to match case-insensitively across the captured output.
59
+ */
60
+ const AUTH_PHRASES = [
61
+ /invalid api key/i, // Claude: "Invalid API key · Please run /login"
62
+ /credit balance is too low/i, // Anthropic out-of-credit banner
63
+ /please run\s+\/login/i, // explicit re-login instruction
64
+ /oauth token[^\n]{0,60}(expired|revoked|invalid)/i, // token lifecycle failure
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"}
71
+ // "not logged in / authenticated" only when paired with a re-auth instruction
72
+ // (a bare mention appears in ordinary code the agent writes).
73
+ /not (logged in|authenticated)[^\n]{0,40}(log ?in|authenticate|run|sign in|\/login)/i,
74
+ ];
75
+ class AuthDetector {
76
+ /** True when text contains any known auth-failure phrase. */
77
+ static matches(text) {
78
+ if (!text) {
79
+ return false;
80
+ }
81
+ return AUTH_PHRASES.some(p => p.test(text));
82
+ }
83
+ /** Build an AuthError from text, or null when text is not an auth failure. */
84
+ static detect(text) {
85
+ if (!AuthDetector.matches(text)) {
86
+ return null;
87
+ }
88
+ return new AuthError((text ?? '').trim() || 'Authentication required');
89
+ }
90
+ }
91
+ exports.AuthDetector = AuthDetector;
39
92
  class RateLimitDetector {
40
93
  /** True when text contains any known rate-limit phrase. */
41
94
  static matches(text) {
@@ -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.js CHANGED
@@ -446,6 +446,10 @@ class TaskLoopRunner {
446
446
  if (this._webhookPoller) {
447
447
  this._webhookPoller.setGitEnabled(settings.gitEnabled ?? false);
448
448
  this._webhookPoller.setFileBrowserEnabled(settings.enableFileBrowser ?? false);
449
+ // VNC / RDP session bridges are honored only when explicitly enabled —
450
+ // an ungated frame would let a remote party open outbound bridges.
451
+ this._webhookPoller.setVncEnabled(settings.vncEnabled ?? false);
452
+ this._webhookPoller.setRdpEnabled(settings.rdpEnabled ?? false);
449
453
  // Wake the idle no-task sleep instantly when a WS-pushed task arrives.
450
454
  this._webhookPoller.setOnTaskAppend(() => this._wakeIdleSleep());
451
455
  this._webhookPoller.setOnSteer((text) => this._handleSteer(text));
@@ -637,11 +641,25 @@ class TaskLoopRunner {
637
641
  const root = this._workspaceRoot;
638
642
  if (!root)
639
643
  return;
640
- this._cb?.log('🔧 mcp_update received writing .mcp.json and syncing providers…');
644
+ // Opt-in gate: writing remote-supplied MCP config spawns stdio child
645
+ // processes on restart (code-execution surface). Ignore unless explicitly
646
+ // enabled, mirroring enableFileBrowser / gitEnabled.
647
+ if (!this._settings?.mcpUpdateEnabled) {
648
+ this._cb?.log('🔒 mcp_update ignored — mcpUpdateEnabled is off (set it in .autodev/settings.json to allow)');
649
+ return;
650
+ }
651
+ this._cb?.log('🔧 mcp_update received — validating and writing .mcp.json…');
652
+ // Reject entries that would execute an arbitrary shell / path command.
653
+ const { safe, rejected } = (0, projectMcp_1.sanitizeRemoteMcpEntries)(entries);
654
+ if (rejected.length) {
655
+ this._cb?.log(`⚠️ mcp_update dropped ${rejected.length} unsafe entr${rejected.length === 1 ? 'y' : 'ies'}: ${rejected.join(', ')}`);
656
+ }
657
+ if (Object.keys(safe).length === 0) {
658
+ this._cb?.log('⚠️ mcp_update had no safe entries — not writing config or restarting.');
659
+ return;
660
+ }
641
661
  try {
642
- // Cast to the shape saveProjectUserMcp expects
643
- const typed = entries;
644
- (0, projectMcp_1.saveProjectUserMcp)(root, typed);
662
+ (0, projectMcp_1.saveProjectUserMcp)(root, safe);
645
663
  configManager_1.ConfigManager.syncProjectMcpServers(root, (m) => this._cb?.log(m));
646
664
  void configManager_1.ConfigManager.reportProjectMcp(root, (m) => this._cb?.log(m));
647
665
  this._cb?.log('✅ MCP config synced to .mcp.json, opencode.json, .vscode/mcp.json — restarting loop…');
@@ -687,6 +705,13 @@ class TaskLoopRunner {
687
705
  this._cb?.log('⚠️ restore_request ignored — serverApiKey not configured');
688
706
  return;
689
707
  }
708
+ // The download carries the agent's Bearer credential and overwrites the
709
+ // workspace, so the URL must point at the configured server origin — never
710
+ // an attacker-chosen host from the WS frame.
711
+ if (!(0, agentBackup_1.isTrustedDownloadUrl)(downloadUrl, settings.serverBaseUrl)) {
712
+ this._cb?.log(`🔒 restore_request refused — downloadUrl origin does not match the configured server: ${downloadUrl}`);
713
+ return;
714
+ }
690
715
  this._cb?.log(`🔄 restore_request received — downloading backup from ${downloadUrl}…`);
691
716
  const tmpPath = path.join(os.tmpdir(), `agent-restore-${agentId}-${Date.now()}.zip`);
692
717
  try {
@@ -766,6 +791,17 @@ class TaskLoopRunner {
766
791
  void this.restart();
767
792
  return;
768
793
  }
794
+ if (c === '/retry' || c === '/resume') {
795
+ if (this._state === 'paused') {
796
+ this._cb?.log(`▶️ ${c} received — resuming paused loop…`);
797
+ this._notifyDiscord(`▶️ Resuming loop (\`${c}\` received)`);
798
+ this.retry();
799
+ }
800
+ else {
801
+ this._cb?.log(`▶️ ${c} received but loop is not paused (state=${this._state}) — ignoring.`);
802
+ }
803
+ return;
804
+ }
769
805
  if (c === '/clear') {
770
806
  this._cb?.log('🗑 /clear received — clearing Claude session…');
771
807
  this._notifyDiscord('🗑 `/clear` received — clearing Claude session');
@@ -866,6 +902,13 @@ class TaskLoopRunner {
866
902
  const verify = String(env.MCP_EMAIL_SERVER_IMAP_VERIFY_SSL ?? 'true').toLowerCase() !== 'false';
867
903
  const allowed = (env.AUTODEV_EMAIL_ALLOWED_SENDERS || '')
868
904
  .split(',').map(s => s.trim()).filter(Boolean);
905
+ // Fail closed: an empty allowlist would otherwise ingest autonomous,
906
+ // full-tool-access instructions from ANY sender who guesses the mailbox.
907
+ // Require an explicit allowlist before enabling inbound email tasks.
908
+ if (allowed.length === 0) {
909
+ this._cb?.log('🔒 Inbound email tasks disabled — set AUTODEV_EMAIL_ALLOWED_SENDERS to an explicit sender allowlist to enable them.');
910
+ return null;
911
+ }
869
912
  return new emailPoller_1.EmailTaskPoller({ host, port, secure, user, pass, allowedSenders: allowed, rejectUnauthorized: verify });
870
913
  }
871
914
  /**
@@ -1687,6 +1730,56 @@ class TaskLoopRunner {
1687
1730
  }
1688
1731
  }
1689
1732
  catch (err) {
1733
+ // --- Auth required: pause INDEFINITELY, never mark the task done ----
1734
+ // A logged-out / out-of-credit CLI is not a task failure. Put the task
1735
+ // back to [ ], flip the office to a distinct 'needs reauth' state, and
1736
+ // block until the operator re-authenticates and clicks Retry.
1737
+ if (err instanceof rateLimit_1.AuthError) {
1738
+ const rawMsg = err.rawMessage.slice(0, 300);
1739
+ const currentProvider = this._cb?.getActiveProvider() ?? 'unknown';
1740
+ this._cb?.log(`🔑 Authentication required (${currentProvider}) — pausing loop until re-auth. ${rawMsg}`);
1741
+ this._notifyDiscord(`🔑 **Authentication required** (${currentProvider}) — loop paused until you re-authenticate.\n\`\`\`\n${rawMsg}\n\`\`\``);
1742
+ this._notifyWebhook('reauth_required', {
1743
+ iteration: this._iterations,
1744
+ task: { text: task.text },
1745
+ message: rawMsg,
1746
+ provider: currentProvider,
1747
+ workDir: this._workspaceRoot,
1748
+ gitRepo: this._gitRepo,
1749
+ gitBranch: this._gitBranch,
1750
+ });
1751
+ // Never markDone — restore the task so it is retried after re-auth.
1752
+ await todoWriteManager_1.todoWriter.resetToTodo(todoPath, task).catch(() => { });
1753
+ // Evict the cached persistent provider process. claude-tui / opencode-sdk
1754
+ // / copilot-sdk cache one live process per root that is still holding the
1755
+ // dead token, so re-login on disk would otherwise never be picked up and
1756
+ // Retry would spawn straight back into the same auth failure. Closing it
1757
+ // forces a fresh process (reading the new credentials) on resume.
1758
+ const evictRoot = this._workspaceRoot;
1759
+ if (evictRoot) {
1760
+ try {
1761
+ const evictLog = (m) => this._cb?.log(m);
1762
+ if (currentProvider === 'claude-tui') {
1763
+ (0, claudeTuiProvider_1.closeClaudeTuiClient)(evictRoot, evictLog);
1764
+ }
1765
+ else if (currentProvider === 'opencode-sdk') {
1766
+ (0, opencodeSdkProvider_1.closeOpencodeSdkClient)(evictRoot, evictLog);
1767
+ }
1768
+ else if (currentProvider === 'copilot-sdk') {
1769
+ (0, copilotSdkProvider_1.closeCopilotSdkSession)(evictRoot, evictLog);
1770
+ }
1771
+ }
1772
+ catch (evictErr) {
1773
+ this._cb?.log(`⚠️ Failed to evict ${currentProvider} client before reauth pause: ${evictErr instanceof Error ? evictErr.message : String(evictErr)}`);
1774
+ }
1775
+ }
1776
+ // Pause indefinitely (no auto-resume) — operator must re-auth + Retry.
1777
+ await this._pauseLoop();
1778
+ if (this._state !== 'running') {
1779
+ break;
1780
+ }
1781
+ continue;
1782
+ }
1690
1783
  // --- Rate limit: pause loop, schedule auto-resume -----------------
1691
1784
  if (err instanceof rateLimit_1.RateLimitError) {
1692
1785
  // Two flavours:
@@ -1979,6 +2072,13 @@ class TaskLoopRunner {
1979
2072
  const iscopilotSdkProvider = this._cb?.getActiveProvider() === 'copilot-sdk';
1980
2073
  const isOpenCodeCli = this._cb?.getActiveProvider() === 'opencode-cli';
1981
2074
  const isOpencodeSdk = this._cb?.getActiveProvider() === 'opencode-sdk';
2075
+ // grok-cli / grok-tui / copilot-cli all tee stdout too — without these flags
2076
+ // the rate-limit / context-length / thrash detectors never ran for them, so
2077
+ // a throttled or context-blown turn was silently walked to give_up and the
2078
+ // task was falsely marked [x].
2079
+ const isGrokCli = this._cb?.getActiveProvider() === 'grok-cli';
2080
+ const isGrokTui = this._cb?.getActiveProvider() === 'grok-tui';
2081
+ const isCopilotCli = this._cb?.getActiveProvider() === 'copilot-cli';
1982
2082
  return new Promise((resolve, reject) => {
1983
2083
  if (this._state !== 'running') {
1984
2084
  resolve();
@@ -2112,10 +2212,15 @@ class TaskLoopRunner {
2112
2212
  // Track how many characters of the stdout file we've already forwarded
2113
2213
  let lastStdoutLen = 0;
2114
2214
  // Check stdout file: forward any new content to Discord/webhook, detect rate limit / context errors
2215
+ // Providers that write a stdout capture file we can scan for failure
2216
+ // banners (rate-limit / context-length / thrash). Every CLI/TUI provider
2217
+ // tees stdout; the two Claude ones and OpenCode also stream/parse extra.
2218
+ const teesStdout = isClaudeCli || isClaudeTui || iscopilotSdkProvider || isOpenCodeCli
2219
+ || isGrokCli || isGrokTui || isCopilotCli;
2115
2220
  const checkStdout = () => {
2116
- if (!isClaudeCli && !isClaudeTui && !iscopilotSdkProvider && !isOpenCodeCli) {
2221
+ if (!teesStdout) {
2117
2222
  return;
2118
- } // only CLI providers tee stdout
2223
+ }
2119
2224
  const content = readStdoutFile();
2120
2225
  // Forward new output lines to Discord / webhook.
2121
2226
  // claude-cli: stream partial chunks so the operator can see live progress.
@@ -2140,8 +2245,19 @@ class TaskLoopRunner {
2140
2245
  else {
2141
2246
  lastStdoutLen = content.length; // keep cursor up to date
2142
2247
  }
2143
- // Rate limit detection (Claude CLI + TUI)
2144
- if (isClaudeCli || isClaudeTui) {
2248
+ // Auth-failure detection is intentionally NOT run here mid-stream. For
2249
+ // claude-tui (and other teeing providers) this growing capture is the
2250
+ // model's own assistant transcript, so an ordinary task that merely
2251
+ // mentions auth/login ("handle the authentication_error case") would
2252
+ // trip AuthDetector and brick the running turn into a false reauth
2253
+ // pause. Auth is detected only on the process-EXIT buffer (see onCliExit
2254
+ // below), which is the provider's real failure surface — a logged-out /
2255
+ // out-of-credit CLI exits fast and is caught there.
2256
+ // Rate limit detection — run for every stdout-teeing provider. The
2257
+ // RateLimitDetector phrases are scoped to error/banner forms, so this
2258
+ // is safe to apply provider-agnostically (grok/copilot were previously
2259
+ // excluded, letting a throttle slip through to a false [x]).
2260
+ {
2145
2261
  const rlErr = rateLimit_1.RateLimitDetector.detect(content);
2146
2262
  if (rlErr) {
2147
2263
  cleanup();
@@ -2158,28 +2274,13 @@ class TaskLoopRunner {
2158
2274
  return;
2159
2275
  }
2160
2276
  }
2161
- // Context length error detection (Claude). Patterns observed:
2277
+ // Context-length / thrash detection for the remaining CLI+TUI providers.
2278
+ // Patterns observed:
2162
2279
  // "prompt is too long: 1018289 tokens > 1000000 maximum"
2163
2280
  // "Prompt is too long" (last_assistant_message in StopFailure hook)
2164
- // "context_length_exceeded"
2281
+ // "context_length_exceeded" / "maximum context length"
2165
2282
  // "Autocompact is thrashing" (context refills immediately after compact)
2166
- if (isClaudeCli) {
2167
- const lc = content.toLowerCase();
2168
- if (lc.includes('autocompact is thrashing')) {
2169
- cleanup();
2170
- reject(new ThrashingError(content.trim()));
2171
- return;
2172
- }
2173
- if (lc.includes('prompt is too long')
2174
- || lc.includes('context_length_exceeded')
2175
- || /tokens?\s*>\s*\d+\s*maximum/.test(lc)) {
2176
- cleanup();
2177
- reject(new ContextLengthError(content.trim()));
2178
- return;
2179
- }
2180
- }
2181
- // Context length detection for claude-tui (same patterns)
2182
- if (isClaudeTui) {
2283
+ if (isClaudeCli || isClaudeTui || isGrokCli || isGrokTui || isCopilotCli) {
2183
2284
  const lc = content.toLowerCase();
2184
2285
  if (lc.includes('autocompact is thrashing')) {
2185
2286
  cleanup();
@@ -2188,6 +2289,7 @@ class TaskLoopRunner {
2188
2289
  }
2189
2290
  if (lc.includes('prompt is too long')
2190
2291
  || lc.includes('context_length_exceeded')
2292
+ || lc.includes('maximum context length')
2191
2293
  || /tokens?\s*>\s*\d+\s*maximum/.test(lc)) {
2192
2294
  cleanup();
2193
2295
  reject(new ContextLengthError(content.trim()));
@@ -2227,9 +2329,17 @@ class TaskLoopRunner {
2227
2329
  return;
2228
2330
  }
2229
2331
  // Fast-path: if the stdout capture file already contains a rate-limit
2230
- // or context-length phrase at exit time, raise immediately.
2332
+ // or context-length phrase at exit time, raise immediately. Applies to
2333
+ // every stdout-teeing provider so a throttled grok/copilot exit pauses
2334
+ // instead of being force-marked done.
2231
2335
  const exitStdout = readStdoutFile();
2232
- if (isClaudeCli) {
2336
+ if (teesStdout) {
2337
+ const authFromStdout = rateLimit_1.AuthDetector.detect(exitStdout);
2338
+ if (authFromStdout) {
2339
+ cleanup();
2340
+ reject(authFromStdout);
2341
+ return;
2342
+ }
2233
2343
  const rlFromStdout = rateLimit_1.RateLimitDetector.detect(exitStdout);
2234
2344
  if (rlFromStdout) {
2235
2345
  cleanup();