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/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 interceptor
197
- this.client.interceptors.request.use((req) => {
198
- const token = this.config.get('authToken');
199
- if (token) {
200
- req.headers.Authorization = `Bearer ${token}`;
201
- req.headers.Cookie = `vigthoria-auth-token=${token}`;
202
- }
203
- return req;
204
- });
205
- this.modelRouterClient.interceptors.request.use((req) => {
206
- const token = this.config.get('authToken');
207
- if (token) {
208
- req.headers.Authorization = `Bearer ${token}`;
209
- req.headers.Cookie = `vigthoria-auth-token=${token}`;
210
- }
211
- return req;
212
- });
213
- this.selfHostedModelRouterClient?.interceptors.request.use((req) => {
214
- const token = this.config.get('authToken');
215
- if (token) {
216
- req.headers.Authorization = `Bearer ${token}`;
217
- }
218
- return req;
219
- });
220
- // Add response interceptors for token refresh + structured errors
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
- if (error.response?.status === 401) {
224
- const refreshed = await this.refreshToken();
225
- if (refreshed && error.config) {
226
- return client.request(error.config);
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, 'auth');
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
- return false;
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
- // Verify auth against the Model Router (api.vigthoria.io) which is
439
- // the backend all AI commands actually use. Falls back to the Coder
440
- // profile endpoint when the Model Router is unreachable so that
441
- // offline/degraded scenarios don't block the user.
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('/api/user/profile', { timeout: 10000 });
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
- // Both unreachable don't assume token is bad
465
- return { valid: true };
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' || preferLocal;
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 = this.buildLocalWorkspaceSummary(localWorkspacePath);
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-8b'),
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.config.get('authToken');
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-30b';
3220
+ return 'vigthoria-v3-code-35b';
2856
3221
  }
2857
3222
  return null;
2858
3223
  }
2859
3224
  isCloudModelId(resolvedModel) {
2860
- return resolvedModel === 'deepseek-v3.1:671b-cloud'
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-30b',
2895
- 'vigthoria-v3-code-30b:latest',
3272
+ 'vigthoria-v3-code-35b',
3273
+ 'vigthoria-v3-code-35b:latest',
2896
3274
  'qwen3-coder:latest',
2897
- 'vigthoria-v2-code-8b',
3275
+ 'vigthoria_c1_m',
2898
3276
  ]);
2899
3277
  return selfHostedModels.has(resolvedModel)
2900
3278
  || normalizedRequested === 'agent'
2901
3279
  || normalizedRequested === 'code'
2902
- || normalizedRequested === 'code-30b'
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-30b' : resolvedModel;
3287
+ return resolvedModel === 'qwen3-coder:latest' ? 'vigthoria-v3-code-35b' : resolvedModel;
2908
3288
  }
2909
- return 'vigthoria-v3-code-30b';
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.config.get('authToken');
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.config.get('authToken');
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-30b';
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-fast-1.7b',
3739
- 'mini': 'vigthoria-mini-0.6b',
3740
- 'balanced': 'vigthoria-balanced-4b',
3741
- 'creative': 'vigthoria-creative-9b-v4',
3742
- // Code Models - 30B is the default powerhouse
3743
- 'code': 'vigthoria-v3-code-30b', // Internal: self-hosted 30B on Blackwell
3744
- 'code-30b': 'vigthoria-v3-code-30b',
3745
- 'code-8b': 'vigthoria-v2-code-8b',
3746
- 'pro': 'vigthoria-v3-code-30b',
3747
- 'agent': 'vigthoria-v3-code-30b',
3748
- 'vigthoria-code': 'vigthoria-v3-code-30b',
3749
- 'vigthoria-agent': 'vigthoria-v3-code-30b',
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-30b';
4236
+ return modelMap[shortName] || 'vigthoria-v3-code-35b';
3765
4237
  }
3766
4238
  async getCoderHealth() {
3767
4239
  try {