vigthoria-cli 1.8.7 → 1.8.9
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/dist/commands/chat.js +32 -14
- package/dist/utils/api.d.ts +2 -0
- package/dist/utils/api.js +80 -24
- package/package.json +1 -1
package/dist/commands/chat.js
CHANGED
|
@@ -49,9 +49,28 @@ const session_js_1 = require("../utils/session.js");
|
|
|
49
49
|
const bridge_client_js_1 = require("../utils/bridge-client.js");
|
|
50
50
|
const workspace_stream_js_1 = require("../utils/workspace-stream.js");
|
|
51
51
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
52
|
-
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS
|
|
52
|
+
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
53
|
+
if (!rawValue) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
57
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
58
|
+
})();
|
|
59
|
+
const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
60
|
+
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
|
|
61
|
+
if (!rawValue) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
65
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
66
|
+
})();
|
|
67
|
+
const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
68
|
+
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS;
|
|
69
|
+
if (!rawValue) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
53
72
|
const parsed = Number.parseInt(rawValue, 10);
|
|
54
|
-
return Number.isFinite(parsed) && parsed
|
|
73
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
55
74
|
})();
|
|
56
75
|
class ChatCommand {
|
|
57
76
|
config;
|
|
@@ -122,13 +141,9 @@ class ChatCommand {
|
|
|
122
141
|
return this.getDefaultChatModel();
|
|
123
142
|
}
|
|
124
143
|
isLegacyAgentFallbackAllowed() {
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
if (process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '0') {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
return true;
|
|
144
|
+
// Hosted V3 Agent Mode must fail loudly by default so clients do not silently drop into legacy mode.
|
|
145
|
+
// Operators can still opt in during emergency local debugging.
|
|
146
|
+
return process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '1';
|
|
132
147
|
}
|
|
133
148
|
resolveAgentExecutionPolicy(prompt) {
|
|
134
149
|
const explicitModel = this.modelExplicitlySelected;
|
|
@@ -1624,7 +1639,7 @@ class ChatCommand {
|
|
|
1624
1639
|
clientSurface: 'cli',
|
|
1625
1640
|
localMachineCapable: true,
|
|
1626
1641
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1627
|
-
agentIdleTimeoutMs:
|
|
1642
|
+
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
1628
1643
|
model: routingPolicy.selectedModel,
|
|
1629
1644
|
requestedModel: this.currentModel,
|
|
1630
1645
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -1634,11 +1649,11 @@ class ChatCommand {
|
|
|
1634
1649
|
...runtimeContext,
|
|
1635
1650
|
onStreamEvent: spinner ? (event) => this.updateV3AgentSpinner(spinner, event) : undefined,
|
|
1636
1651
|
});
|
|
1637
|
-
const response = await (rescueEligible
|
|
1652
|
+
const response = await (rescueEligible && DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS > 0
|
|
1638
1653
|
? Promise.race([
|
|
1639
1654
|
workflowPromise,
|
|
1640
1655
|
new Promise((_, reject) => {
|
|
1641
|
-
setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')),
|
|
1656
|
+
setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')), DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS);
|
|
1642
1657
|
}),
|
|
1643
1658
|
])
|
|
1644
1659
|
: workflowPromise);
|
|
@@ -1758,13 +1773,16 @@ class ChatCommand {
|
|
|
1758
1773
|
spinner.stop();
|
|
1759
1774
|
}
|
|
1760
1775
|
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
1761
|
-
this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
|
|
1776
|
+
this.logger.debug(`V3 agent workflow unavailable: ${(0, api_js_1.sanitizeUserFacingErrorText)(error.message || '')}`);
|
|
1762
1777
|
return false;
|
|
1763
1778
|
}
|
|
1764
1779
|
if (spinner) {
|
|
1765
1780
|
spinner.stop();
|
|
1766
1781
|
}
|
|
1767
|
-
const
|
|
1782
|
+
const safeDetail = (0, api_js_1.sanitizeUserFacingErrorText)(error.message || '');
|
|
1783
|
+
const errorMessage = safeDetail
|
|
1784
|
+
? `Agent mode is unavailable right now. ${safeDetail}`
|
|
1785
|
+
: 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
|
|
1768
1786
|
this.logger.error(errorMessage);
|
|
1769
1787
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
1770
1788
|
if (this.jsonOutput) {
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -186,6 +186,8 @@ export interface VigthoriUser {
|
|
|
186
186
|
adminAccess: boolean;
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
|
+
export declare function sanitizeUserFacingErrorText(input: string): string;
|
|
190
|
+
export declare function describeUpstreamStatus(status: number): string;
|
|
189
191
|
export declare class APIClient {
|
|
190
192
|
private client;
|
|
191
193
|
private modelRouterClient;
|
package/dist/utils/api.js
CHANGED
|
@@ -10,6 +10,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.APIClient = exports.CLIError = void 0;
|
|
11
11
|
exports.classifyError = classifyError;
|
|
12
12
|
exports.formatCLIError = formatCLIError;
|
|
13
|
+
exports.sanitizeUserFacingErrorText = sanitizeUserFacingErrorText;
|
|
14
|
+
exports.describeUpstreamStatus = describeUpstreamStatus;
|
|
13
15
|
const axios_1 = __importDefault(require("axios"));
|
|
14
16
|
const crypto_1 = require("crypto");
|
|
15
17
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -90,20 +92,65 @@ function formatCLIError(err) {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
93
|
-
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS
|
|
95
|
+
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
96
|
+
if (!rawValue) {
|
|
97
|
+
// No total timeout by default for long-running SSE agent workflows.
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
94
100
|
const parsed = Number.parseInt(rawValue, 10);
|
|
95
|
-
return Number.isFinite(parsed) && parsed
|
|
101
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
96
102
|
})();
|
|
97
103
|
const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
98
|
-
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS
|
|
104
|
+
const rawValue = process.env.VIGTHORIA_AGENT_IDLE_TIMEOUT_MS || process.env.V3_AGENT_IDLE_TIMEOUT_MS;
|
|
105
|
+
if (!rawValue) {
|
|
106
|
+
// Keep stream open indefinitely unless user configures an idle limit.
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
99
109
|
const parsed = Number.parseInt(rawValue, 10);
|
|
100
|
-
return Number.isFinite(parsed) && parsed
|
|
110
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
101
111
|
})();
|
|
102
112
|
const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
|
|
103
|
-
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS
|
|
113
|
+
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS;
|
|
114
|
+
if (!rawValue) {
|
|
115
|
+
// BMAD/operator flows can be long-running; do not cap by default.
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
104
118
|
const parsed = Number.parseInt(rawValue, 10);
|
|
105
|
-
return Number.isFinite(parsed) && parsed
|
|
119
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
106
120
|
})();
|
|
121
|
+
// Sanitize an upstream error string before exposing it to the end user.
|
|
122
|
+
// Strips URLs, IPs:ports, absolute server paths, and bare hostnames so the
|
|
123
|
+
// CLI never reveals internal infrastructure to remote users.
|
|
124
|
+
function sanitizeUserFacingErrorText(input) {
|
|
125
|
+
if (!input)
|
|
126
|
+
return '';
|
|
127
|
+
let out = String(input);
|
|
128
|
+
out = out.replace(/https?:\/\/[^\s'"<>)]+/gi, '[redacted-url]');
|
|
129
|
+
out = out.replace(/\b\d{1,3}(?:\.\d{1,3}){3}(?::\d+)?\b/g, '[redacted-host]');
|
|
130
|
+
out = out.replace(/\b(?:localhost|127\.0\.0\.1)(?::\d+)?\b/gi, '[redacted-host]');
|
|
131
|
+
out = out.replace(/\b[a-z0-9.-]+\.vigthoria\.io\b/gi, '[redacted-host]');
|
|
132
|
+
out = out.replace(/(?:[A-Za-z]:)?[\\/](?:var|opt|tmp|home|root|etc|usr)[\\/][^\s'"<>)]*/gi, '[redacted-path]');
|
|
133
|
+
out = out.replace(/\{\s*"detail"\s*:\s*"[^"]*"\s*\}/g, '');
|
|
134
|
+
out = out.replace(/\s+/g, ' ').trim();
|
|
135
|
+
if (out.length > 160)
|
|
136
|
+
out = out.slice(0, 160) + '...';
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function describeUpstreamStatus(status) {
|
|
140
|
+
if (status === 401 || status === 403)
|
|
141
|
+
return 'Authentication failed. Please run vigthoria login.';
|
|
142
|
+
if (status === 404)
|
|
143
|
+
return 'Requested service endpoint was not found.';
|
|
144
|
+
if (status === 408 || status === 504)
|
|
145
|
+
return 'Upstream service timed out.';
|
|
146
|
+
if (status === 429)
|
|
147
|
+
return 'Rate limit reached. Please retry shortly.';
|
|
148
|
+
if (status >= 500)
|
|
149
|
+
return 'Upstream service is temporarily unavailable.';
|
|
150
|
+
if (status >= 400)
|
|
151
|
+
return 'Request was rejected by the service.';
|
|
152
|
+
return 'Unexpected response from service.';
|
|
153
|
+
}
|
|
107
154
|
class APIClient {
|
|
108
155
|
client;
|
|
109
156
|
modelRouterClient;
|
|
@@ -793,7 +840,7 @@ class APIClient {
|
|
|
793
840
|
});
|
|
794
841
|
if (!response.ok) {
|
|
795
842
|
const errorText = await response.text().catch(() => '');
|
|
796
|
-
throw new Error(`Template preview proof ${response.status}: ${
|
|
843
|
+
throw new Error(`Template preview proof ${response.status}: ${describeUpstreamStatus(response.status)}`);
|
|
797
844
|
}
|
|
798
845
|
const payload = await response.json();
|
|
799
846
|
const modes = payload?.modes || {};
|
|
@@ -1734,7 +1781,7 @@ menu {
|
|
|
1734
1781
|
});
|
|
1735
1782
|
if (!response.ok) {
|
|
1736
1783
|
const errorText = await response.text().catch(() => '');
|
|
1737
|
-
throw new Error(`MCP context update ${response.status}: ${
|
|
1784
|
+
throw new Error(`MCP context update ${response.status}: ${describeUpstreamStatus(response.status)}`);
|
|
1738
1785
|
}
|
|
1739
1786
|
return {
|
|
1740
1787
|
...executionContext,
|
|
@@ -1765,7 +1812,7 @@ menu {
|
|
|
1765
1812
|
});
|
|
1766
1813
|
if (!createResponse.ok) {
|
|
1767
1814
|
const errorText = await createResponse.text().catch(() => '');
|
|
1768
|
-
throw new Error(`MCP context create ${createResponse.status}: ${
|
|
1815
|
+
throw new Error(`MCP context create ${createResponse.status}: ${describeUpstreamStatus(createResponse.status)}`);
|
|
1769
1816
|
}
|
|
1770
1817
|
const payload = await createResponse.json();
|
|
1771
1818
|
const mcpContextId = String(payload.contextId || '').trim();
|
|
@@ -2122,6 +2169,11 @@ menu {
|
|
|
2122
2169
|
if (!looksLikeFrontendTask) {
|
|
2123
2170
|
return;
|
|
2124
2171
|
}
|
|
2172
|
+
// Skip motion/scroll enhancements for games — they use canvas, not section-based layouts
|
|
2173
|
+
const looksLikeGame = /\bgame\b|arcade|pac.?man|tetris|platformer|roguelike|breakout|pong|snake\s+game|tower\s+defense|playable/i.test(prompt);
|
|
2174
|
+
if (looksLikeGame) {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2125
2177
|
const htmlPath = path_1.default.join(rootPath, 'index.html');
|
|
2126
2178
|
if (!fs_1.default.existsSync(htmlPath)) {
|
|
2127
2179
|
return;
|
|
@@ -2568,7 +2620,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2568
2620
|
let contextId = response.headers.get('x-context-id') || String(context.contextId || '').trim() || null;
|
|
2569
2621
|
let serverWorkspaceRoot = null;
|
|
2570
2622
|
const streamedFiles = {};
|
|
2571
|
-
const idleTimeoutMs = context.agentIdleTimeoutMs
|
|
2623
|
+
const idleTimeoutMs = context.agentIdleTimeoutMs ?? DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS;
|
|
2572
2624
|
while (true) {
|
|
2573
2625
|
let chunk;
|
|
2574
2626
|
try {
|
|
@@ -2706,7 +2758,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2706
2758
|
partial: true,
|
|
2707
2759
|
};
|
|
2708
2760
|
}
|
|
2709
|
-
throw new Error(event.message || '
|
|
2761
|
+
throw new Error(`V3 agent: ${sanitizeUserFacingErrorText(event.message || '') || 'returned an error'}`);
|
|
2710
2762
|
}
|
|
2711
2763
|
if (event.type === 'complete' || event.type === 'message') {
|
|
2712
2764
|
final = event;
|
|
@@ -2724,15 +2776,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2724
2776
|
}
|
|
2725
2777
|
async runV3AgentWorkflow(message, context = {}) {
|
|
2726
2778
|
const executionContext = await this.bindExecutionContext(context);
|
|
2727
|
-
const baseTimeoutMs = executionContext.agentTimeoutMs
|
|
2779
|
+
const baseTimeoutMs = executionContext.agentTimeoutMs ?? DEFAULT_V3_AGENT_TIMEOUT_MS;
|
|
2728
2780
|
const expectedFiles = this.extractExpectedWorkspaceFiles(message, executionContext);
|
|
2729
2781
|
const requestedModel = String(executionContext.model || executionContext.requestedModel || 'agent');
|
|
2730
2782
|
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
2731
2783
|
const preferLocalV3 = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|responsive|animated|create the required project files and write them to the workspace)/i.test(message)
|
|
2732
2784
|
&& context.localMachineCapable !== false;
|
|
2733
|
-
const
|
|
2734
|
-
&& /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
|
|
2735
|
-
const timeoutMs = rescueEligibleSaaS ? Math.min(baseTimeoutMs, 210000) : baseTimeoutMs;
|
|
2785
|
+
const timeoutMs = baseTimeoutMs;
|
|
2736
2786
|
const maxAttempts = preferLocalV3 ? 2 : 1;
|
|
2737
2787
|
let lastErrors = [];
|
|
2738
2788
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
@@ -2766,12 +2816,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2766
2816
|
};
|
|
2767
2817
|
for (const baseUrl of this.getV3AgentBaseUrls(preferLocalV3)) {
|
|
2768
2818
|
const controller = new AbortController();
|
|
2769
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2819
|
+
const timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
2770
2820
|
try {
|
|
2771
2821
|
const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
|
|
2772
2822
|
if (!response.ok) {
|
|
2773
2823
|
const errorText = await response.text().catch(() => '');
|
|
2774
|
-
throw new Error(`V3 agent ${response.status}: ${
|
|
2824
|
+
throw new Error(`V3 agent ${response.status}: ${describeUpstreamStatus(response.status)}`);
|
|
2775
2825
|
}
|
|
2776
2826
|
const data = await this.collectV3AgentStream(response, requestExecutionContext);
|
|
2777
2827
|
// Auto-continuation: if the agent checkpointed (budget exceeded), continue automatically
|
|
@@ -2798,7 +2848,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2798
2848
|
stream: true,
|
|
2799
2849
|
};
|
|
2800
2850
|
const continueController = new AbortController();
|
|
2801
|
-
const continueTimeoutId = setTimeout(() => continueController.abort(), timeoutMs);
|
|
2851
|
+
const continueTimeoutId = timeoutMs > 0 ? setTimeout(() => continueController.abort(), timeoutMs) : null;
|
|
2802
2852
|
try {
|
|
2803
2853
|
const continueHeaders = await this.getV3AgentHeaders();
|
|
2804
2854
|
const continueResponse = await fetch(this.getV3AgentContinueUrl(baseUrl), {
|
|
@@ -2816,7 +2866,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2816
2866
|
break; // Fall through to normal completion with partial data
|
|
2817
2867
|
}
|
|
2818
2868
|
finally {
|
|
2819
|
-
|
|
2869
|
+
if (continueTimeoutId) {
|
|
2870
|
+
clearTimeout(continueTimeoutId);
|
|
2871
|
+
}
|
|
2820
2872
|
}
|
|
2821
2873
|
}
|
|
2822
2874
|
// Use the final continuation data for workspace recovery
|
|
@@ -2873,7 +2925,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2873
2925
|
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
2874
2926
|
}
|
|
2875
2927
|
finally {
|
|
2876
|
-
|
|
2928
|
+
if (timeoutId) {
|
|
2929
|
+
clearTimeout(timeoutId);
|
|
2930
|
+
}
|
|
2877
2931
|
}
|
|
2878
2932
|
}
|
|
2879
2933
|
lastErrors = errors;
|
|
@@ -2940,7 +2994,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2940
2994
|
}
|
|
2941
2995
|
async runOperatorWorkflow(message, context = {}) {
|
|
2942
2996
|
const executionContext = await this.bindExecutionContext(context);
|
|
2943
|
-
const timeoutMs = context.operatorTimeoutMs
|
|
2997
|
+
const timeoutMs = context.operatorTimeoutMs ?? DEFAULT_OPERATOR_TIMEOUT_MS;
|
|
2944
2998
|
const errors = [];
|
|
2945
2999
|
const authToken = this.config.get('authToken');
|
|
2946
3000
|
// Collect a lightweight workspace file listing so the operator can
|
|
@@ -2949,7 +3003,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2949
3003
|
const workspaceSummary = this.buildLocalWorkspaceSummary(workspacePath);
|
|
2950
3004
|
for (const baseUrl of this.getOperatorBaseUrls()) {
|
|
2951
3005
|
const controller = new AbortController();
|
|
2952
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
3006
|
+
const timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
2953
3007
|
try {
|
|
2954
3008
|
const response = await fetch(this.getOperatorStreamUrl(baseUrl), {
|
|
2955
3009
|
method: 'POST',
|
|
@@ -2994,7 +3048,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2994
3048
|
});
|
|
2995
3049
|
if (!response.ok) {
|
|
2996
3050
|
const errorText = await response.text().catch(() => '');
|
|
2997
|
-
throw new Error(`Operator stream ${response.status}: ${
|
|
3051
|
+
throw new Error(`Operator stream ${response.status}: ${describeUpstreamStatus(response.status)}`);
|
|
2998
3052
|
}
|
|
2999
3053
|
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
3000
3054
|
const fallbackData = await response.json();
|
|
@@ -3096,7 +3150,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3096
3150
|
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
3097
3151
|
}
|
|
3098
3152
|
finally {
|
|
3099
|
-
|
|
3153
|
+
if (timeoutId) {
|
|
3154
|
+
clearTimeout(timeoutId);
|
|
3155
|
+
}
|
|
3100
3156
|
}
|
|
3101
3157
|
}
|
|
3102
3158
|
throw new CLIError(`Operator workflow failed on all endpoints: ${errors.join(' | ')}`, 'model_backend');
|