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/agentBackup/export.js +69 -2
- package/out/agentBackup/export.js.map +1 -1
- package/out/agentBackup/import.js +1 -7
- package/out/agentBackup/import.js.map +1 -1
- package/out/agentBackup/layout.d.ts +9 -0
- package/out/agentBackup/layout.js +10 -1
- package/out/agentBackup/layout.js.map +1 -1
- package/out/commands/start.js +11 -0
- package/out/commands/start.js.map +1 -1
- package/out/core/commands.d.ts +4 -0
- package/out/core/commands.js +26 -0
- package/out/core/commands.js.map +1 -0
- package/out/core/pathSafe.d.ts +9 -0
- package/out/core/pathSafe.js +97 -0
- package/out/core/pathSafe.js.map +1 -0
- package/out/core/projectMcp.d.ts +4 -0
- package/out/core/projectMcp.js +42 -1
- package/out/core/projectMcp.js.map +1 -1
- package/out/emailPoller.d.ts +1 -1
- package/out/emailPoller.js +4 -1
- package/out/emailPoller.js.map +1 -1
- package/out/git/gitService.js +9 -1
- package/out/git/gitService.js.map +1 -1
- package/out/rateLimit.js +6 -1
- package/out/rateLimit.js.map +1 -1
- package/out/taskLoop.d.ts +7 -0
- package/out/taskLoop.js +155 -19
- package/out/taskLoop.js.map +1 -1
- package/out/webSocketPoller.d.ts +1 -1
- package/out/webSocketPoller.js +308 -260
- package/out/webSocketPoller.js.map +1 -1
- package/out/webhookPoller.d.ts +1 -1
- package/out/webhookPoller.js +21 -5
- package/out/webhookPoller.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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,
|
package/out/rateLimit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,2EAA2E;AAC3E,uEAAuE;AACvE,+DAA+D;AAC/D,8EAA8E;;;AAE9E,MAAa,cAAe,SAAQ,KAAK;IAClB;IAA6B;IAAlD,YAAqB,UAAkB,EAAW,OAAyB;QACzE,KAAK,CAAC,UAAU,CAAC,CAAC;QADC,eAAU,GAAV,UAAU,CAAQ;QAAW,YAAO,GAAP,OAAO,CAAkB;QAEzE,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AALD,wCAKC;AAED;;;;;GAKG;AACH,MAAM,OAAO,GAA0B;IACrC,6EAA6E;IAC7E,mDAAmD;IACnD,iBAAiB,EAAiB,sDAAsD;IACxF,qBAAqB,EAAa,6DAA6D;IAC/F,sBAAsB;IACtB,6EAA6E;IAC7E,+EAA+E;IAC/E,4EAA4E;IAC5E,mCAAmC;IACnC,kCAAkC,EAAI,sCAAsC;IAC5E,mBAAmB,EAAmB,qCAAqC;CAC5E,CAAC;AAEF;;;;;GAKG;AACH,MAAa,SAAU,SAAQ,KAAK;IACb;IAArB,YAAqB,UAAkB;QACrC,KAAK,CAAC,UAAU,CAAC,CAAC;QADC,eAAU,GAAV,UAAU,CAAQ;QAErC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AALD,8BAKC;AAED;;;;;GAKG;AACH,MAAM,YAAY,GAA0B;IAC1C,kBAAkB,EAAqC,gDAAgD;IACvG,4BAA4B,EAA2B,iCAAiC;IACxF,uBAAuB,EAAgC,gCAAgC;IACvF,kDAAkD,EAAK,0BAA0B;IACjF,
|
|
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(() =>
|
|
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
|
|
2208
|
-
//
|
|
2209
|
-
//
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
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
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
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) {
|