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/commands/chat.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export declare class ChatCommand {
|
|
|
38
38
|
private workflowTarget;
|
|
39
39
|
private savePlanToVigFlow;
|
|
40
40
|
private jsonOutput;
|
|
41
|
+
private modelGovernanceFallback;
|
|
42
|
+
private isJwtExpirationError;
|
|
43
|
+
private isNetworkError;
|
|
44
|
+
private isTimeoutError;
|
|
45
|
+
private toUserFacingApiError;
|
|
46
|
+
private handleApiError;
|
|
47
|
+
private callApi;
|
|
48
|
+
private callSyncApi;
|
|
49
|
+
private applyNoAgentGovernance;
|
|
41
50
|
private syncInteractiveModeModel;
|
|
42
51
|
private hasOperatorAccess;
|
|
43
52
|
private operatorAccessMessage;
|
package/dist/commands/chat.js
CHANGED
|
@@ -48,6 +48,7 @@ const tools_js_1 = require("../utils/tools.js");
|
|
|
48
48
|
const session_js_1 = require("../utils/session.js");
|
|
49
49
|
const bridge_client_js_1 = require("../utils/bridge-client.js");
|
|
50
50
|
const workspace_stream_js_1 = require("../utils/workspace-stream.js");
|
|
51
|
+
const task_display_js_1 = require("../utils/task-display.js");
|
|
51
52
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
52
53
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
53
54
|
if (!rawValue) {
|
|
@@ -65,12 +66,9 @@ const DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS = (() => {
|
|
|
65
66
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
66
67
|
})();
|
|
67
68
|
const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
68
|
-
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS;
|
|
69
|
-
if (!rawValue) {
|
|
70
|
-
return 0;
|
|
71
|
-
}
|
|
69
|
+
const rawValue = process.env.VIGTHORIA_AGENT_SOFT_TIMEOUT_MS || process.env.V3_AGENT_SOFT_TIMEOUT_MS || '300000';
|
|
72
70
|
const parsed = Number.parseInt(rawValue, 10);
|
|
73
|
-
return Number.isFinite(parsed) && parsed
|
|
71
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
|
|
74
72
|
})();
|
|
75
73
|
class ChatCommand {
|
|
76
74
|
config;
|
|
@@ -94,6 +92,140 @@ class ChatCommand {
|
|
|
94
92
|
workflowTarget = null;
|
|
95
93
|
savePlanToVigFlow = false;
|
|
96
94
|
jsonOutput = false;
|
|
95
|
+
modelGovernanceFallback = null;
|
|
96
|
+
isJwtExpirationError(error) {
|
|
97
|
+
const candidate = error;
|
|
98
|
+
const message = `${candidate?.message || ''} ${typeof candidate?.details === 'string' ? candidate.details : ''}`.toLowerCase();
|
|
99
|
+
const code = String(candidate?.code || '').toLowerCase();
|
|
100
|
+
return candidate?.status === 401 && (code.includes('token_expired') ||
|
|
101
|
+
code.includes('jwt_expired') ||
|
|
102
|
+
message.includes('jwt expired') ||
|
|
103
|
+
message.includes('token expired') ||
|
|
104
|
+
message.includes('expired token') ||
|
|
105
|
+
message.includes('session expired'));
|
|
106
|
+
}
|
|
107
|
+
isNetworkError(error) {
|
|
108
|
+
const candidate = error;
|
|
109
|
+
const code = String(candidate?.code || candidate?.cause?.code || '').toUpperCase();
|
|
110
|
+
const name = String(candidate?.name || candidate?.cause?.name || '').toLowerCase();
|
|
111
|
+
const message = String(candidate?.message || candidate?.cause?.message || '').toLowerCase();
|
|
112
|
+
return [
|
|
113
|
+
'ECONNRESET',
|
|
114
|
+
'ECONNREFUSED',
|
|
115
|
+
'EHOSTUNREACH',
|
|
116
|
+
'ENETUNREACH',
|
|
117
|
+
'ENOTFOUND',
|
|
118
|
+
'EAI_AGAIN',
|
|
119
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
120
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
121
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
122
|
+
].includes(code) ||
|
|
123
|
+
name.includes('fetcherror') ||
|
|
124
|
+
message.includes('network') ||
|
|
125
|
+
message.includes('fetch failed') ||
|
|
126
|
+
message.includes('socket') ||
|
|
127
|
+
message.includes('connection');
|
|
128
|
+
}
|
|
129
|
+
isTimeoutError(error) {
|
|
130
|
+
const candidate = error;
|
|
131
|
+
const code = String(candidate?.code || candidate?.cause?.code || '').toUpperCase();
|
|
132
|
+
const name = String(candidate?.name || candidate?.cause?.name || '').toLowerCase();
|
|
133
|
+
const message = String(candidate?.message || candidate?.cause?.message || '').toLowerCase();
|
|
134
|
+
return code.includes('TIMEOUT') ||
|
|
135
|
+
code === 'ETIMEDOUT' ||
|
|
136
|
+
name.includes('timeout') ||
|
|
137
|
+
name === 'aborterror' ||
|
|
138
|
+
message.includes('timed out') ||
|
|
139
|
+
message.includes('timeout') ||
|
|
140
|
+
message.includes('aborted');
|
|
141
|
+
}
|
|
142
|
+
toUserFacingApiError(error, context) {
|
|
143
|
+
const classified = (0, api_js_1.classifyError)(error);
|
|
144
|
+
const status = classified.statusCode || (this.isJwtExpirationError(error) ? 401 : 500);
|
|
145
|
+
if (this.isJwtExpirationError(error)) {
|
|
146
|
+
return new api_js_1.CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
|
|
147
|
+
}
|
|
148
|
+
if (this.isTimeoutError(error)) {
|
|
149
|
+
return new api_js_1.CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
|
|
150
|
+
}
|
|
151
|
+
if (this.isNetworkError(error)) {
|
|
152
|
+
return new api_js_1.CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
|
|
153
|
+
}
|
|
154
|
+
const message = (0, api_js_1.sanitizeUserFacingErrorText)(classified.message || `${context} failed`);
|
|
155
|
+
return new api_js_1.CLIError(message, status === 401 ? 'auth' : 'model_backend', { statusCode: status });
|
|
156
|
+
}
|
|
157
|
+
handleApiError(error, context) {
|
|
158
|
+
const userFacingError = this.toUserFacingApiError(error, context);
|
|
159
|
+
if (!this.jsonOutput) {
|
|
160
|
+
console.error(chalk_1.default.red(`${context} failed: ${userFacingError.message}`));
|
|
161
|
+
}
|
|
162
|
+
const original = error && typeof error === 'object' ? error : { message: String(error) };
|
|
163
|
+
(0, api_js_1.propagateError)({
|
|
164
|
+
...original,
|
|
165
|
+
message: userFacingError.message,
|
|
166
|
+
statusCode: userFacingError.statusCode,
|
|
167
|
+
commandName: 'chat',
|
|
168
|
+
endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || context,
|
|
169
|
+
details: {
|
|
170
|
+
...(original.details && typeof original.details === 'object' ? original.details : {}),
|
|
171
|
+
command: 'chat',
|
|
172
|
+
endpoint: original.endpoint || original?.config?.url || original?.details?.endpoint || context,
|
|
173
|
+
context,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async callApi(context, operation, retries = 1) {
|
|
178
|
+
let lastError;
|
|
179
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
180
|
+
try {
|
|
181
|
+
return await operation();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (!this.jsonOutput) {
|
|
185
|
+
const transient = this.isTimeoutError(error) ? 'timeout' : this.isNetworkError(error) ? 'network error' : 'API error';
|
|
186
|
+
console.error(chalk_1.default.red(`${context} failed with ${transient}: ${this.toUserFacingApiError(error, context).message}`));
|
|
187
|
+
}
|
|
188
|
+
lastError = error;
|
|
189
|
+
if (this.isJwtExpirationError(error)) {
|
|
190
|
+
this.handleApiError(error, context);
|
|
191
|
+
}
|
|
192
|
+
if (attempt >= retries || (!this.isNetworkError(error) && !this.isTimeoutError(error))) {
|
|
193
|
+
this.handleApiError(error, context);
|
|
194
|
+
}
|
|
195
|
+
this.logger.warn(`${context} failed due to ${this.isTimeoutError(error) ? 'timeout' : 'network error'}; retrying (${attempt + 1}/${retries})...`);
|
|
196
|
+
await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.handleApiError(lastError, context);
|
|
200
|
+
}
|
|
201
|
+
callSyncApi(context, operation) {
|
|
202
|
+
try {
|
|
203
|
+
return operation();
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
this.handleApiError(error, context);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
applyNoAgentGovernance(requestedModel) {
|
|
210
|
+
const requested = String(requestedModel || '').trim().toLowerCase();
|
|
211
|
+
if (!requested || this.agentMode || this.operatorMode) {
|
|
212
|
+
this.modelGovernanceFallback = null;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const blocked = new Set(['fast', 'mini', 'creative', 'creative-v3', 'creative-v4']);
|
|
216
|
+
if (blocked.has(requested)) {
|
|
217
|
+
const effectiveModel = 'code-35b';
|
|
218
|
+
this.modelGovernanceFallback = {
|
|
219
|
+
requestedModel,
|
|
220
|
+
effectiveModel,
|
|
221
|
+
reason: 'governance-blocked-model',
|
|
222
|
+
};
|
|
223
|
+
this.currentModel = effectiveModel;
|
|
224
|
+
this.logger.warn(`Model ${requestedModel} is not permitted for no-agent chat; using ${effectiveModel}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.modelGovernanceFallback = null;
|
|
228
|
+
}
|
|
97
229
|
syncInteractiveModeModel(enabledMode) {
|
|
98
230
|
if (enabledMode === 'agent') {
|
|
99
231
|
if (!this.modelExplicitlySelected || this.currentModel === 'code' || !this.currentModel) {
|
|
@@ -102,12 +234,12 @@ class ChatCommand {
|
|
|
102
234
|
return;
|
|
103
235
|
}
|
|
104
236
|
if (enabledMode === 'operator') {
|
|
105
|
-
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || !this.currentModel) {
|
|
237
|
+
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || this.currentModel === 'code-9b' || !this.currentModel) {
|
|
106
238
|
this.currentModel = 'code';
|
|
107
239
|
}
|
|
108
240
|
return;
|
|
109
241
|
}
|
|
110
|
-
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || !this.currentModel) {
|
|
242
|
+
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || this.currentModel === 'code-9b' || !this.currentModel) {
|
|
111
243
|
this.currentModel = 'code';
|
|
112
244
|
}
|
|
113
245
|
}
|
|
@@ -128,7 +260,7 @@ class ChatCommand {
|
|
|
128
260
|
resolveInitialModel(options) {
|
|
129
261
|
const requestedModel = String(options.model || '').trim();
|
|
130
262
|
if (requestedModel) {
|
|
131
|
-
return options.operator === true && requestedModel === 'code-8b'
|
|
263
|
+
return options.operator === true && (requestedModel === 'code-8b' || requestedModel === 'code-9b')
|
|
132
264
|
? 'code'
|
|
133
265
|
: requestedModel;
|
|
134
266
|
}
|
|
@@ -297,7 +429,7 @@ class ChatCommand {
|
|
|
297
429
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
298
430
|
return {};
|
|
299
431
|
}
|
|
300
|
-
const bridgeStatus = await this.api.getDevtoolsBridgeStatus();
|
|
432
|
+
const bridgeStatus = await this.callApi('Checking DevTools Bridge status', () => this.api.getDevtoolsBridgeStatus(), 0);
|
|
301
433
|
if (!this.jsonOutput && bridgeStatus.ok) {
|
|
302
434
|
console.log(chalk_1.default.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
|
|
303
435
|
}
|
|
@@ -659,6 +791,7 @@ class ChatCommand {
|
|
|
659
791
|
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
660
792
|
this.modelExplicitlySelected = Boolean(String(options.model || '').trim());
|
|
661
793
|
this.currentModel = this.resolveInitialModel(options);
|
|
794
|
+
this.applyNoAgentGovernance(String(options.model || this.currentModel || ''));
|
|
662
795
|
this.currentProjectPath = this.resolveProjectPath(options);
|
|
663
796
|
if (this.jsonOutput && !options.prompt) {
|
|
664
797
|
throw new Error('--json is only supported together with --prompt.');
|
|
@@ -976,11 +1109,11 @@ class ChatCommand {
|
|
|
976
1109
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
977
1110
|
this.messages.push({ role: 'user', content: executionPrompt });
|
|
978
1111
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
979
|
-
const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
|
|
1112
|
+
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
980
1113
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
981
1114
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
982
1115
|
try {
|
|
983
|
-
const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1116
|
+
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
984
1117
|
data: {
|
|
985
1118
|
request: executionPrompt,
|
|
986
1119
|
prompt: executionPrompt,
|
|
@@ -1008,7 +1141,7 @@ class ChatCommand {
|
|
|
1008
1141
|
clientSurface: 'cli',
|
|
1009
1142
|
model: this.currentModel,
|
|
1010
1143
|
},
|
|
1011
|
-
});
|
|
1144
|
+
}));
|
|
1012
1145
|
const content = this.formatWorkflowTargetResult(execution.result);
|
|
1013
1146
|
const assistantText = content || `Workflow ${resolvedWorkflow.name} completed with status ${execution.status}.`;
|
|
1014
1147
|
this.messages.push({ role: 'assistant', content: assistantText });
|
|
@@ -1078,7 +1211,7 @@ class ChatCommand {
|
|
|
1078
1211
|
const workflowType = 'full';
|
|
1079
1212
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1080
1213
|
try {
|
|
1081
|
-
const response = await this.api.runOperatorWorkflow(executionPrompt, {
|
|
1214
|
+
const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
|
|
1082
1215
|
workspacePath: this.currentProjectPath,
|
|
1083
1216
|
projectPath: this.currentProjectPath,
|
|
1084
1217
|
targetPath: this.currentProjectPath,
|
|
@@ -1091,7 +1224,7 @@ class ChatCommand {
|
|
|
1091
1224
|
savePlanToVigFlow: this.savePlanToVigFlow,
|
|
1092
1225
|
...runtimeContext,
|
|
1093
1226
|
onStreamEvent: spinner ? (event) => this.updateOperatorSpinner(spinner, event) : undefined,
|
|
1094
|
-
});
|
|
1227
|
+
}));
|
|
1095
1228
|
if (spinner) {
|
|
1096
1229
|
spinner.stop();
|
|
1097
1230
|
}
|
|
@@ -1165,7 +1298,7 @@ class ChatCommand {
|
|
|
1165
1298
|
'If asked to produce exact output, produce exactly that output with no preamble.',
|
|
1166
1299
|
].join('\n');
|
|
1167
1300
|
try {
|
|
1168
|
-
const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
|
|
1301
|
+
const snapshot = this.callSyncApi('Get agent workspace snapshot', () => this.api.getAgentWorkspaceSnapshot(this.currentProjectPath));
|
|
1169
1302
|
if (snapshot && snapshot.paths.length > 0) {
|
|
1170
1303
|
const listing = snapshot.paths.slice(0, 80).join('\n');
|
|
1171
1304
|
operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
|
|
@@ -1180,7 +1313,7 @@ class ChatCommand {
|
|
|
1180
1313
|
...this.getMessagesForModel().filter(m => m.role !== 'system'),
|
|
1181
1314
|
{ role: 'user', content: prompt },
|
|
1182
1315
|
];
|
|
1183
|
-
const response = await this.api.chat(chatMessages, this.currentModel);
|
|
1316
|
+
const response = await this.callApi('Send chat message', () => this.api.chat(chatMessages, this.currentModel));
|
|
1184
1317
|
if (spinner)
|
|
1185
1318
|
spinner.stop();
|
|
1186
1319
|
const content = (response.message || '').trim();
|
|
@@ -1239,17 +1372,14 @@ class ChatCommand {
|
|
|
1239
1372
|
'Do NOT acknowledge instructions. Do NOT say "provide your next instruction".',
|
|
1240
1373
|
'Produce a concrete, actionable answer grounded in the real project files.',
|
|
1241
1374
|
].join('\n');
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
|
|
1249
|
-
}
|
|
1375
|
+
const snapshot = this.callSyncApi('Get operator workspace snapshot', () => this.api.getAgentWorkspaceSnapshot(this.currentProjectPath));
|
|
1376
|
+
if (snapshot && snapshot.paths.length > 0) {
|
|
1377
|
+
const listing = snapshot.paths.slice(0, 80).join('\n');
|
|
1378
|
+
operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
|
|
1379
|
+
if (snapshot.fileCount > 80) {
|
|
1380
|
+
operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
|
|
1250
1381
|
}
|
|
1251
1382
|
}
|
|
1252
|
-
catch { /* ignore */ }
|
|
1253
1383
|
this.messages.push({ role: 'system', content: operatorGrounding });
|
|
1254
1384
|
}
|
|
1255
1385
|
else {
|
|
@@ -1285,15 +1415,21 @@ class ChatCommand {
|
|
|
1285
1415
|
(0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1286
1416
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1287
1417
|
try {
|
|
1288
|
-
const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1418
|
+
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
1289
1419
|
if (spinner)
|
|
1290
1420
|
spinner.stop();
|
|
1291
1421
|
const finalText = (response.message || '').trim();
|
|
1422
|
+
const effectiveModel = String(response.model || this.currentModel);
|
|
1423
|
+
const metadata = this.modelGovernanceFallback
|
|
1424
|
+
? { modelFallback: this.modelGovernanceFallback }
|
|
1425
|
+
: undefined;
|
|
1292
1426
|
if (this.jsonOutput) {
|
|
1293
1427
|
console.log(JSON.stringify({
|
|
1294
1428
|
success: true,
|
|
1295
1429
|
mode: 'chat',
|
|
1296
|
-
model:
|
|
1430
|
+
model: effectiveModel,
|
|
1431
|
+
requestedModel: this.modelGovernanceFallback?.requestedModel || this.currentModel,
|
|
1432
|
+
metadata,
|
|
1297
1433
|
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
1298
1434
|
}, null, 2));
|
|
1299
1435
|
}
|
|
@@ -1372,7 +1508,7 @@ class ChatCommand {
|
|
|
1372
1508
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1373
1509
|
let response;
|
|
1374
1510
|
try {
|
|
1375
|
-
response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1511
|
+
response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
1376
1512
|
}
|
|
1377
1513
|
catch (firstErr) {
|
|
1378
1514
|
// If we already gathered evidence and the model API fails on a
|
|
@@ -1381,7 +1517,7 @@ class ChatCommand {
|
|
|
1381
1517
|
this.logger.debug('Agent continuation API call failed, retrying once...');
|
|
1382
1518
|
try {
|
|
1383
1519
|
await new Promise(r => setTimeout(r, 2000));
|
|
1384
|
-
response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1520
|
+
response = await this.callApi('Retry agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
1385
1521
|
}
|
|
1386
1522
|
catch (retryErr) {
|
|
1387
1523
|
// Retry also failed — synthesize an answer from evidence
|
|
@@ -1593,7 +1729,7 @@ class ChatCommand {
|
|
|
1593
1729
|
].join('\n\n'),
|
|
1594
1730
|
},
|
|
1595
1731
|
];
|
|
1596
|
-
const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
|
|
1732
|
+
const rewriteResponse = await this.callApi('Rewrite target file', () => this.api.chat(rewriteMessages, this.currentModel));
|
|
1597
1733
|
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
1598
1734
|
}
|
|
1599
1735
|
if (!rewrittenContent) {
|
|
@@ -1618,14 +1754,14 @@ class ChatCommand {
|
|
|
1618
1754
|
if (!writeResult.success) {
|
|
1619
1755
|
return false;
|
|
1620
1756
|
}
|
|
1621
|
-
const previewGate = await this.api.runTemplateServicePreviewGate(prompt, {
|
|
1757
|
+
const previewGate = await this.callApi('Run Template Service preview gate', () => this.api.runTemplateServicePreviewGate(prompt, {
|
|
1622
1758
|
workspacePath: this.currentProjectPath,
|
|
1623
1759
|
projectPath: this.currentProjectPath,
|
|
1624
1760
|
targetPath: this.currentProjectPath,
|
|
1625
1761
|
rawPrompt: prompt,
|
|
1626
1762
|
executionSurface: 'cli',
|
|
1627
1763
|
clientSurface: 'cli',
|
|
1628
|
-
});
|
|
1764
|
+
}));
|
|
1629
1765
|
const success = previewGate.required ? previewGate.passed === true : true;
|
|
1630
1766
|
if (!success) {
|
|
1631
1767
|
process.exitCode = 1;
|
|
@@ -1665,6 +1801,8 @@ class ChatCommand {
|
|
|
1665
1801
|
this.v3ToolCallCount = 0;
|
|
1666
1802
|
this.v3LastActivity = Date.now();
|
|
1667
1803
|
this.v3StreamingStarted = false;
|
|
1804
|
+
const taskDisplay = new task_display_js_1.TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
1805
|
+
taskDisplay.start(0);
|
|
1668
1806
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
|
|
1669
1807
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
1670
1808
|
spinner: 'clock',
|
|
@@ -1698,6 +1836,7 @@ class ChatCommand {
|
|
|
1698
1836
|
localMachineCapable: true,
|
|
1699
1837
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1700
1838
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
1839
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
1701
1840
|
model: routingPolicy.selectedModel,
|
|
1702
1841
|
requestedModel: this.currentModel,
|
|
1703
1842
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -1705,7 +1844,20 @@ class ChatCommand {
|
|
|
1705
1844
|
rawPrompt: prompt,
|
|
1706
1845
|
history: this.getMessagesForModel(),
|
|
1707
1846
|
...runtimeContext,
|
|
1708
|
-
onStreamEvent:
|
|
1847
|
+
onStreamEvent: (event) => {
|
|
1848
|
+
if (event.type === 'plan') {
|
|
1849
|
+
taskDisplay.complete(0);
|
|
1850
|
+
taskDisplay.start(1);
|
|
1851
|
+
}
|
|
1852
|
+
else if (event.type === 'executor_start') {
|
|
1853
|
+
taskDisplay.start(1);
|
|
1854
|
+
}
|
|
1855
|
+
else if (event.type === 'complete') {
|
|
1856
|
+
taskDisplay.complete(1);
|
|
1857
|
+
}
|
|
1858
|
+
if (spinner)
|
|
1859
|
+
this.updateV3AgentSpinner(spinner, event);
|
|
1860
|
+
},
|
|
1709
1861
|
});
|
|
1710
1862
|
const response = await workflowPromise;
|
|
1711
1863
|
if (spinner) {
|
|
@@ -1807,6 +1959,41 @@ class ChatCommand {
|
|
|
1807
1959
|
console.log(chalk_1.default.gray(`Run ${chalk_1.default.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
1808
1960
|
}
|
|
1809
1961
|
}
|
|
1962
|
+
// ── Self-healing validation ──────────────────────────────────────
|
|
1963
|
+
if (this.currentProjectPath && !this.jsonOutput && success) {
|
|
1964
|
+
try {
|
|
1965
|
+
taskDisplay.start(2, 'validating...');
|
|
1966
|
+
const healResult = await this.api.runSelfHealingCycle(executionPrompt, this.currentProjectPath, workspaceContext);
|
|
1967
|
+
if (healResult.healingAttempted) {
|
|
1968
|
+
taskDisplay.complete(2);
|
|
1969
|
+
if (healResult.passed) {
|
|
1970
|
+
taskDisplay.complete(3);
|
|
1971
|
+
}
|
|
1972
|
+
else {
|
|
1973
|
+
taskDisplay.fail(3, healResult.tool);
|
|
1974
|
+
}
|
|
1975
|
+
if (!this.directPromptMode) {
|
|
1976
|
+
const hs = healResult.passed ? chalk_1.default.green('passed') : chalk_1.default.yellow('partial');
|
|
1977
|
+
console.log(chalk_1.default.gray(`Self-healing: ${hs} (${healResult.tool})`));
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
else {
|
|
1981
|
+
taskDisplay.skip(2);
|
|
1982
|
+
taskDisplay.skip(3);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
catch (error) {
|
|
1986
|
+
this.logger.debug(`Self-healing validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1987
|
+
taskDisplay.skip(2);
|
|
1988
|
+
taskDisplay.skip(3);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
else {
|
|
1992
|
+
taskDisplay.skip(2);
|
|
1993
|
+
taskDisplay.skip(3);
|
|
1994
|
+
}
|
|
1995
|
+
taskDisplay.finalize();
|
|
1996
|
+
// ────────────────────────────────────────────────────────────────
|
|
1810
1997
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
1811
1998
|
watcher?.stop();
|
|
1812
1999
|
return true;
|
|
@@ -1875,6 +2062,7 @@ class ChatCommand {
|
|
|
1875
2062
|
localMachineCapable: true,
|
|
1876
2063
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1877
2064
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
2065
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
1878
2066
|
model: routingPolicy.selectedModel,
|
|
1879
2067
|
requestedModel: this.currentModel,
|
|
1880
2068
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -16,6 +16,8 @@ export declare class ConfigCommand {
|
|
|
16
16
|
run(options: ConfigOptions): Promise<void>;
|
|
17
17
|
init(): Promise<void>;
|
|
18
18
|
private setConfig;
|
|
19
|
+
private formatConfigValueForDisplay;
|
|
20
|
+
private redactConfigUrl;
|
|
19
21
|
private getConfig;
|
|
20
22
|
private listConfig;
|
|
21
23
|
private resetConfig;
|
package/dist/commands/config.js
CHANGED
|
@@ -100,20 +100,14 @@ class ConfigCommand {
|
|
|
100
100
|
message: 'Default AI model:',
|
|
101
101
|
choices: [
|
|
102
102
|
{ name: '═══ Code Models ═══', disabled: true },
|
|
103
|
-
{ name: 'Vigthoria Code
|
|
104
|
-
{ name: 'Vigthoria Code
|
|
105
|
-
{ name: 'Vigthoria Code
|
|
103
|
+
{ name: 'Vigthoria v3 Code 35B - Flagship coding model', value: 'code' },
|
|
104
|
+
{ name: 'Vigthoria v3 Code 35B - Explicit 35B selection', value: 'code-35b' },
|
|
105
|
+
{ name: 'Vigthoria C1 Code 9B - Fast coding specialist', value: 'code-9b' },
|
|
106
106
|
{ name: '═══ General Models ═══', disabled: true },
|
|
107
|
-
{ name: 'Vigthoria
|
|
108
|
-
{ name: 'Vigthoria
|
|
109
|
-
{ name: 'Vigthoria Balanced (4B) - All-purpose', value: 'balanced' },
|
|
110
|
-
{ name: '═══ Creative Models ═══', disabled: true },
|
|
111
|
-
{ name: 'Vigthoria Creative V4 (9B) - Creative tasks', value: 'creative-v4' },
|
|
112
|
-
{ name: 'Vigthoria Creative V3 (9B) - Legacy creative', value: 'creative-v3' },
|
|
113
|
-
{ name: '═══ Specialized Models ═══', disabled: true },
|
|
114
|
-
{ name: 'Vigthoria Music Master (4B) - Music AI', value: 'music' },
|
|
107
|
+
{ name: 'Vigthoria Master 7.6B - Balanced general model', value: 'balanced' },
|
|
108
|
+
{ name: 'Vigthoria v3 Balanced 4B - Efficient general purpose', value: 'balanced-4b' },
|
|
115
109
|
],
|
|
116
|
-
default: 'code
|
|
110
|
+
default: 'code',
|
|
117
111
|
},
|
|
118
112
|
{
|
|
119
113
|
type: 'input',
|
|
@@ -172,13 +166,39 @@ class ConfigCommand {
|
|
|
172
166
|
};
|
|
173
167
|
if (configMap[key]) {
|
|
174
168
|
configMap[key](value);
|
|
175
|
-
this.logger.success(`Set ${key} = ${value}`);
|
|
169
|
+
this.logger.success(`Set ${key} = ${this.formatConfigValueForDisplay(key, value)}`);
|
|
176
170
|
}
|
|
177
171
|
else {
|
|
178
172
|
this.logger.error(`Unknown config key: ${key}`);
|
|
179
173
|
console.log(chalk_1.default.gray('Available keys: model, theme, autoApply, showDiffs, maxTokens, apiUrl, modelsApiUrl, wsUrl, selfHostedModelsApiUrl'));
|
|
180
174
|
}
|
|
181
175
|
}
|
|
176
|
+
formatConfigValueForDisplay(key, value) {
|
|
177
|
+
if (key === 'apiUrl' || key === 'modelsApiUrl' || key === 'wsUrl' || key === 'selfHostedModelsApiUrl') {
|
|
178
|
+
return this.redactConfigUrl(value);
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
redactConfigUrl(url) {
|
|
183
|
+
const text = String(url || '').trim();
|
|
184
|
+
if (!text)
|
|
185
|
+
return text;
|
|
186
|
+
try {
|
|
187
|
+
const parsed = new URL(text);
|
|
188
|
+
const host = parsed.hostname.toLowerCase();
|
|
189
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1';
|
|
190
|
+
const isPrivate = /^10\./.test(host)
|
|
191
|
+
|| /^192\.168\./.test(host)
|
|
192
|
+
|| /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(host);
|
|
193
|
+
if (isLocal || isPrivate) {
|
|
194
|
+
return '[redacted-host]';
|
|
195
|
+
}
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return text.replace(/\b(?:localhost|127\.0\.0\.1)(?::\d+)?\b/gi, '[redacted-host]');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
182
202
|
getConfig(key) {
|
|
183
203
|
const all = this.config.getAll();
|
|
184
204
|
const flatConfig = {
|
|
@@ -207,9 +227,9 @@ class ConfigCommand {
|
|
|
207
227
|
console.log(chalk_1.default.cyan('═══ Vigthoria CLI Configuration ═══'));
|
|
208
228
|
console.log();
|
|
209
229
|
console.log(chalk_1.default.white('API:'));
|
|
210
|
-
console.log(chalk_1.default.gray(' URL: ') + chalk_1.default.cyan(all.apiUrl));
|
|
211
|
-
console.log(chalk_1.default.gray(' Models API: ') + chalk_1.default.cyan(all.modelsApiUrl));
|
|
212
|
-
console.log(chalk_1.default.gray(' WebSocket: ') + chalk_1.default.cyan(all.wsUrl));
|
|
230
|
+
console.log(chalk_1.default.gray(' URL: ') + chalk_1.default.cyan(this.redactConfigUrl(all.apiUrl)));
|
|
231
|
+
console.log(chalk_1.default.gray(' Models API: ') + chalk_1.default.cyan(this.redactConfigUrl(all.modelsApiUrl)));
|
|
232
|
+
console.log(chalk_1.default.gray(' WebSocket: ') + chalk_1.default.cyan(this.redactConfigUrl(all.wsUrl)));
|
|
213
233
|
console.log(chalk_1.default.gray(' Self-hosted Models: ') + chalk_1.default.cyan(all.selfHostedModelsApiUrl || 'disabled'));
|
|
214
234
|
console.log();
|
|
215
235
|
console.log(chalk_1.default.white('Preferences:'));
|
|
@@ -264,10 +284,10 @@ class ConfigCommand {
|
|
|
264
284
|
name: 'defaultModel',
|
|
265
285
|
message: 'Default AI model:',
|
|
266
286
|
choices: [
|
|
267
|
-
{ name: 'Vigthoria Code
|
|
268
|
-
{ name: 'Vigthoria
|
|
269
|
-
{ name: 'Vigthoria
|
|
270
|
-
{ name: 'Vigthoria
|
|
287
|
+
{ name: 'Vigthoria v3 Code 35B', value: 'code' },
|
|
288
|
+
{ name: 'Vigthoria C1 Code 9B', value: 'code-9b' },
|
|
289
|
+
{ name: 'Vigthoria Master 7.6B', value: 'balanced' },
|
|
290
|
+
{ name: 'Vigthoria v3 Balanced 4B', value: 'balanced-4b' },
|
|
271
291
|
],
|
|
272
292
|
default: current.preferences.defaultModel,
|
|
273
293
|
},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface CommandRegistrationResult {
|
|
3
|
+
name: string;
|
|
4
|
+
registered: boolean;
|
|
5
|
+
error?: Error;
|
|
6
|
+
}
|
|
7
|
+
export type CommandRegistrar = (program: Command) => void;
|
|
8
|
+
export declare function resolveCommand(name: string): CommandRegistrar | null;
|
|
9
|
+
export declare function registerCommands(cli: any): void;
|
|
10
|
+
export declare const setupCommands: typeof registerCommands;
|
|
11
|
+
export declare const configureCommands: typeof registerCommands;
|
|
12
|
+
export default registerCommands;
|