vigthoria-cli 1.6.28 → 1.6.29
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/auth.js +14 -4
- package/dist/commands/chat.js +13 -8
- package/dist/commands/edit.js +36 -8
- package/dist/commands/explain.js +2 -1
- package/dist/commands/generate.js +2 -1
- package/dist/commands/review.js +2 -1
- package/dist/utils/api.d.ts +27 -0
- package/dist/utils/api.js +245 -19
- package/dist/utils/logger.js +6 -1
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -217,25 +217,35 @@ class AuthCommand {
|
|
|
217
217
|
console.log();
|
|
218
218
|
console.log(chalk_1.default.white('Capability Truth:'));
|
|
219
219
|
console.log(chalk_1.default.gray(' Overall: ') + (capabilityStatus.overallOk ? chalk_1.default.green('Verified') : chalk_1.default.yellow('Partial')));
|
|
220
|
+
// V3 Agent — used by agent/chat commands
|
|
220
221
|
console.log(chalk_1.default.gray(' V3 Agent: ') + (capabilityStatus.v3Agent.ok ? chalk_1.default.green('Reachable') : chalk_1.default.red('Unavailable')));
|
|
221
222
|
if (capabilityStatus.v3Agent.error) {
|
|
222
223
|
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.red(capabilityStatus.v3Agent.error));
|
|
223
224
|
}
|
|
224
|
-
|
|
225
|
+
// Hyper Loop — optional orchestration layer
|
|
226
|
+
console.log(chalk_1.default.gray(' Hyper Loop: ') + (capabilityStatus.hyperLoop.ok ? chalk_1.default.green('Reachable') : chalk_1.default.yellow('Unavailable (does not affect AI commands)')));
|
|
225
227
|
if (capabilityStatus.hyperLoop.error) {
|
|
226
|
-
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.
|
|
228
|
+
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.yellow(capabilityStatus.hyperLoop.error));
|
|
227
229
|
}
|
|
228
|
-
|
|
230
|
+
// Repo Memory — separate auth scope, only affects repo commands
|
|
231
|
+
console.log(chalk_1.default.gray(' Repo Memory: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.yellow('Unavailable (does not affect AI commands)')));
|
|
229
232
|
if (capabilityStatus.repoMemory.details?.compactContextLength !== undefined) {
|
|
230
233
|
console.log(chalk_1.default.gray(' Compact Context: ') + chalk_1.default.cyan(`${capabilityStatus.repoMemory.details.compactContextLength} chars`));
|
|
231
234
|
}
|
|
232
235
|
if (capabilityStatus.repoMemory.error) {
|
|
233
236
|
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.yellow(capabilityStatus.repoMemory.error));
|
|
234
237
|
}
|
|
235
|
-
|
|
238
|
+
// DevTools Bridge — local service, not required
|
|
239
|
+
console.log(chalk_1.default.gray(' DevTools Bridge: ') + (capabilityStatus.devtoolsBridge.ok ? chalk_1.default.green('Reachable') : chalk_1.default.gray('Not running (optional)')));
|
|
236
240
|
if (capabilityStatus.devtoolsBridge.error) {
|
|
237
241
|
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.gray(capabilityStatus.devtoolsBridge.error));
|
|
238
242
|
}
|
|
243
|
+
// Auth scope summary
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(chalk_1.default.white('Auth Scopes:'));
|
|
246
|
+
console.log(chalk_1.default.gray(' Model Auth: ') + (this.config.isAuthenticated() ? chalk_1.default.green('Active') : chalk_1.default.red('Missing')) + chalk_1.default.gray(' (used by chat, agent, review, explain, generate, fix)'));
|
|
247
|
+
console.log(chalk_1.default.gray(' Repo Auth: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.yellow('Inactive')) + chalk_1.default.gray(' (used by repo push/pull/list only)'));
|
|
248
|
+
console.log(chalk_1.default.gray(' Bridge Auth: ') + (capabilityStatus.devtoolsBridge.ok ? chalk_1.default.green('Connected') : chalk_1.default.gray('N/A')) + chalk_1.default.gray(' (used by --bridge flag only)'));
|
|
239
249
|
console.log();
|
|
240
250
|
}
|
|
241
251
|
printLoginSuccess() {
|
package/dist/commands/chat.js
CHANGED
|
@@ -846,7 +846,8 @@ class ChatCommand {
|
|
|
846
846
|
if (spinner) {
|
|
847
847
|
spinner.stop();
|
|
848
848
|
}
|
|
849
|
-
const
|
|
849
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
850
|
+
const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
|
|
850
851
|
if (!this.jsonOutput) {
|
|
851
852
|
this.logger.error('Operator workflow failed');
|
|
852
853
|
}
|
|
@@ -857,6 +858,7 @@ class ChatCommand {
|
|
|
857
858
|
mode: 'operator',
|
|
858
859
|
content: '',
|
|
859
860
|
error: errorMsg,
|
|
861
|
+
errorCategory: cliErr.category,
|
|
860
862
|
}, null, 2));
|
|
861
863
|
}
|
|
862
864
|
else {
|
|
@@ -924,9 +926,8 @@ class ChatCommand {
|
|
|
924
926
|
catch (error) {
|
|
925
927
|
if (spinner)
|
|
926
928
|
spinner.stop();
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const errorMsg = error.message;
|
|
929
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
930
|
+
const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
|
|
930
931
|
if (this.jsonOutput) {
|
|
931
932
|
process.exitCode = 1;
|
|
932
933
|
console.log(JSON.stringify({
|
|
@@ -935,6 +936,7 @@ class ChatCommand {
|
|
|
935
936
|
model: this.currentModel,
|
|
936
937
|
content: '',
|
|
937
938
|
error: errorMsg,
|
|
939
|
+
errorCategory: cliErr.category,
|
|
938
940
|
}, null, 2));
|
|
939
941
|
}
|
|
940
942
|
else {
|
|
@@ -1046,8 +1048,8 @@ class ChatCommand {
|
|
|
1046
1048
|
catch (error) {
|
|
1047
1049
|
if (spinner)
|
|
1048
1050
|
spinner.stop();
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
1052
|
+
const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
|
|
1051
1053
|
if (this.jsonOutput) {
|
|
1052
1054
|
process.exitCode = 1;
|
|
1053
1055
|
console.log(JSON.stringify({
|
|
@@ -1056,13 +1058,16 @@ class ChatCommand {
|
|
|
1056
1058
|
model: this.currentModel,
|
|
1057
1059
|
partial: false,
|
|
1058
1060
|
content: '',
|
|
1059
|
-
error:
|
|
1061
|
+
error: errorMsg,
|
|
1062
|
+
errorCategory: cliErr.category,
|
|
1060
1063
|
metadata: {
|
|
1061
1064
|
executionPath: 'local-agent-loop',
|
|
1062
1065
|
},
|
|
1063
1066
|
}, null, 2));
|
|
1064
1067
|
}
|
|
1065
|
-
|
|
1068
|
+
else {
|
|
1069
|
+
this.logger.error(errorMsg);
|
|
1070
|
+
}
|
|
1066
1071
|
return;
|
|
1067
1072
|
}
|
|
1068
1073
|
}
|
package/dist/commands/edit.js
CHANGED
|
@@ -108,7 +108,8 @@ Return the complete modified file content:`,
|
|
|
108
108
|
}
|
|
109
109
|
catch (error) {
|
|
110
110
|
spinner.stop();
|
|
111
|
-
|
|
111
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
112
|
+
this.logger.error((0, api_js_1.formatCLIError)(cliErr));
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
async fix(filePath, options) {
|
|
@@ -173,7 +174,8 @@ Return the complete modified file content:`,
|
|
|
173
174
|
}
|
|
174
175
|
catch (error) {
|
|
175
176
|
spinner.stop();
|
|
176
|
-
|
|
177
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
178
|
+
this.logger.error((0, api_js_1.formatCLIError)(cliErr));
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
181
|
extractCode(response, language) {
|
|
@@ -216,15 +218,41 @@ Return the complete modified file content:`,
|
|
|
216
218
|
const len = lines.length;
|
|
217
219
|
if (len < 4)
|
|
218
220
|
return code;
|
|
219
|
-
// Pass 1: Remove
|
|
221
|
+
// Pass 1: Remove model stutter — consecutive runs of 3+ identical
|
|
222
|
+
// non-empty lines are collapsed to one. A pair (exactly 2) is only
|
|
223
|
+
// collapsed when it occurs at the very end of the output (trailing
|
|
224
|
+
// stutter). Pairs in the middle are kept — they may be intentional
|
|
225
|
+
// (e.g. repeated data rows, CSS rules).
|
|
220
226
|
const deduped = [];
|
|
221
|
-
|
|
227
|
+
let i = 0;
|
|
228
|
+
while (i < lines.length) {
|
|
222
229
|
const trimmed = lines[i].trim();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
if (!trimmed) {
|
|
231
|
+
deduped.push(lines[i]);
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Count the run length of identical consecutive lines
|
|
236
|
+
let runEnd = i + 1;
|
|
237
|
+
while (runEnd < lines.length && lines[runEnd].trim() === trimmed) {
|
|
238
|
+
runEnd++;
|
|
239
|
+
}
|
|
240
|
+
const runLen = runEnd - i;
|
|
241
|
+
if (runLen >= 3) {
|
|
242
|
+
// 3+ identical lines is almost certainly stutter — keep one
|
|
243
|
+
deduped.push(lines[i]);
|
|
244
|
+
}
|
|
245
|
+
else if (runLen === 2 && runEnd === lines.length) {
|
|
246
|
+
// Exactly 2 identical lines at the very end — trailing stutter
|
|
247
|
+
deduped.push(lines[i]);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// 1 line, or a pair in the middle — keep all
|
|
251
|
+
for (let j = i; j < runEnd; j++) {
|
|
252
|
+
deduped.push(lines[j]);
|
|
253
|
+
}
|
|
226
254
|
}
|
|
227
|
-
|
|
255
|
+
i = runEnd;
|
|
228
256
|
}
|
|
229
257
|
if (deduped.length < lines.length) {
|
|
230
258
|
return deduped.join('\n');
|
package/dist/commands/explain.js
CHANGED
|
@@ -75,7 +75,8 @@ class ExplainCommand {
|
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
77
|
spinner.stop();
|
|
78
|
-
|
|
78
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
79
|
+
this.logger.error((0, api_js_1.formatCLIError)(cliErr));
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
formatExplanation(explanation, detail) {
|
|
@@ -105,7 +105,8 @@ class GenerateCommand {
|
|
|
105
105
|
}
|
|
106
106
|
catch (error) {
|
|
107
107
|
spinner.stop();
|
|
108
|
-
|
|
108
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
109
|
+
this.logger.error((0, api_js_1.formatCLIError)(cliErr));
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
/**
|
package/dist/commands/review.js
CHANGED
|
@@ -65,7 +65,8 @@ class ReviewCommand {
|
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
67
|
spinner.stop();
|
|
68
|
-
|
|
68
|
+
const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
|
|
69
|
+
this.logger.error((0, api_js_1.formatCLIError)(cliErr));
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
printTextReview(review) {
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Config } from './config.js';
|
|
6
6
|
import { Logger } from './logger.js';
|
|
7
|
+
export type CLIErrorCategory = 'auth' | 'repo_session' | 'model_backend' | 'bridge' | 'network' | 'timeout' | 'parsing' | 'tool_execution';
|
|
8
|
+
export declare class CLIError extends Error {
|
|
9
|
+
category: CLIErrorCategory;
|
|
10
|
+
statusCode?: number;
|
|
11
|
+
endpoint?: string;
|
|
12
|
+
constructor(message: string, category: CLIErrorCategory, opts?: {
|
|
13
|
+
statusCode?: number;
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
cause?: Error;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Classify an axios or fetch error into a structured CLIError. */
|
|
19
|
+
export declare function classifyError(error: unknown, fallbackCategory?: CLIErrorCategory): CLIError;
|
|
20
|
+
/** Format a CLIError for user-facing display. */
|
|
21
|
+
export declare function formatCLIError(err: CLIError): string;
|
|
7
22
|
export interface ChatMessage {
|
|
8
23
|
role: 'user' | 'assistant' | 'system';
|
|
9
24
|
content: string;
|
|
@@ -395,6 +410,18 @@ export declare class APIClient {
|
|
|
395
410
|
* Returns a human-readable description of obvious errors, or empty string.
|
|
396
411
|
*/
|
|
397
412
|
private detectSyntaxErrors;
|
|
413
|
+
/**
|
|
414
|
+
* Strip comment lines that the model added during a fix but were not
|
|
415
|
+
* present in the original code. Used for syntax-only fixes where the
|
|
416
|
+
* model tends to annotate its changes with "// Fixed ..." comments.
|
|
417
|
+
*/
|
|
418
|
+
private stripInjectedComments;
|
|
419
|
+
/**
|
|
420
|
+
* Ensure the fixed code hasn't lost closing delimiters relative to the
|
|
421
|
+
* original. Counts {, }, (, ), [, ] outside strings/comments and if
|
|
422
|
+
* the fix has fewer closers than the original, appends the missing ones.
|
|
423
|
+
*/
|
|
424
|
+
private repairBracketBalance;
|
|
398
425
|
private resolveModelId;
|
|
399
426
|
private getCoderHealth;
|
|
400
427
|
private getModelsHealth;
|
package/dist/utils/api.js
CHANGED
|
@@ -7,7 +7,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.APIClient = void 0;
|
|
10
|
+
exports.APIClient = exports.CLIError = void 0;
|
|
11
|
+
exports.classifyError = classifyError;
|
|
12
|
+
exports.formatCLIError = formatCLIError;
|
|
11
13
|
const axios_1 = __importDefault(require("axios"));
|
|
12
14
|
const crypto_1 = require("crypto");
|
|
13
15
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -15,6 +17,77 @@ const https_1 = __importDefault(require("https"));
|
|
|
15
17
|
const net_1 = __importDefault(require("net"));
|
|
16
18
|
const path_1 = __importDefault(require("path"));
|
|
17
19
|
const ws_1 = __importDefault(require("ws"));
|
|
20
|
+
class CLIError extends Error {
|
|
21
|
+
category;
|
|
22
|
+
statusCode;
|
|
23
|
+
endpoint;
|
|
24
|
+
constructor(message, category, opts) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'CLIError';
|
|
27
|
+
this.category = category;
|
|
28
|
+
this.statusCode = opts?.statusCode;
|
|
29
|
+
this.endpoint = opts?.endpoint;
|
|
30
|
+
if (opts?.cause)
|
|
31
|
+
this.cause = opts.cause;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.CLIError = CLIError;
|
|
35
|
+
/** Classify an axios or fetch error into a structured CLIError. */
|
|
36
|
+
function classifyError(error, fallbackCategory = 'network') {
|
|
37
|
+
if (error instanceof CLIError)
|
|
38
|
+
return error;
|
|
39
|
+
const axErr = error;
|
|
40
|
+
const status = axErr?.response?.status;
|
|
41
|
+
const endpoint = axErr?.config?.url || axErr?.config?.baseURL || '';
|
|
42
|
+
const message = axErr?.response?.data
|
|
43
|
+
? typeof axErr.response.data.error === 'string'
|
|
44
|
+
? axErr.response.data.error
|
|
45
|
+
: typeof axErr.response.data.message === 'string'
|
|
46
|
+
? axErr.response.data.message
|
|
47
|
+
: error.message
|
|
48
|
+
: error.message || String(error);
|
|
49
|
+
if (status === 401 || status === 403) {
|
|
50
|
+
// Distinguish repo/community auth from model auth
|
|
51
|
+
if (/community|repo/i.test(endpoint)) {
|
|
52
|
+
return new CLIError(message, 'repo_session', { statusCode: status, endpoint });
|
|
53
|
+
}
|
|
54
|
+
return new CLIError(message, 'auth', { statusCode: status, endpoint });
|
|
55
|
+
}
|
|
56
|
+
if (status && status >= 500) {
|
|
57
|
+
return new CLIError(message, 'model_backend', { statusCode: status, endpoint });
|
|
58
|
+
}
|
|
59
|
+
if (/timeout|ETIMEDOUT|ESOCKETTIMEDOUT|aborted/i.test(message)) {
|
|
60
|
+
return new CLIError(message, 'timeout', { endpoint });
|
|
61
|
+
}
|
|
62
|
+
if (/ECONNREFUSED|ENOTFOUND|ENETUNREACH|EAI_AGAIN|fetch failed/i.test(message)) {
|
|
63
|
+
return new CLIError(message, 'network', { endpoint });
|
|
64
|
+
}
|
|
65
|
+
return new CLIError(message, fallbackCategory, { statusCode: status, endpoint, cause: error instanceof Error ? error : undefined });
|
|
66
|
+
}
|
|
67
|
+
/** Format a CLIError for user-facing display. */
|
|
68
|
+
function formatCLIError(err) {
|
|
69
|
+
const tag = `[${err.category}]`;
|
|
70
|
+
switch (err.category) {
|
|
71
|
+
case 'auth':
|
|
72
|
+
return `${tag} Authentication failed${err.statusCode ? ` (${err.statusCode})` : ''}. Run: vigthoria login`;
|
|
73
|
+
case 'repo_session':
|
|
74
|
+
return `${tag} Repository session expired or missing${err.statusCode ? ` (${err.statusCode})` : ''}. This does not affect AI commands. Re-authenticate repo with: vigthoria repo list`;
|
|
75
|
+
case 'model_backend':
|
|
76
|
+
return `${tag} Model backend error${err.statusCode ? ` (${err.statusCode})` : ''}: ${err.message}`;
|
|
77
|
+
case 'bridge':
|
|
78
|
+
return `${tag} Bridge connection error: ${err.message}`;
|
|
79
|
+
case 'network':
|
|
80
|
+
return `${tag} Network error: ${err.message}. Check your internet connection.`;
|
|
81
|
+
case 'timeout':
|
|
82
|
+
return `${tag} Request timed out: ${err.message}`;
|
|
83
|
+
case 'parsing':
|
|
84
|
+
return `${tag} Response parsing error: ${err.message}`;
|
|
85
|
+
case 'tool_execution':
|
|
86
|
+
return `${tag} Tool execution error: ${err.message}`;
|
|
87
|
+
default:
|
|
88
|
+
return `${tag} ${err.message}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
18
91
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
19
92
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS || '1200000';
|
|
20
93
|
const parsed = Number.parseInt(rawValue, 10);
|
|
@@ -26,9 +99,9 @@ const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
|
26
99
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
|
|
27
100
|
})();
|
|
28
101
|
const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
|
|
29
|
-
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS || '
|
|
102
|
+
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS || '300000';
|
|
30
103
|
const parsed = Number.parseInt(rawValue, 10);
|
|
31
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed :
|
|
104
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
|
|
32
105
|
})();
|
|
33
106
|
class APIClient {
|
|
34
107
|
client;
|
|
@@ -102,16 +175,24 @@ class APIClient {
|
|
|
102
175
|
}
|
|
103
176
|
return req;
|
|
104
177
|
});
|
|
105
|
-
// Add response
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
178
|
+
// Add response interceptors for token refresh + structured errors
|
|
179
|
+
const createAuthRetryInterceptor = (client) => {
|
|
180
|
+
client.interceptors.response.use((res) => res, async (error) => {
|
|
181
|
+
if (error.response?.status === 401) {
|
|
182
|
+
const refreshed = await this.refreshToken();
|
|
183
|
+
if (refreshed && error.config) {
|
|
184
|
+
return client.request(error.config);
|
|
185
|
+
}
|
|
186
|
+
throw classifyError(error, 'auth');
|
|
111
187
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
188
|
+
throw classifyError(error);
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
createAuthRetryInterceptor(this.client);
|
|
192
|
+
createAuthRetryInterceptor(this.modelRouterClient);
|
|
193
|
+
if (this.selfHostedModelRouterClient) {
|
|
194
|
+
createAuthRetryInterceptor(this.selfHostedModelRouterClient);
|
|
195
|
+
}
|
|
115
196
|
}
|
|
116
197
|
getSelfHostedModelsApiUrl() {
|
|
117
198
|
const configuredUrl = process.env.VIGTHORIA_SELF_HOSTED_MODELS_API_URL
|
|
@@ -2932,13 +3013,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2932
3013
|
};
|
|
2933
3014
|
}
|
|
2934
3015
|
catch (error) {
|
|
3016
|
+
const isAbort = error?.name === 'AbortError' || error?.code === 'ABORT_ERR';
|
|
3017
|
+
if (isAbort) {
|
|
3018
|
+
const mins = Math.round(timeoutMs / 60000);
|
|
3019
|
+
throw new CLIError(`Operator workflow timed out after ${mins} minute(s). You can increase the timeout with VIGTHORIA_OPERATOR_TIMEOUT_MS.`, 'timeout', error);
|
|
3020
|
+
}
|
|
2935
3021
|
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
2936
3022
|
}
|
|
2937
3023
|
finally {
|
|
2938
3024
|
clearTimeout(timeoutId);
|
|
2939
3025
|
}
|
|
2940
3026
|
}
|
|
2941
|
-
throw new
|
|
3027
|
+
throw new CLIError(`Operator workflow failed on all endpoints: ${errors.join(' | ')}`, 'model_backend');
|
|
2942
3028
|
}
|
|
2943
3029
|
/**
|
|
2944
3030
|
* Chat API - Direct Vigthoria Models API Architecture
|
|
@@ -2970,7 +3056,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2970
3056
|
}
|
|
2971
3057
|
}
|
|
2972
3058
|
// No more localhost fallbacks - CLI is for external users!
|
|
2973
|
-
throw new
|
|
3059
|
+
throw new CLIError('AI service unavailable. Please check your internet connection or try again later.', 'model_backend');
|
|
2974
3060
|
}
|
|
2975
3061
|
shouldSkipCloudRoutes(resolvedModel) {
|
|
2976
3062
|
return this.shouldSimulateCloudFailure() && this.isCloudModelId(resolvedModel);
|
|
@@ -3602,9 +3688,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3602
3688
|
.map(i => `Line ${i.line}: [${i.type}] ${i.message}`)
|
|
3603
3689
|
.join('\n// ');
|
|
3604
3690
|
const allHints = [syntaxHints, logicHints].filter(Boolean).join('\n// ');
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3691
|
+
// Build a constraint preamble that's stricter for syntax-only mode.
|
|
3692
|
+
let preamble;
|
|
3693
|
+
if (fixType === 'syntax') {
|
|
3694
|
+
preamble = allHints
|
|
3695
|
+
? `// SYNTAX ERRORS DETECTED BY CLIENT:\n// ${allHints}\n// RULES: Fix ONLY bracket/parenthesis/brace mismatches and keyword typos. Do NOT rename functions, do NOT add comments, do NOT restructure or reformat. Output the corrected code ONLY.\n\n`
|
|
3696
|
+
: '';
|
|
3697
|
+
}
|
|
3698
|
+
else {
|
|
3699
|
+
preamble = allHints
|
|
3700
|
+
? `// BUGS DETECTED BY STATIC ANALYSIS — YOU MUST FIX THESE:\n// ${allHints}\n// IMPORTANT: Fix ONLY these specific bugs. Do not add comments, do not restructure the code, do not add or remove lines beyond the minimal fix.\n\n`
|
|
3701
|
+
: '';
|
|
3702
|
+
}
|
|
3703
|
+
const augmentedCode = preamble ? `${preamble}${code}` : code;
|
|
3608
3704
|
const response = await this.client.post('/api/ai/fix', {
|
|
3609
3705
|
code: augmentedCode,
|
|
3610
3706
|
language,
|
|
@@ -3616,16 +3712,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3616
3712
|
// If server returned no changes but we found issues, strip
|
|
3617
3713
|
// our injected comment prefix from the returned code and attempt
|
|
3618
3714
|
// a basic client-side repair.
|
|
3619
|
-
if (changes.length === 0 &&
|
|
3715
|
+
if (changes.length === 0 && preamble && fixed === augmentedCode) {
|
|
3620
3716
|
fixed = code; // restore original
|
|
3621
3717
|
}
|
|
3622
3718
|
// Strip the injected comment block if it leaked into the output
|
|
3623
|
-
if (fixed.startsWith('// BUGS DETECTED BY STATIC ANALYSIS') || fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT
|
|
3719
|
+
if (fixed.startsWith('// BUGS DETECTED BY STATIC ANALYSIS') || fixed.startsWith('// SYNTAX ERRORS DETECTED BY CLIENT')) {
|
|
3624
3720
|
const idx = fixed.indexOf('\n\n');
|
|
3625
3721
|
if (idx !== -1) {
|
|
3626
3722
|
fixed = fixed.slice(idx + 2);
|
|
3627
3723
|
}
|
|
3628
3724
|
}
|
|
3725
|
+
// For syntax-only fixes, strip any comments the model added that
|
|
3726
|
+
// weren't in the original code (e.g. "// Fixed mismatched parentheses").
|
|
3727
|
+
if (fixType === 'syntax' && fixed !== code) {
|
|
3728
|
+
fixed = this.stripInjectedComments(code, fixed, language);
|
|
3729
|
+
}
|
|
3730
|
+
// Safety net: for syntax fixes, ensure the fix didn't make bracket
|
|
3731
|
+
// balance worse. If the original had more closing delimiters than
|
|
3732
|
+
// the fix, append the missing ones.
|
|
3733
|
+
if (fixType === 'syntax' && fixed !== code) {
|
|
3734
|
+
fixed = this.repairBracketBalance(code, fixed);
|
|
3735
|
+
}
|
|
3629
3736
|
// If there are still no changes but the fixed code differs, compute
|
|
3630
3737
|
// a semantic diff using LCS so inserted/removed lines don't cause
|
|
3631
3738
|
// every subsequent line to appear as changed.
|
|
@@ -3806,6 +3913,125 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3806
3913
|
}
|
|
3807
3914
|
return errors.join('; ');
|
|
3808
3915
|
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Strip comment lines that the model added during a fix but were not
|
|
3918
|
+
* present in the original code. Used for syntax-only fixes where the
|
|
3919
|
+
* model tends to annotate its changes with "// Fixed ..." comments.
|
|
3920
|
+
*/
|
|
3921
|
+
stripInjectedComments(original, fixed, language) {
|
|
3922
|
+
const lang = language.toLowerCase();
|
|
3923
|
+
// Only handle JS/TS/Python single-line comment patterns for now
|
|
3924
|
+
let wholeLineRe;
|
|
3925
|
+
let inlineRe;
|
|
3926
|
+
if (lang === 'python' || lang === 'py') {
|
|
3927
|
+
wholeLineRe = /^\s*#\s/;
|
|
3928
|
+
inlineRe = /\s+#\s.*$/;
|
|
3929
|
+
}
|
|
3930
|
+
else {
|
|
3931
|
+
wholeLineRe = /^\s*\/\/\s/;
|
|
3932
|
+
inlineRe = /\s+\/\/\s.*$/;
|
|
3933
|
+
}
|
|
3934
|
+
const origLines = original.split('\n');
|
|
3935
|
+
const origSet = new Set(origLines.map(l => l.trim()));
|
|
3936
|
+
const fixedLines = fixed.split('\n');
|
|
3937
|
+
const result = [];
|
|
3938
|
+
for (let idx = 0; idx < fixedLines.length; idx++) {
|
|
3939
|
+
let line = fixedLines[idx];
|
|
3940
|
+
// Remove whole-line comments not in original
|
|
3941
|
+
if (wholeLineRe.test(line) && !origSet.has(line.trim())) {
|
|
3942
|
+
continue;
|
|
3943
|
+
}
|
|
3944
|
+
// Strip inline trailing comments the model added.
|
|
3945
|
+
// Only strip if the corresponding original line at the same position
|
|
3946
|
+
// didn't have an inline comment at all.
|
|
3947
|
+
if (inlineRe.test(line)) {
|
|
3948
|
+
const origLine = idx < origLines.length ? origLines[idx] : '';
|
|
3949
|
+
if (!inlineRe.test(origLine)) {
|
|
3950
|
+
line = line.replace(inlineRe, '');
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
result.push(line);
|
|
3954
|
+
}
|
|
3955
|
+
return result.join('\n');
|
|
3956
|
+
}
|
|
3957
|
+
/**
|
|
3958
|
+
* Ensure the fixed code hasn't lost closing delimiters relative to the
|
|
3959
|
+
* original. Counts {, }, (, ), [, ] outside strings/comments and if
|
|
3960
|
+
* the fix has fewer closers than the original, appends the missing ones.
|
|
3961
|
+
*/
|
|
3962
|
+
repairBracketBalance(original, fixed) {
|
|
3963
|
+
const count = (src) => {
|
|
3964
|
+
let braces = 0, parens = 0, brackets = 0;
|
|
3965
|
+
let inStr = null;
|
|
3966
|
+
let inLine = false, inBlock = false;
|
|
3967
|
+
for (let i = 0; i < src.length; i++) {
|
|
3968
|
+
const ch = src[i], nx = src[i + 1] || '';
|
|
3969
|
+
if (inLine) {
|
|
3970
|
+
if (ch === '\n')
|
|
3971
|
+
inLine = false;
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
if (inBlock) {
|
|
3975
|
+
if (ch === '*' && nx === '/') {
|
|
3976
|
+
inBlock = false;
|
|
3977
|
+
i++;
|
|
3978
|
+
}
|
|
3979
|
+
continue;
|
|
3980
|
+
}
|
|
3981
|
+
if (inStr) {
|
|
3982
|
+
if (ch === inStr && src[i - 1] !== '\\')
|
|
3983
|
+
inStr = null;
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3986
|
+
if (ch === '/' && nx === '/') {
|
|
3987
|
+
inLine = true;
|
|
3988
|
+
continue;
|
|
3989
|
+
}
|
|
3990
|
+
if (ch === '/' && nx === '*') {
|
|
3991
|
+
inBlock = true;
|
|
3992
|
+
continue;
|
|
3993
|
+
}
|
|
3994
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
3995
|
+
inStr = ch;
|
|
3996
|
+
continue;
|
|
3997
|
+
}
|
|
3998
|
+
if (ch === '{')
|
|
3999
|
+
braces++;
|
|
4000
|
+
else if (ch === '}')
|
|
4001
|
+
braces--;
|
|
4002
|
+
else if (ch === '(')
|
|
4003
|
+
parens++;
|
|
4004
|
+
else if (ch === ')')
|
|
4005
|
+
parens--;
|
|
4006
|
+
else if (ch === '[')
|
|
4007
|
+
brackets++;
|
|
4008
|
+
else if (ch === ']')
|
|
4009
|
+
brackets--;
|
|
4010
|
+
}
|
|
4011
|
+
return { braces, parens, brackets };
|
|
4012
|
+
};
|
|
4013
|
+
const orig = count(original);
|
|
4014
|
+
const fix = count(fixed);
|
|
4015
|
+
const append = [];
|
|
4016
|
+
// If original was balanced (or close) but fix is missing closers,
|
|
4017
|
+
// append them. Only act when the fix is *more unbalanced* than original.
|
|
4018
|
+
if (fix.braces > orig.braces) {
|
|
4019
|
+
for (let i = 0; i < fix.braces - orig.braces; i++)
|
|
4020
|
+
append.push('}');
|
|
4021
|
+
}
|
|
4022
|
+
if (fix.parens > orig.parens) {
|
|
4023
|
+
for (let i = 0; i < fix.parens - orig.parens; i++)
|
|
4024
|
+
append.push(')');
|
|
4025
|
+
}
|
|
4026
|
+
if (fix.brackets > orig.brackets) {
|
|
4027
|
+
for (let i = 0; i < fix.brackets - orig.brackets; i++)
|
|
4028
|
+
append.push(']');
|
|
4029
|
+
}
|
|
4030
|
+
if (append.length > 0) {
|
|
4031
|
+
return fixed.trimEnd() + '\n' + append.join('\n');
|
|
4032
|
+
}
|
|
4033
|
+
return fixed;
|
|
4034
|
+
}
|
|
3809
4035
|
// Model resolution - maps Vigthoria model names to internal IDs
|
|
3810
4036
|
// INTERNAL USE ONLY - users see only Vigthoria branding
|
|
3811
4037
|
resolveModelId(shortName) {
|
package/dist/utils/logger.js
CHANGED
|
@@ -24,7 +24,12 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
24
24
|
*/
|
|
25
25
|
function createSpinner(textOrOpts) {
|
|
26
26
|
const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
|
|
27
|
-
|
|
27
|
+
// Suppress spinner animation when stderr is not a TTY (piped output,
|
|
28
|
+
// CI, non-interactive terminals). The spinner object still works — its
|
|
29
|
+
// .start()/.stop()/.succeed() methods are no-ops — so callers don't
|
|
30
|
+
// need conditional logic.
|
|
31
|
+
const isSilent = !process.stderr.isTTY;
|
|
32
|
+
return (0, ora_1.default)({ ...opts, stream: process.stderr, isSilent });
|
|
28
33
|
}
|
|
29
34
|
class Logger {
|
|
30
35
|
verbose = false;
|