vigthoria-cli 1.8.19 → 1.9.2
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/README.md +2 -6
- package/dist/commands/auth.d.ts +49 -21
- package/dist/commands/auth.js +385 -343
- package/dist/commands/chat.d.ts +9 -0
- package/dist/commands/chat.js +221 -33
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +40 -20
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.js +182 -0
- package/dist/commands/legion.d.ts +39 -0
- package/dist/commands/legion.js +999 -71
- package/dist/index.d.ts +3 -1
- package/dist/index.js +374 -34
- package/dist/utils/api.d.ts +61 -1
- package/dist/utils/api.js +558 -86
- package/dist/utils/config.js +9 -10
- package/dist/utils/context-ranker.d.ts +24 -0
- package/dist/utils/context-ranker.js +147 -0
- package/dist/utils/post-write-validator.d.ts +25 -0
- package/dist/utils/post-write-validator.js +138 -0
- package/dist/utils/session.d.ts +19 -0
- package/dist/utils/session.js +91 -6
- package/dist/utils/task-display.d.ts +31 -0
- package/dist/utils/task-display.js +115 -0
- package/dist/utils/tools.d.ts +15 -0
- package/dist/utils/tools.js +341 -58
- package/dist/utils/workspace-cache.d.ts +31 -0
- package/dist/utils/workspace-cache.js +96 -0
- package/package.json +7 -3
package/dist/utils/api.js
CHANGED
|
@@ -8,11 +8,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.APIClient = exports.CLIError = void 0;
|
|
11
|
+
exports.handleApiError = handleApiError;
|
|
12
|
+
exports.handleAuthError = handleAuthError;
|
|
13
|
+
exports.propagateError = propagateError;
|
|
11
14
|
exports.classifyError = classifyError;
|
|
12
15
|
exports.formatCLIError = formatCLIError;
|
|
13
16
|
exports.sanitizeUserFacingErrorText = sanitizeUserFacingErrorText;
|
|
14
17
|
exports.isServerRuntime = isServerRuntime;
|
|
15
18
|
exports.describeUpstreamStatus = describeUpstreamStatus;
|
|
19
|
+
exports.validateJwtExpiry = validateJwtExpiry;
|
|
20
|
+
exports.validateJwt = validateJwt;
|
|
21
|
+
exports.refreshJwtIfNeeded = refreshJwtIfNeeded;
|
|
22
|
+
exports.createApiClient = createApiClient;
|
|
16
23
|
const axios_1 = __importDefault(require("axios"));
|
|
17
24
|
const crypto_1 = require("crypto");
|
|
18
25
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -20,25 +27,202 @@ const https_1 = __importDefault(require("https"));
|
|
|
20
27
|
const net_1 = __importDefault(require("net"));
|
|
21
28
|
const path_1 = __importDefault(require("path"));
|
|
22
29
|
const ws_1 = __importDefault(require("ws"));
|
|
30
|
+
const logger_js_1 = require("./logger.js");
|
|
31
|
+
const context_ranker_js_1 = require("./context-ranker.js");
|
|
32
|
+
const post_write_validator_js_1 = require("./post-write-validator.js");
|
|
33
|
+
const workspace_cache_js_1 = require("./workspace-cache.js");
|
|
23
34
|
class CLIError extends Error {
|
|
24
35
|
category;
|
|
25
36
|
statusCode;
|
|
26
37
|
endpoint;
|
|
38
|
+
code;
|
|
39
|
+
details;
|
|
40
|
+
isCritical;
|
|
27
41
|
constructor(message, category, opts) {
|
|
28
42
|
super(message);
|
|
29
43
|
this.name = 'CLIError';
|
|
30
44
|
this.category = category;
|
|
31
45
|
this.statusCode = opts?.statusCode;
|
|
32
46
|
this.endpoint = opts?.endpoint;
|
|
47
|
+
this.code = category === 'auth' || category === 'repo_session' ? 'AUTH_REQUIRED' : category.toUpperCase();
|
|
48
|
+
this.details = { status: opts?.statusCode, endpoint: opts?.endpoint };
|
|
49
|
+
this.isCritical = category === 'auth' || category === 'repo_session' || category === 'model_backend' || Boolean(opts?.statusCode && opts.statusCode >= 500);
|
|
33
50
|
if (opts?.cause)
|
|
34
51
|
this.cause = opts.cause;
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
54
|
exports.CLIError = CLIError;
|
|
55
|
+
function toCliError(error, fallbackCode = 'API_ERROR', fallbackMessage = 'API request failed') {
|
|
56
|
+
if (isCliError(error))
|
|
57
|
+
return error;
|
|
58
|
+
const axErr = error;
|
|
59
|
+
const status = axErr?.response?.status;
|
|
60
|
+
const fetchStatus = typeof error?.status === 'number' ? error.status : undefined;
|
|
61
|
+
const effectiveStatus = status ?? fetchStatus;
|
|
62
|
+
const data = axErr?.response?.data;
|
|
63
|
+
const message = typeof data?.error === 'string'
|
|
64
|
+
? data.error
|
|
65
|
+
: typeof data?.message === 'string'
|
|
66
|
+
? data.message
|
|
67
|
+
: error instanceof Error && error.message
|
|
68
|
+
? error.message
|
|
69
|
+
: fallbackMessage;
|
|
70
|
+
const code = effectiveStatus === 401 || effectiveStatus === 403
|
|
71
|
+
? 'AUTH_REQUIRED'
|
|
72
|
+
: effectiveStatus && effectiveStatus >= 500
|
|
73
|
+
? 'SERVER_ERROR'
|
|
74
|
+
: /timeout|ETIMEDOUT|ESOCKETTIMEDOUT|aborted/i.test(message)
|
|
75
|
+
? 'TIMEOUT'
|
|
76
|
+
: /ECONNREFUSED|ENOTFOUND|ENETUNREACH|EAI_AGAIN|fetch failed/i.test(message)
|
|
77
|
+
? 'NETWORK_ERROR'
|
|
78
|
+
: fallbackCode;
|
|
79
|
+
return {
|
|
80
|
+
code,
|
|
81
|
+
message,
|
|
82
|
+
details: {
|
|
83
|
+
status: effectiveStatus,
|
|
84
|
+
endpoint: axErr?.config?.url || axErr?.config?.baseURL,
|
|
85
|
+
data,
|
|
86
|
+
},
|
|
87
|
+
isCritical: code === 'AUTH_REQUIRED' || code === 'AUTH_REFRESH_FAILED' || code === 'SERVER_ERROR' || fallbackCode === 'API_ERROR',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function isCliError(error) {
|
|
91
|
+
return Boolean(error && typeof error === 'object' && 'code' in error && 'message' in error && 'isCritical' in error);
|
|
92
|
+
}
|
|
93
|
+
function isRetryableApiError(error) {
|
|
94
|
+
const status = error instanceof CLIError
|
|
95
|
+
? error.statusCode
|
|
96
|
+
: typeof error.details?.status === 'number'
|
|
97
|
+
? error.details.status
|
|
98
|
+
: undefined;
|
|
99
|
+
const code = String(error.code || '').toUpperCase();
|
|
100
|
+
const message = String(error.message || '');
|
|
101
|
+
if (status === 401 || status === 403)
|
|
102
|
+
return false;
|
|
103
|
+
if (status !== undefined && status >= 500)
|
|
104
|
+
return true;
|
|
105
|
+
return code === 'TIMEOUT'
|
|
106
|
+
|| code === 'NETWORK_ERROR'
|
|
107
|
+
|| /timeout|timed out|ECONNRESET|ECONNREFUSED|ENOTFOUND|ENETUNREACH|EAI_AGAIN/i.test(message);
|
|
108
|
+
}
|
|
109
|
+
function decodeJwtPayload(token) {
|
|
110
|
+
try {
|
|
111
|
+
const parts = token.split('.');
|
|
112
|
+
if (parts.length !== 3 || !parts[1])
|
|
113
|
+
return null;
|
|
114
|
+
const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
115
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=');
|
|
116
|
+
const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
|
|
117
|
+
return decoded && typeof decoded === 'object' ? decoded : null;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function getJwtExpiresAt(token) {
|
|
124
|
+
if (!token)
|
|
125
|
+
return null;
|
|
126
|
+
const payload = decodeJwtPayload(token);
|
|
127
|
+
const exp = payload?.exp;
|
|
128
|
+
return typeof exp === 'number' && Number.isFinite(exp) ? exp * 1000 : null;
|
|
129
|
+
}
|
|
130
|
+
const JWT_EXPIRY_SKEW_MS = 30_000;
|
|
131
|
+
function isJwtUsable(token) {
|
|
132
|
+
const expiresAt = getJwtExpiresAt(token);
|
|
133
|
+
return Boolean(token && expiresAt !== null && Date.now() + JWT_EXPIRY_SKEW_MS < expiresAt);
|
|
134
|
+
}
|
|
135
|
+
function isJwtExpired(token) {
|
|
136
|
+
const expiresAt = getJwtExpiresAt(token);
|
|
137
|
+
return Boolean(token && expiresAt !== null && Date.now() + JWT_EXPIRY_SKEW_MS >= expiresAt);
|
|
138
|
+
}
|
|
139
|
+
function shouldAttemptJwtRefresh(token) {
|
|
140
|
+
return Boolean(token && (!isJwtUsable(token) || isJwtExpired(token)));
|
|
141
|
+
}
|
|
142
|
+
function wrapApiError(error, fallbackCode = 'API_ERROR', fallbackMessage = 'API request failed') {
|
|
143
|
+
const cliError = toCliError(error, fallbackCode, fallbackMessage);
|
|
144
|
+
return { ...cliError, details: { ...(cliError.details || {}), retryable: isRetryableApiError(cliError) } };
|
|
145
|
+
}
|
|
146
|
+
function handleApiError(error) {
|
|
147
|
+
return wrapApiError(error, 'API_ERROR', 'API request failed');
|
|
148
|
+
}
|
|
149
|
+
function handleAuthError(error) {
|
|
150
|
+
const cliError = toCliError(error, 'AUTH_ERROR', 'Authentication failed. Please run: vigthoria login');
|
|
151
|
+
if (cliError.code === 'AUTH_REQUIRED' || cliError.code === 'AUTH_ERROR') {
|
|
152
|
+
return { ...cliError, code: 'AUTH_REQUIRED', message: cliError.message || 'Authentication failed. Please run: vigthoria login', isCritical: true };
|
|
153
|
+
}
|
|
154
|
+
return cliError;
|
|
155
|
+
}
|
|
156
|
+
function propagateError(err) {
|
|
157
|
+
const responseData = err?.response?.data;
|
|
158
|
+
const existingDetails = err?.details && typeof err.details === 'object' ? err.details : {};
|
|
159
|
+
const status = typeof err?.response?.status === 'number'
|
|
160
|
+
? err.response.status
|
|
161
|
+
: typeof err?.status === 'number'
|
|
162
|
+
? err.status
|
|
163
|
+
: typeof err?.statusCode === 'number'
|
|
164
|
+
? err.statusCode
|
|
165
|
+
: typeof existingDetails.status === 'number'
|
|
166
|
+
? existingDetails.status
|
|
167
|
+
: typeof err?.code === 'number'
|
|
168
|
+
? err.code
|
|
169
|
+
: 500;
|
|
170
|
+
const endpoint = err?.endpoint || err?.config?.url || err?.config?.baseURL || existingDetails.endpoint || 'unknown';
|
|
171
|
+
const command = err?.commandName || err?.command || existingDetails.command || 'unknown';
|
|
172
|
+
const message = typeof responseData?.error === 'string'
|
|
173
|
+
? responseData.error
|
|
174
|
+
: typeof responseData?.message === 'string'
|
|
175
|
+
? responseData.message
|
|
176
|
+
: typeof err?.message === 'string' && err.message
|
|
177
|
+
? err.message
|
|
178
|
+
: 'API request failed';
|
|
179
|
+
const originalCode = err?.code;
|
|
180
|
+
const isAuthError = status === 401
|
|
181
|
+
|| status === 403
|
|
182
|
+
|| err?.isAuthError === true
|
|
183
|
+
|| String(originalCode || '').toUpperCase() === 'AUTH_REQUIRED';
|
|
184
|
+
const apiError = {
|
|
185
|
+
code: status,
|
|
186
|
+
message,
|
|
187
|
+
details: {
|
|
188
|
+
...existingDetails,
|
|
189
|
+
command,
|
|
190
|
+
endpoint,
|
|
191
|
+
status,
|
|
192
|
+
data: responseData ?? existingDetails.data,
|
|
193
|
+
originalCode,
|
|
194
|
+
originalName: err?.name,
|
|
195
|
+
},
|
|
196
|
+
isAuthError,
|
|
197
|
+
};
|
|
198
|
+
const logPayload = {
|
|
199
|
+
code: apiError.code,
|
|
200
|
+
message: apiError.message,
|
|
201
|
+
command,
|
|
202
|
+
endpoint,
|
|
203
|
+
status,
|
|
204
|
+
isAuthError: apiError.isAuthError,
|
|
205
|
+
};
|
|
206
|
+
try {
|
|
207
|
+
console.error('[Vigthoria API Error]', JSON.stringify(logPayload));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
console.error('[Vigthoria API Error]', logPayload);
|
|
211
|
+
}
|
|
212
|
+
throw apiError;
|
|
213
|
+
}
|
|
38
214
|
/** Classify an axios or fetch error into a structured CLIError. */
|
|
39
215
|
function classifyError(error, fallbackCategory = 'network') {
|
|
40
216
|
if (error instanceof CLIError)
|
|
41
217
|
return error;
|
|
218
|
+
const structuredApiError = error;
|
|
219
|
+
if (structuredApiError && typeof structuredApiError.code === 'number' && typeof structuredApiError.message === 'string') {
|
|
220
|
+
return new CLIError(structuredApiError.message, structuredApiError.isAuthError ? 'auth' : structuredApiError.code >= 500 ? 'model_backend' : fallbackCategory, {
|
|
221
|
+
statusCode: structuredApiError.code,
|
|
222
|
+
endpoint: typeof structuredApiError.details?.endpoint === 'string' ? structuredApiError.details.endpoint : undefined,
|
|
223
|
+
cause: error instanceof Error ? error : undefined,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
42
226
|
const axErr = error;
|
|
43
227
|
const status = axErr?.response?.status;
|
|
44
228
|
const endpoint = axErr?.config?.url || axErr?.config?.baseURL || '';
|
|
@@ -130,6 +314,114 @@ function describeUpstreamStatus(status) {
|
|
|
130
314
|
return 'Request was rejected by the service.';
|
|
131
315
|
return 'Unexpected response from service.';
|
|
132
316
|
}
|
|
317
|
+
const JWT_VALIDATE_EXPIRY_SKEW_MS = 60_000;
|
|
318
|
+
function validateJwtExpiry(token) {
|
|
319
|
+
if (!token || typeof token !== 'string')
|
|
320
|
+
return false;
|
|
321
|
+
const payload = decodeJwtPayload(token);
|
|
322
|
+
const exp = payload?.exp;
|
|
323
|
+
if (typeof exp !== 'number' || !Number.isFinite(exp))
|
|
324
|
+
return false;
|
|
325
|
+
return Date.now() + JWT_VALIDATE_EXPIRY_SKEW_MS < exp * 1000;
|
|
326
|
+
}
|
|
327
|
+
function validateJwt(token) {
|
|
328
|
+
if (!token || typeof token !== 'string')
|
|
329
|
+
return null;
|
|
330
|
+
const payload = decodeJwtPayload(token);
|
|
331
|
+
if (!payload)
|
|
332
|
+
return null;
|
|
333
|
+
try {
|
|
334
|
+
if (typeof payload.exp !== 'number' || !Number.isFinite(payload.exp))
|
|
335
|
+
return null;
|
|
336
|
+
if (!validateJwtExpiry(token))
|
|
337
|
+
return null;
|
|
338
|
+
return payload;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (process.env.VIGTHORIA_DEBUG_JWT === '1') {
|
|
342
|
+
// Avoid logging token contents; only expose parser failure class for diagnostics.
|
|
343
|
+
console.debug('Failed to validate JWT payload:', error instanceof Error ? error.message : String(error));
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function refreshJwtIfNeeded(state) {
|
|
349
|
+
const runtimeClient = state;
|
|
350
|
+
try {
|
|
351
|
+
const currentToken = runtimeClient.getAccessToken?.() || runtimeClient.config?.get('authToken') || state.token;
|
|
352
|
+
if (!currentToken) {
|
|
353
|
+
state.token = null;
|
|
354
|
+
state.expiresAt = null;
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const decodedExpiresAt = getJwtExpiresAt(currentToken);
|
|
358
|
+
state.token = currentToken;
|
|
359
|
+
state.expiresAt = decodedExpiresAt;
|
|
360
|
+
const expired = (typeof state.isExpired === 'function' ? state.isExpired() : shouldAttemptJwtRefresh(currentToken)) || decodedExpiresAt === null;
|
|
361
|
+
if (!expired) {
|
|
362
|
+
return currentToken;
|
|
363
|
+
}
|
|
364
|
+
const refreshToken = runtimeClient.getRefreshToken?.() || runtimeClient.config?.get('refreshToken');
|
|
365
|
+
if (refreshToken && runtimeClient.refreshToken) {
|
|
366
|
+
const refreshed = await runtimeClient.refreshToken().catch((error) => {
|
|
367
|
+
runtimeClient.logger?.debug?.(`JWT refresh request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
368
|
+
throw error;
|
|
369
|
+
});
|
|
370
|
+
const nextToken = runtimeClient.getAccessToken?.() || runtimeClient.config?.get('authToken') || null;
|
|
371
|
+
if (refreshed && nextToken && validateJwtExpiry(nextToken)) {
|
|
372
|
+
state.token = nextToken;
|
|
373
|
+
state.expiresAt = getJwtExpiresAt(nextToken);
|
|
374
|
+
return nextToken;
|
|
375
|
+
}
|
|
376
|
+
runtimeClient.logger?.debug?.('JWT refresh endpoint did not return a usable replacement token');
|
|
377
|
+
}
|
|
378
|
+
state.token = null;
|
|
379
|
+
state.expiresAt = null;
|
|
380
|
+
runtimeClient.config?.clearAuth();
|
|
381
|
+
throw new CLIError('Authentication token is expired or invalid. Please run: vigthoria login', 'auth');
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
if (error instanceof CLIError) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
const cliError = toCliError(error, 'AUTH_REFRESH_FAILED', 'Authentication refresh failed. Please run: vigthoria login');
|
|
388
|
+
state.token = null;
|
|
389
|
+
state.expiresAt = null;
|
|
390
|
+
runtimeClient.config?.clearAuth();
|
|
391
|
+
throw new CLIError(cliError.message, 'auth', {
|
|
392
|
+
statusCode: typeof cliError.details?.status === 'number' ? cliError.details.status : undefined,
|
|
393
|
+
endpoint: typeof cliError.details?.endpoint === 'string' ? cliError.details.endpoint : undefined,
|
|
394
|
+
cause: error instanceof Error ? error : undefined,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function createApiClient(config) {
|
|
399
|
+
const apiClient = new APIClient(config, new logger_js_1.Logger());
|
|
400
|
+
return {
|
|
401
|
+
get: async (path) => {
|
|
402
|
+
try {
|
|
403
|
+
const response = await apiClient.client.get(path);
|
|
404
|
+
return response.data;
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
propagateError(err);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
post: async (path, body) => {
|
|
411
|
+
try {
|
|
412
|
+
const response = await apiClient.client.post(path, body);
|
|
413
|
+
return response.data;
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
propagateError(err);
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
handleAuthError: (err) => {
|
|
420
|
+
const authError = handleAuthError(err);
|
|
421
|
+
throw new CLIError(authError.message, 'auth', { cause: err instanceof Error ? err : undefined });
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
133
425
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
134
426
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS || '1200000';
|
|
135
427
|
const parsed = Number.parseInt(rawValue, 10);
|
|
@@ -140,6 +432,11 @@ const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
|
140
432
|
const parsed = Number.parseInt(rawValue, 10);
|
|
141
433
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 90000;
|
|
142
434
|
})();
|
|
435
|
+
const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
436
|
+
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS || '300000';
|
|
437
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
438
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
|
|
439
|
+
})();
|
|
143
440
|
const DEFAULT_OPERATOR_TIMEOUT_MS = (() => {
|
|
144
441
|
const rawValue = process.env.VIGTHORIA_OPERATOR_TIMEOUT_MS || process.env.OPERATOR_TIMEOUT_MS || '300000';
|
|
145
442
|
const parsed = Number.parseInt(rawValue, 10);
|
|
@@ -150,6 +447,8 @@ class APIClient {
|
|
|
150
447
|
modelRouterClient;
|
|
151
448
|
selfHostedModelRouterClient;
|
|
152
449
|
config;
|
|
450
|
+
token = null;
|
|
451
|
+
expiresAt = null;
|
|
153
452
|
logger;
|
|
154
453
|
ws = null;
|
|
155
454
|
vigFlowTokens = new Map();
|
|
@@ -193,41 +492,77 @@ class APIClient {
|
|
|
193
492
|
'User-Agent': `Vigthoria-CLI/${process.env.npm_package_version || '1.6.9'}`,
|
|
194
493
|
},
|
|
195
494
|
}) : null;
|
|
196
|
-
// Add auth
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
495
|
+
// Add auth interceptors that validate JWT expiry before every request.
|
|
496
|
+
const createAuthRequestInterceptor = (client, includeCookie) => {
|
|
497
|
+
client.interceptors.request.use(async (req) => {
|
|
498
|
+
const skipAuthRefresh = Boolean(req.__skipAuthRefresh) || req.url?.includes('/api/token/refresh') || req.url?.includes('/api/login');
|
|
499
|
+
if (!skipAuthRefresh) {
|
|
500
|
+
await refreshJwtIfNeeded(this);
|
|
501
|
+
}
|
|
502
|
+
const token = this.getAccessToken();
|
|
503
|
+
if (token) {
|
|
504
|
+
const payload = validateJwt(token);
|
|
505
|
+
if (!payload && !skipAuthRefresh) {
|
|
506
|
+
throw new CLIError('Authentication token is expired or invalid. Please run: vigthoria login', 'auth', { endpoint: req.url });
|
|
507
|
+
}
|
|
508
|
+
req.headers.Authorization = `Bearer ${token}`;
|
|
509
|
+
if (includeCookie) {
|
|
510
|
+
req.headers.Cookie = `vigthoria-auth-token=${token}`;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return req;
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
createAuthRequestInterceptor(this.client, true);
|
|
517
|
+
createAuthRequestInterceptor(this.modelRouterClient, true);
|
|
518
|
+
if (this.selfHostedModelRouterClient) {
|
|
519
|
+
createAuthRequestInterceptor(this.selfHostedModelRouterClient, false);
|
|
520
|
+
}
|
|
521
|
+
// Add response interceptors for token refresh + structured errors.
|
|
221
522
|
const createAuthRetryInterceptor = (client) => {
|
|
222
523
|
client.interceptors.response.use((res) => res, async (error) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
524
|
+
const originalConfig = error.config;
|
|
525
|
+
try {
|
|
526
|
+
if (error.response?.status === 401) {
|
|
527
|
+
if (originalConfig?.__authRetry) {
|
|
528
|
+
throw new CLIError('Authentication failed after token refresh. Please run: vigthoria login', 'auth', {
|
|
529
|
+
statusCode: 401,
|
|
530
|
+
endpoint: originalConfig.url,
|
|
531
|
+
cause: error instanceof Error ? error : undefined,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const refreshedToken = await refreshJwtIfNeeded(this);
|
|
535
|
+
if (refreshedToken && originalConfig) {
|
|
536
|
+
originalConfig.__authRetry = true;
|
|
537
|
+
const token = refreshedToken;
|
|
538
|
+
if (token) {
|
|
539
|
+
originalConfig.headers = originalConfig.headers || {};
|
|
540
|
+
originalConfig.headers.Authorization = `Bearer ${token}`;
|
|
541
|
+
originalConfig.headers.Cookie = `vigthoria-auth-token=${token}`;
|
|
542
|
+
}
|
|
543
|
+
return client.request(originalConfig);
|
|
544
|
+
}
|
|
545
|
+
throw new CLIError('Authentication token expired or was rejected. Please run: vigthoria login', 'auth', {
|
|
546
|
+
statusCode: 401,
|
|
547
|
+
endpoint: originalConfig?.url,
|
|
548
|
+
cause: error instanceof Error ? error : undefined,
|
|
549
|
+
});
|
|
227
550
|
}
|
|
228
|
-
throw classifyError(error
|
|
551
|
+
throw classifyError(error);
|
|
552
|
+
}
|
|
553
|
+
catch (interceptorError) {
|
|
554
|
+
if (interceptorError instanceof CLIError) {
|
|
555
|
+
throw interceptorError;
|
|
556
|
+
}
|
|
557
|
+
const authError = error.response?.status === 401
|
|
558
|
+
? handleAuthError(interceptorError)
|
|
559
|
+
: toCliError(interceptorError, 'API_ERROR', 'API request failed');
|
|
560
|
+
throw new CLIError(authError.message, authError.code === 'AUTH_REQUIRED' ? 'auth' : 'network', {
|
|
561
|
+
statusCode: error.response?.status,
|
|
562
|
+
endpoint: originalConfig?.url,
|
|
563
|
+
cause: interceptorError instanceof Error ? interceptorError : error,
|
|
564
|
+
});
|
|
229
565
|
}
|
|
230
|
-
throw classifyError(error);
|
|
231
566
|
});
|
|
232
567
|
};
|
|
233
568
|
createAuthRetryInterceptor(this.client);
|
|
@@ -340,6 +675,7 @@ class APIClient {
|
|
|
340
675
|
});
|
|
341
676
|
return true;
|
|
342
677
|
}
|
|
678
|
+
this.logger.warn('Token validation failed: no profile endpoint or JWT identity claim matched');
|
|
343
679
|
this.config.clearAuth();
|
|
344
680
|
return false;
|
|
345
681
|
}
|
|
@@ -398,9 +734,11 @@ class APIClient {
|
|
|
398
734
|
}
|
|
399
735
|
return true;
|
|
400
736
|
}
|
|
401
|
-
catch {
|
|
737
|
+
catch (error) {
|
|
738
|
+
const cliError = toCliError(error, 'AUTH_REFRESH_FAILED', 'Failed to refresh authentication token');
|
|
739
|
+
this.logger.debug(`Token refresh failed: ${cliError.message}`);
|
|
402
740
|
this.config.clearAuth();
|
|
403
|
-
|
|
741
|
+
throw cliError;
|
|
404
742
|
}
|
|
405
743
|
}
|
|
406
744
|
async getSubscriptionStatus() {
|
|
@@ -435,22 +773,12 @@ class APIClient {
|
|
|
435
773
|
if (!token) {
|
|
436
774
|
return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
|
|
437
775
|
}
|
|
438
|
-
//
|
|
439
|
-
// the
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
await this.modelRouterClient.get('/v1/models', { timeout: 10000 });
|
|
444
|
-
return { valid: true };
|
|
445
|
-
}
|
|
446
|
-
catch (mrError) {
|
|
447
|
-
const mrAxErr = mrError;
|
|
448
|
-
if (mrAxErr.response?.status === 401 || mrAxErr.response?.status === 403) {
|
|
449
|
-
return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
|
|
450
|
-
}
|
|
451
|
-
// Model Router unreachable — try Coder profile as fallback
|
|
776
|
+
// Validate against authenticated Coder endpoints so we verify
|
|
777
|
+
// the actual Vigthoria Gateway user session state.
|
|
778
|
+
const authEndpoints = ['/api/user/profile', '/api/user/subscription'];
|
|
779
|
+
for (const endpoint of authEndpoints) {
|
|
452
780
|
try {
|
|
453
|
-
await this.client.get(
|
|
781
|
+
await this.client.get(endpoint, { timeout: 10000 });
|
|
454
782
|
return { valid: true };
|
|
455
783
|
}
|
|
456
784
|
catch (error) {
|
|
@@ -461,14 +789,19 @@ class APIClient {
|
|
|
461
789
|
if (axErr.response?.status === 401 || axErr.response?.status === 403) {
|
|
462
790
|
return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
|
|
463
791
|
}
|
|
464
|
-
//
|
|
465
|
-
|
|
792
|
+
// Try the next authenticated endpoint before deciding this is
|
|
793
|
+
// a transient network/backend issue.
|
|
794
|
+
continue;
|
|
466
795
|
}
|
|
467
796
|
}
|
|
797
|
+
// Auth endpoints unreachable — do not misclassify as invalid token.
|
|
798
|
+
return { valid: true };
|
|
468
799
|
}
|
|
469
800
|
getV3AgentBaseUrls(preferLocal = false) {
|
|
470
801
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
471
|
-
const allowLocalV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1'
|
|
802
|
+
const allowLocalV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1'
|
|
803
|
+
|| this.allowLocalServiceFallbacks()
|
|
804
|
+
|| preferLocal;
|
|
472
805
|
const urls = [
|
|
473
806
|
process.env.VIGTHORIA_V3_AGENT_URL,
|
|
474
807
|
process.env.V3_AGENT_URL,
|
|
@@ -494,7 +827,7 @@ class APIClient {
|
|
|
494
827
|
const urls = [
|
|
495
828
|
process.env.VIGTHORIA_OPERATOR_URL,
|
|
496
829
|
process.env.OPERATOR_URL,
|
|
497
|
-
'http://127.0.0.1:4009',
|
|
830
|
+
...(this.allowLocalServiceFallbacks() ? ['http://127.0.0.1:4009'] : []),
|
|
498
831
|
configuredModelsApiUrl,
|
|
499
832
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
500
833
|
return [...new Set(urls)];
|
|
@@ -507,7 +840,7 @@ class APIClient {
|
|
|
507
840
|
const urls = [
|
|
508
841
|
process.env.VIGTHORIA_MCP_URL,
|
|
509
842
|
process.env.MCP_SERVER_URL,
|
|
510
|
-
'http://127.0.0.1:4008',
|
|
843
|
+
...(this.allowLocalServiceFallbacks() ? ['http://127.0.0.1:4008'] : []),
|
|
511
844
|
configuredApiUrl,
|
|
512
845
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
513
846
|
return [...new Set(urls)];
|
|
@@ -525,8 +858,7 @@ class APIClient {
|
|
|
525
858
|
process.env.VIGFLOW_URL,
|
|
526
859
|
process.env.WORKFLOW_BUILDER_URL,
|
|
527
860
|
`${configuredApiUrl}/api/vigflow`,
|
|
528
|
-
'http://127.0.0.1:5060',
|
|
529
|
-
'http://127.0.0.1:5050',
|
|
861
|
+
...(this.allowLocalServiceFallbacks() ? ['http://127.0.0.1:5060', 'http://127.0.0.1:5050'] : []),
|
|
530
862
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
531
863
|
return [...new Set(urls)];
|
|
532
864
|
}
|
|
@@ -535,11 +867,14 @@ class APIClient {
|
|
|
535
867
|
const urls = [
|
|
536
868
|
process.env.VIGTHORIA_TEMPLATE_SERVICE_URL,
|
|
537
869
|
process.env.TEMPLATE_SERVICE_URL,
|
|
538
|
-
'http://127.0.0.1:4011',
|
|
870
|
+
...(this.allowLocalServiceFallbacks() ? ['http://127.0.0.1:4011'] : []),
|
|
539
871
|
`${configuredApiUrl}/api/template-service`,
|
|
540
872
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
541
873
|
return [...new Set(urls)];
|
|
542
874
|
}
|
|
875
|
+
allowLocalServiceFallbacks() {
|
|
876
|
+
return process.env.VIGTHORIA_ALLOW_LOCAL_SERVICES === '1' || isServerRuntime();
|
|
877
|
+
}
|
|
543
878
|
isFrontendTask(message = '', context = {}) {
|
|
544
879
|
// Never treat analysis-only tasks as frontend tasks — preview gate
|
|
545
880
|
// should not fire for read-only inspection prompts.
|
|
@@ -914,6 +1249,12 @@ class APIClient {
|
|
|
914
1249
|
'Content-Type': 'application/json',
|
|
915
1250
|
Accept: 'application/json',
|
|
916
1251
|
};
|
|
1252
|
+
try {
|
|
1253
|
+
await refreshJwtIfNeeded(this);
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
throw toCliError(error, 'AUTH_REFRESH_FAILED', 'Failed to refresh authentication token before API request');
|
|
1257
|
+
}
|
|
917
1258
|
const authToken = this.getAccessToken();
|
|
918
1259
|
if (authToken) {
|
|
919
1260
|
headers.Authorization = `Bearer ${authToken}`;
|
|
@@ -925,6 +1266,12 @@ class APIClient {
|
|
|
925
1266
|
const headers = {
|
|
926
1267
|
'Content-Type': 'application/json',
|
|
927
1268
|
};
|
|
1269
|
+
try {
|
|
1270
|
+
await refreshJwtIfNeeded(this);
|
|
1271
|
+
}
|
|
1272
|
+
catch (error) {
|
|
1273
|
+
throw toCliError(error, 'AUTH_REFRESH_FAILED', 'Failed to refresh authentication token before API request');
|
|
1274
|
+
}
|
|
928
1275
|
const authToken = this.getAccessToken();
|
|
929
1276
|
if (authToken) {
|
|
930
1277
|
headers.Authorization = `Bearer ${authToken}`;
|
|
@@ -1142,7 +1489,9 @@ class APIClient {
|
|
|
1142
1489
|
const targetPath = resolvedContext.targetPath || resolvedContext.projectPath || resolvedContext.workspacePath || resolvedContext.projectRoot || process.cwd();
|
|
1143
1490
|
const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
|
|
1144
1491
|
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
|
|
1145
|
-
const localWorkspaceSummary =
|
|
1492
|
+
const localWorkspaceSummary = resolvedContext.rawPrompt
|
|
1493
|
+
? this.buildSemanticWorkspaceSummary(localWorkspacePath, String(resolvedContext.rawPrompt))
|
|
1494
|
+
: this.buildLocalWorkspaceSummary(localWorkspacePath);
|
|
1146
1495
|
const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
|
|
1147
1496
|
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
1148
1497
|
// When the server cannot directly access the workspace (e.g. Windows
|
|
@@ -1470,6 +1819,14 @@ class APIClient {
|
|
|
1470
1819
|
* Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
|
|
1471
1820
|
*/
|
|
1472
1821
|
collectWorkspaceFileContents(rootPath, filePaths) {
|
|
1822
|
+
// Prioritise files that changed since last agent run — budget goes to them first.
|
|
1823
|
+
if (rootPath && filePaths.length > 0) {
|
|
1824
|
+
try {
|
|
1825
|
+
const { changed, unchanged } = (0, workspace_cache_js_1.getChangedFiles)(rootPath, filePaths);
|
|
1826
|
+
filePaths = [...changed, ...unchanged];
|
|
1827
|
+
}
|
|
1828
|
+
catch { /* non-fatal */ }
|
|
1829
|
+
}
|
|
1473
1830
|
const MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
1474
1831
|
const MAX_FILE_BYTES = 200 * 1024;
|
|
1475
1832
|
const BINARY_EXTENSIONS = new Set([
|
|
@@ -2335,6 +2692,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2335
2692
|
const rescueEligibleSaaS = preferLocalV3
|
|
2336
2693
|
&& /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
|
|
2337
2694
|
const timeoutMs = rescueEligibleSaaS ? Math.min(baseTimeoutMs, 210000) : baseTimeoutMs;
|
|
2695
|
+
const softTimeoutMs = executionContext.agentSoftTimeoutMs || DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS;
|
|
2338
2696
|
const maxAttempts = preferLocalV3 ? 2 : 1;
|
|
2339
2697
|
let lastErrors = [];
|
|
2340
2698
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
@@ -2369,6 +2727,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2369
2727
|
for (const baseUrl of this.getV3AgentBaseUrls(preferLocalV3)) {
|
|
2370
2728
|
const controller = new AbortController();
|
|
2371
2729
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2730
|
+
const softTimeoutId = softTimeoutMs > 0 ? setTimeout(() => controller.abort(), softTimeoutMs) : null;
|
|
2372
2731
|
try {
|
|
2373
2732
|
const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
|
|
2374
2733
|
clearTimeout(timeoutId);
|
|
@@ -2402,6 +2761,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2402
2761
|
};
|
|
2403
2762
|
const continueController = new AbortController();
|
|
2404
2763
|
const continueTimeoutId = setTimeout(() => continueController.abort(), timeoutMs);
|
|
2764
|
+
const continueSoftTimeoutId = softTimeoutMs > 0 ? setTimeout(() => continueController.abort(), softTimeoutMs) : null;
|
|
2405
2765
|
try {
|
|
2406
2766
|
const continueHeaders = await this.getV3AgentHeaders();
|
|
2407
2767
|
const continueResponse = await fetch(this.getV3AgentContinueUrl(baseUrl), {
|
|
@@ -2421,6 +2781,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2421
2781
|
}
|
|
2422
2782
|
finally {
|
|
2423
2783
|
clearTimeout(continueTimeoutId);
|
|
2784
|
+
if (continueSoftTimeoutId) {
|
|
2785
|
+
clearTimeout(continueSoftTimeoutId);
|
|
2786
|
+
}
|
|
2424
2787
|
}
|
|
2425
2788
|
}
|
|
2426
2789
|
// Use the final continuation data for workspace recovery
|
|
@@ -2475,6 +2838,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2475
2838
|
}
|
|
2476
2839
|
finally {
|
|
2477
2840
|
clearTimeout(timeoutId);
|
|
2841
|
+
if (softTimeoutId) {
|
|
2842
|
+
clearTimeout(softTimeoutId);
|
|
2843
|
+
}
|
|
2478
2844
|
}
|
|
2479
2845
|
}
|
|
2480
2846
|
lastErrors = errors;
|
|
@@ -2556,7 +2922,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2556
2922
|
workspace: { path: workspacePath },
|
|
2557
2923
|
workspace_path: workspacePath,
|
|
2558
2924
|
workspace_summary: workspaceSummary,
|
|
2559
|
-
model: this.resolveModelId(executionContext.model || 'code-
|
|
2925
|
+
model: this.resolveModelId(executionContext.model || 'code-9b'),
|
|
2560
2926
|
history: executionContext.history || [],
|
|
2561
2927
|
executionSurface: executionContext.executionSurface || 'cli',
|
|
2562
2928
|
clientSurface: executionContext.clientSurface || 'cli',
|
|
@@ -2731,7 +3097,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2731
3097
|
if (!this.shouldSkipCloudRoutes(resolvedModel)) {
|
|
2732
3098
|
try {
|
|
2733
3099
|
this.logger.debug(`Direct Vigthoria Models API: ${resolvedModel}`);
|
|
2734
|
-
const token = this.
|
|
3100
|
+
const token = this.getAccessToken();
|
|
2735
3101
|
const response = await this.modelRouterClient.post('/v1/chat/completions', {
|
|
2736
3102
|
model: resolvedModel,
|
|
2737
3103
|
messages,
|
|
@@ -2845,20 +3211,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2845
3211
|
}
|
|
2846
3212
|
getFallbackModelId(resolvedModel) {
|
|
2847
3213
|
const cloudModels = new Set([
|
|
2848
|
-
'deepseek-v3.1:671b-cloud',
|
|
2849
3214
|
'moonshotai/kimi-k2.5',
|
|
2850
3215
|
'vigthoria-cloud-pro',
|
|
2851
3216
|
'vigthoria-cloud-k2',
|
|
2852
3217
|
'vigthoria-cloud-ultra',
|
|
2853
3218
|
]);
|
|
2854
3219
|
if (cloudModels.has(resolvedModel)) {
|
|
2855
|
-
return 'vigthoria-v3-code-
|
|
3220
|
+
return 'vigthoria-v3-code-35b';
|
|
2856
3221
|
}
|
|
2857
3222
|
return null;
|
|
2858
3223
|
}
|
|
2859
3224
|
isCloudModelId(resolvedModel) {
|
|
2860
|
-
return resolvedModel === '
|
|
2861
|
-
|| resolvedModel === 'moonshotai/kimi-k2.5'
|
|
3225
|
+
return resolvedModel === 'moonshotai/kimi-k2.5'
|
|
2862
3226
|
|| resolvedModel === 'vigthoria-cloud-pro'
|
|
2863
3227
|
|| resolvedModel === 'vigthoria-cloud-k2'
|
|
2864
3228
|
|| resolvedModel === 'vigthoria-cloud-ultra';
|
|
@@ -2868,11 +3232,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2868
3232
|
}
|
|
2869
3233
|
resolvePermittedModelId(shortName) {
|
|
2870
3234
|
const resolvedModel = this.resolveModelId(shortName);
|
|
3235
|
+
const requested = String(shortName || '').toLowerCase();
|
|
2871
3236
|
if (this.isCloudModelId(resolvedModel) && !this.canUseCloudModel()) {
|
|
2872
3237
|
const fallbackModel = this.getSelfHostedFallbackModelId(resolvedModel, shortName);
|
|
2873
3238
|
this.logger.debug(`Blocked unauthorized cloud model ${shortName}; using fallback ${fallbackModel}`);
|
|
2874
3239
|
return fallbackModel;
|
|
2875
3240
|
}
|
|
3241
|
+
const blockedRequestedModels = new Set(['fast', 'mini', 'creative', 'creative-v3', 'creative-v4']);
|
|
3242
|
+
const blockedResolvedModels = new Set([
|
|
3243
|
+
'vigthoria-creative-9b-v4',
|
|
3244
|
+
'vigthoria-fast-1.7b',
|
|
3245
|
+
'vigthoria-mini-0.6b',
|
|
3246
|
+
'vigthoria_p1_m',
|
|
3247
|
+
'vigthoria_r1_s'
|
|
3248
|
+
]);
|
|
3249
|
+
if (blockedRequestedModels.has(requested) || blockedResolvedModels.has(resolvedModel)) {
|
|
3250
|
+
const fallbackModel = 'vigthoria-v3-code-35b';
|
|
3251
|
+
this.logger.warn(`Model ${shortName} is not permitted for CLI operational workflows; using ${fallbackModel}`);
|
|
3252
|
+
return fallbackModel;
|
|
3253
|
+
}
|
|
2876
3254
|
return resolvedModel;
|
|
2877
3255
|
}
|
|
2878
3256
|
shouldSimulateCloudFailure() {
|
|
@@ -2891,27 +3269,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2891
3269
|
isSelfHostedPreferredModel(resolvedModel, requestedModel) {
|
|
2892
3270
|
const normalizedRequested = String(requestedModel || '').toLowerCase();
|
|
2893
3271
|
const selfHostedModels = new Set([
|
|
2894
|
-
'vigthoria-v3-code-
|
|
2895
|
-
'vigthoria-v3-code-
|
|
3272
|
+
'vigthoria-v3-code-35b',
|
|
3273
|
+
'vigthoria-v3-code-35b:latest',
|
|
2896
3274
|
'qwen3-coder:latest',
|
|
2897
|
-
'
|
|
3275
|
+
'vigthoria_c1_m',
|
|
2898
3276
|
]);
|
|
2899
3277
|
return selfHostedModels.has(resolvedModel)
|
|
2900
3278
|
|| normalizedRequested === 'agent'
|
|
2901
3279
|
|| normalizedRequested === 'code'
|
|
2902
|
-
|| normalizedRequested === 'code-
|
|
3280
|
+
|| normalizedRequested === 'code-35b'
|
|
3281
|
+
|| normalizedRequested === 'code-35b'
|
|
3282
|
+
|| normalizedRequested === 'code-9b'
|
|
2903
3283
|
|| normalizedRequested === 'pro';
|
|
2904
3284
|
}
|
|
2905
3285
|
getSelfHostedFallbackModelId(resolvedModel, requestedModel) {
|
|
2906
3286
|
if (this.isSelfHostedPreferredModel(resolvedModel, requestedModel)) {
|
|
2907
|
-
return resolvedModel === 'qwen3-coder:latest' ? 'vigthoria-v3-code-
|
|
3287
|
+
return resolvedModel === 'qwen3-coder:latest' ? 'vigthoria-v3-code-35b' : resolvedModel;
|
|
2908
3288
|
}
|
|
2909
|
-
return 'vigthoria-v3-code-
|
|
3289
|
+
return 'vigthoria-v3-code-35b';
|
|
2910
3290
|
}
|
|
2911
3291
|
// Streaming chat
|
|
2912
3292
|
async *chatStream(messages, model) {
|
|
2913
3293
|
const wsUrl = this.config.get('wsUrl');
|
|
2914
|
-
const token = this.
|
|
3294
|
+
const token = this.getAccessToken();
|
|
2915
3295
|
return new Promise((resolve, reject) => {
|
|
2916
3296
|
const ws = new ws_1.default(`${wsUrl}/chat`, {
|
|
2917
3297
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -2940,7 +3320,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2940
3320
|
// Non-streaming alternative with callback
|
|
2941
3321
|
async chatWithCallback(messages, model, onChunk, onDone, onError) {
|
|
2942
3322
|
const wsUrl = this.config.get('wsUrl');
|
|
2943
|
-
const token = this.
|
|
3323
|
+
const token = this.getAccessToken();
|
|
2944
3324
|
return new Promise((resolve, reject) => {
|
|
2945
3325
|
const ws = new ws_1.default(`${wsUrl}/chat`, {
|
|
2946
3326
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -2989,7 +3369,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2989
3369
|
// (/v1/chat/completions on api.vigthoria.io) which is the only
|
|
2990
3370
|
// backend that reliably accepts our auth token.
|
|
2991
3371
|
async chatComplete(systemPrompt, userPrompt, model, maxTokens) {
|
|
2992
|
-
const resolvedModel = model ? this.resolvePermittedModelId(model) : 'vigthoria-v3-code-
|
|
3372
|
+
const resolvedModel = model ? this.resolvePermittedModelId(model) : 'vigthoria-v3-code-35b';
|
|
2993
3373
|
const response = await this.modelRouterClient.post('/v1/chat/completions', {
|
|
2994
3374
|
model: resolvedModel,
|
|
2995
3375
|
messages: [
|
|
@@ -3730,23 +4110,116 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3730
4110
|
}
|
|
3731
4111
|
// Model resolution - maps Vigthoria model names to internal IDs
|
|
3732
4112
|
// INTERNAL USE ONLY - users see only Vigthoria branding
|
|
4113
|
+
/**
|
|
4114
|
+
* Build workspace summary re-ordered by semantic relevance to the prompt.
|
|
4115
|
+
* Changed files are listed first, then keyword-matched files, then the rest.
|
|
4116
|
+
* Falls back to plain buildLocalWorkspaceSummary when no prompt is provided.
|
|
4117
|
+
*/
|
|
4118
|
+
buildSemanticWorkspaceSummary(workspacePath, prompt) {
|
|
4119
|
+
const summary = this.buildLocalWorkspaceSummary(workspacePath);
|
|
4120
|
+
if (!summary?.workspaceFiles || !prompt)
|
|
4121
|
+
return summary;
|
|
4122
|
+
try {
|
|
4123
|
+
const { topFiles } = (0, context_ranker_js_1.buildSemanticContext)(workspacePath, prompt, 15);
|
|
4124
|
+
if (topFiles.length === 0)
|
|
4125
|
+
return summary;
|
|
4126
|
+
const prioritySet = new Set(topFiles.map(f => f.path));
|
|
4127
|
+
const allFiles = summary.workspaceFiles;
|
|
4128
|
+
const reordered = {};
|
|
4129
|
+
// Semantically ranked files first
|
|
4130
|
+
for (const f of topFiles) {
|
|
4131
|
+
if (allFiles[f.path] !== undefined)
|
|
4132
|
+
reordered[f.path] = allFiles[f.path];
|
|
4133
|
+
}
|
|
4134
|
+
// Remaining files after priority set
|
|
4135
|
+
for (const [p, c] of Object.entries(allFiles)) {
|
|
4136
|
+
if (!prioritySet.has(p))
|
|
4137
|
+
reordered[p] = c;
|
|
4138
|
+
}
|
|
4139
|
+
return { ...summary, workspaceFiles: reordered };
|
|
4140
|
+
}
|
|
4141
|
+
catch {
|
|
4142
|
+
return summary;
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
/**
|
|
4146
|
+
* Self-healing cycle: run post-write validators and, if errors are found,
|
|
4147
|
+
* send a targeted correction prompt to the V3 agent (max one healing round).
|
|
4148
|
+
*
|
|
4149
|
+
* This is a best-effort operation — failures never propagate to the user as
|
|
4150
|
+
* hard errors; they are surfaced as a status line in the terminal output.
|
|
4151
|
+
*/
|
|
4152
|
+
async runSelfHealingCycle(originalPrompt, workspacePath, context = {}) {
|
|
4153
|
+
// Guard: don't heal analysis tasks or recursive healing rounds
|
|
4154
|
+
if (context._isHealingRound || this.isAnalysisOnlyTask(originalPrompt, context)) {
|
|
4155
|
+
return { healingAttempted: false, passed: true, tool: 'none' };
|
|
4156
|
+
}
|
|
4157
|
+
let validations = [];
|
|
4158
|
+
try {
|
|
4159
|
+
validations = await (0, post_write_validator_js_1.runPostWriteValidation)(workspacePath);
|
|
4160
|
+
}
|
|
4161
|
+
catch {
|
|
4162
|
+
return { healingAttempted: false, passed: true, tool: 'none' };
|
|
4163
|
+
}
|
|
4164
|
+
const failures = validations.filter(v => v.ran && !v.passed);
|
|
4165
|
+
if (failures.length === 0) {
|
|
4166
|
+
// All validators passed — update cache to reflect current state
|
|
4167
|
+
try {
|
|
4168
|
+
const { getAgentWorkspaceSnapshot } = this;
|
|
4169
|
+
if (typeof getAgentWorkspaceSnapshot === 'function') {
|
|
4170
|
+
const snap = getAgentWorkspaceSnapshot.call(this, workspacePath);
|
|
4171
|
+
if (snap?.paths?.length > 0)
|
|
4172
|
+
(0, workspace_cache_js_1.updateWorkspaceCache)(workspacePath, snap.paths);
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
catch { /* non-fatal */ }
|
|
4176
|
+
return { healingAttempted: false, passed: true, tool: 'none' };
|
|
4177
|
+
}
|
|
4178
|
+
const errorText = (0, post_write_validator_js_1.formatValidationErrors)(failures);
|
|
4179
|
+
const healPrompt = `The code you just generated has the following validation errors. Fix ONLY these errors — do not change anything else:
|
|
4180
|
+
|
|
4181
|
+
${errorText}
|
|
4182
|
+
|
|
4183
|
+
Apply the minimum change needed to make the validator pass.`;
|
|
4184
|
+
try {
|
|
4185
|
+
await this.runV3AgentWorkflow(healPrompt, {
|
|
4186
|
+
...context,
|
|
4187
|
+
workspacePath,
|
|
4188
|
+
projectPath: workspacePath,
|
|
4189
|
+
targetPath: workspacePath,
|
|
4190
|
+
agentTaskType: 'debugging',
|
|
4191
|
+
agentTimeoutMs: 90_000,
|
|
4192
|
+
_isHealingRound: true,
|
|
4193
|
+
});
|
|
4194
|
+
// Re-run validators to check healing success
|
|
4195
|
+
const recheck = await (0, post_write_validator_js_1.runPostWriteValidation)(workspacePath);
|
|
4196
|
+
const passed = recheck.filter(r => r.ran).every(r => r.passed);
|
|
4197
|
+
return { healingAttempted: true, passed, tool: failures.map(f => f.tool).join('+') };
|
|
4198
|
+
}
|
|
4199
|
+
catch {
|
|
4200
|
+
return { healingAttempted: true, passed: false, tool: failures.map(f => f.tool).join('+') };
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
3733
4203
|
resolveModelId(shortName) {
|
|
3734
4204
|
const modelMap = {
|
|
3735
4205
|
// ═══════════════════════════════════════════════════════════════
|
|
3736
4206
|
// VIGTHORIA LOCAL - Self-hosted models
|
|
3737
4207
|
// ═══════════════════════════════════════════════════════════════
|
|
3738
|
-
'fast': 'vigthoria-
|
|
3739
|
-
'mini': 'vigthoria-
|
|
3740
|
-
'balanced': '
|
|
3741
|
-
'
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
'code
|
|
3745
|
-
'code-
|
|
3746
|
-
'
|
|
3747
|
-
'
|
|
3748
|
-
'
|
|
3749
|
-
'
|
|
4208
|
+
'fast': 'vigthoria-v3-code-35b',
|
|
4209
|
+
'mini': 'vigthoria-v3-code-35b',
|
|
4210
|
+
'balanced': 'vigthoria_master',
|
|
4211
|
+
'balanced-4b': 'vigthoria-balanced-4b',
|
|
4212
|
+
'creative': 'vigthoria-v3-code-35b',
|
|
4213
|
+
// Code models
|
|
4214
|
+
'code': 'vigthoria-v3-code-35b',
|
|
4215
|
+
'code-30b': 'vigthoria-v3-code-35b',
|
|
4216
|
+
'code-35b': 'vigthoria-v3-code-35b',
|
|
4217
|
+
'code-8b': 'vigthoria_c1_m',
|
|
4218
|
+
'code-9b': 'vigthoria_c1_m',
|
|
4219
|
+
'pro': 'vigthoria-v3-code-35b',
|
|
4220
|
+
'agent': 'vigthoria-v3-code-35b',
|
|
4221
|
+
'vigthoria-code': 'vigthoria-v3-code-35b',
|
|
4222
|
+
'vigthoria-agent': 'vigthoria-v3-code-35b',
|
|
3750
4223
|
// ═══════════════════════════════════════════════════════════════
|
|
3751
4224
|
// VIGTHORIA CLOUD - Premium cloud models (internal routing)
|
|
3752
4225
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -3754,14 +4227,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3754
4227
|
'cloud-reason': 'vigthoria-cloud-k2',
|
|
3755
4228
|
'ultra': 'vigthoria-cloud-ultra',
|
|
3756
4229
|
};
|
|
3757
|
-
// If already a full model ID, return as-is
|
|
3758
4230
|
if (shortName.includes('vigthoria') || shortName.includes('/') || shortName.includes(':')) {
|
|
3759
4231
|
if (modelMap[shortName]) {
|
|
3760
4232
|
return modelMap[shortName];
|
|
3761
4233
|
}
|
|
3762
4234
|
return shortName;
|
|
3763
4235
|
}
|
|
3764
|
-
return modelMap[shortName] || 'vigthoria-v3-code-
|
|
4236
|
+
return modelMap[shortName] || 'vigthoria-v3-code-35b';
|
|
3765
4237
|
}
|
|
3766
4238
|
async getCoderHealth() {
|
|
3767
4239
|
try {
|