autodev-cli 1.4.30 β†’ 1.4.32

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/taskLoop.js CHANGED
@@ -445,6 +445,11 @@ class TaskLoopRunner {
445
445
  }
446
446
  if (this._webhookPoller) {
447
447
  this._webhookPoller.setGitEnabled(settings.gitEnabled ?? false);
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);
448
453
  // Wake the idle no-task sleep instantly when a WS-pushed task arrives.
449
454
  this._webhookPoller.setOnTaskAppend(() => this._wakeIdleSleep());
450
455
  this._webhookPoller.setOnSteer((text) => this._handleSteer(text));
@@ -636,11 +641,25 @@ class TaskLoopRunner {
636
641
  const root = this._workspaceRoot;
637
642
  if (!root)
638
643
  return;
639
- 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
+ }
640
661
  try {
641
- // Cast to the shape saveProjectUserMcp expects
642
- const typed = entries;
643
- (0, projectMcp_1.saveProjectUserMcp)(root, typed);
662
+ (0, projectMcp_1.saveProjectUserMcp)(root, safe);
644
663
  configManager_1.ConfigManager.syncProjectMcpServers(root, (m) => this._cb?.log(m));
645
664
  void configManager_1.ConfigManager.reportProjectMcp(root, (m) => this._cb?.log(m));
646
665
  this._cb?.log('βœ… MCP config synced to .mcp.json, opencode.json, .vscode/mcp.json β€” restarting loop…');
@@ -686,6 +705,13 @@ class TaskLoopRunner {
686
705
  this._cb?.log('⚠️ restore_request ignored β€” serverApiKey not configured');
687
706
  return;
688
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
+ }
689
715
  this._cb?.log(`πŸ”„ restore_request received β€” downloading backup from ${downloadUrl}…`);
690
716
  const tmpPath = path.join(os.tmpdir(), `agent-restore-${agentId}-${Date.now()}.zip`);
691
717
  try {
@@ -1686,6 +1712,33 @@ class TaskLoopRunner {
1686
1712
  }
1687
1713
  }
1688
1714
  catch (err) {
1715
+ // --- Auth required: pause INDEFINITELY, never mark the task done ----
1716
+ // A logged-out / out-of-credit CLI is not a task failure. Put the task
1717
+ // back to [ ], flip the office to a distinct 'needs reauth' state, and
1718
+ // block until the operator re-authenticates and clicks Retry.
1719
+ if (err instanceof rateLimit_1.AuthError) {
1720
+ const rawMsg = err.rawMessage.slice(0, 300);
1721
+ const currentProvider = this._cb?.getActiveProvider() ?? 'unknown';
1722
+ this._cb?.log(`πŸ”‘ Authentication required (${currentProvider}) β€” pausing loop until re-auth. ${rawMsg}`);
1723
+ this._notifyDiscord(`πŸ”‘ **Authentication required** (${currentProvider}) β€” loop paused until you re-authenticate.\n\`\`\`\n${rawMsg}\n\`\`\``);
1724
+ this._notifyWebhook('reauth_required', {
1725
+ iteration: this._iterations,
1726
+ task: { text: task.text },
1727
+ message: rawMsg,
1728
+ provider: currentProvider,
1729
+ workDir: this._workspaceRoot,
1730
+ gitRepo: this._gitRepo,
1731
+ gitBranch: this._gitBranch,
1732
+ });
1733
+ // Never markDone β€” restore the task so it is retried after re-auth.
1734
+ await todoWriteManager_1.todoWriter.resetToTodo(todoPath, task).catch(() => { });
1735
+ // Pause indefinitely (no auto-resume) β€” operator must re-auth + Retry.
1736
+ await this._pauseLoop();
1737
+ if (this._state !== 'running') {
1738
+ break;
1739
+ }
1740
+ continue;
1741
+ }
1689
1742
  // --- Rate limit: pause loop, schedule auto-resume -----------------
1690
1743
  if (err instanceof rateLimit_1.RateLimitError) {
1691
1744
  // Two flavours:
@@ -1978,6 +2031,13 @@ class TaskLoopRunner {
1978
2031
  const iscopilotSdkProvider = this._cb?.getActiveProvider() === 'copilot-sdk';
1979
2032
  const isOpenCodeCli = this._cb?.getActiveProvider() === 'opencode-cli';
1980
2033
  const isOpencodeSdk = this._cb?.getActiveProvider() === 'opencode-sdk';
2034
+ // grok-cli / grok-tui / copilot-cli all tee stdout too β€” without these flags
2035
+ // the rate-limit / context-length / thrash detectors never ran for them, so
2036
+ // a throttled or context-blown turn was silently walked to give_up and the
2037
+ // task was falsely marked [x].
2038
+ const isGrokCli = this._cb?.getActiveProvider() === 'grok-cli';
2039
+ const isGrokTui = this._cb?.getActiveProvider() === 'grok-tui';
2040
+ const isCopilotCli = this._cb?.getActiveProvider() === 'copilot-cli';
1981
2041
  return new Promise((resolve, reject) => {
1982
2042
  if (this._state !== 'running') {
1983
2043
  resolve();
@@ -2111,10 +2171,15 @@ class TaskLoopRunner {
2111
2171
  // Track how many characters of the stdout file we've already forwarded
2112
2172
  let lastStdoutLen = 0;
2113
2173
  // Check stdout file: forward any new content to Discord/webhook, detect rate limit / context errors
2174
+ // Providers that write a stdout capture file we can scan for failure
2175
+ // banners (rate-limit / context-length / thrash). Every CLI/TUI provider
2176
+ // tees stdout; the two Claude ones and OpenCode also stream/parse extra.
2177
+ const teesStdout = isClaudeCli || isClaudeTui || iscopilotSdkProvider || isOpenCodeCli
2178
+ || isGrokCli || isGrokTui || isCopilotCli;
2114
2179
  const checkStdout = () => {
2115
- if (!isClaudeCli && !isClaudeTui && !iscopilotSdkProvider && !isOpenCodeCli) {
2180
+ if (!teesStdout) {
2116
2181
  return;
2117
- } // only CLI providers tee stdout
2182
+ }
2118
2183
  const content = readStdoutFile();
2119
2184
  // Forward new output lines to Discord / webhook.
2120
2185
  // claude-cli: stream partial chunks so the operator can see live progress.
@@ -2139,8 +2204,22 @@ class TaskLoopRunner {
2139
2204
  else {
2140
2205
  lastStdoutLen = content.length; // keep cursor up to date
2141
2206
  }
2142
- // Rate limit detection (Claude CLI + TUI)
2143
- if (isClaudeCli || isClaudeTui) {
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
+ }
2218
+ // Rate limit detection β€” run for every stdout-teeing provider. The
2219
+ // RateLimitDetector phrases are scoped to error/banner forms, so this
2220
+ // is safe to apply provider-agnostically (grok/copilot were previously
2221
+ // excluded, letting a throttle slip through to a false [x]).
2222
+ {
2144
2223
  const rlErr = rateLimit_1.RateLimitDetector.detect(content);
2145
2224
  if (rlErr) {
2146
2225
  cleanup();
@@ -2157,28 +2236,13 @@ class TaskLoopRunner {
2157
2236
  return;
2158
2237
  }
2159
2238
  }
2160
- // Context length error detection (Claude). Patterns observed:
2239
+ // Context-length / thrash detection for the remaining CLI+TUI providers.
2240
+ // Patterns observed:
2161
2241
  // "prompt is too long: 1018289 tokens > 1000000 maximum"
2162
2242
  // "Prompt is too long" (last_assistant_message in StopFailure hook)
2163
- // "context_length_exceeded"
2243
+ // "context_length_exceeded" / "maximum context length"
2164
2244
  // "Autocompact is thrashing" (context refills immediately after compact)
2165
- if (isClaudeCli) {
2166
- const lc = content.toLowerCase();
2167
- if (lc.includes('autocompact is thrashing')) {
2168
- cleanup();
2169
- reject(new ThrashingError(content.trim()));
2170
- return;
2171
- }
2172
- if (lc.includes('prompt is too long')
2173
- || lc.includes('context_length_exceeded')
2174
- || /tokens?\s*>\s*\d+\s*maximum/.test(lc)) {
2175
- cleanup();
2176
- reject(new ContextLengthError(content.trim()));
2177
- return;
2178
- }
2179
- }
2180
- // Context length detection for claude-tui (same patterns)
2181
- if (isClaudeTui) {
2245
+ if (isClaudeCli || isClaudeTui || isGrokCli || isGrokTui || isCopilotCli) {
2182
2246
  const lc = content.toLowerCase();
2183
2247
  if (lc.includes('autocompact is thrashing')) {
2184
2248
  cleanup();
@@ -2187,6 +2251,7 @@ class TaskLoopRunner {
2187
2251
  }
2188
2252
  if (lc.includes('prompt is too long')
2189
2253
  || lc.includes('context_length_exceeded')
2254
+ || lc.includes('maximum context length')
2190
2255
  || /tokens?\s*>\s*\d+\s*maximum/.test(lc)) {
2191
2256
  cleanup();
2192
2257
  reject(new ContextLengthError(content.trim()));
@@ -2226,9 +2291,17 @@ class TaskLoopRunner {
2226
2291
  return;
2227
2292
  }
2228
2293
  // Fast-path: if the stdout capture file already contains a rate-limit
2229
- // or context-length phrase at exit time, raise immediately.
2294
+ // or context-length phrase at exit time, raise immediately. Applies to
2295
+ // every stdout-teeing provider so a throttled grok/copilot exit pauses
2296
+ // instead of being force-marked done.
2230
2297
  const exitStdout = readStdoutFile();
2231
- if (isClaudeCli) {
2298
+ if (teesStdout) {
2299
+ const authFromStdout = rateLimit_1.AuthDetector.detect(exitStdout);
2300
+ if (authFromStdout) {
2301
+ cleanup();
2302
+ reject(authFromStdout);
2303
+ return;
2304
+ }
2232
2305
  const rlFromStdout = rateLimit_1.RateLimitDetector.detect(exitStdout);
2233
2306
  if (rlFromStdout) {
2234
2307
  cleanup();
@@ -2440,8 +2513,9 @@ class TaskLoopRunner {
2440
2513
  return;
2441
2514
  }
2442
2515
  }
2443
- // Also check stdout capture file as poller fallback (watcher handles most cases)
2444
- checkStdout();
2516
+ // (stdout capture file is already checked once at the top of this tick via
2517
+ // checkStdout() at line ~2215; a second scan here was redundant and doubled
2518
+ // the per-tick cost of re-reading the growing stdout file.)
2445
2519
  // Track JSONL activity
2446
2520
  const currentSize = (0, dispatcher_1.getClaudeSessionCursor)(this._workspaceRoot);
2447
2521
  if (currentSize !== lastJSONLSize) {