vigthoria-cli 1.8.19 → 1.9.5
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 +16 -10
- package/dist/commands/auth.d.ts +36 -18
- package/dist/commands/auth.js +440 -329
- package/dist/commands/chat.d.ts +12 -0
- package/dist/commands/chat.js +287 -48
- 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 +49 -7
- package/dist/commands/legion.js +1418 -72
- package/dist/commands/preview.js +32 -7
- package/dist/commands/repo.js +19 -13
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +235 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +472 -51
- package/dist/utils/api.d.ts +24 -9
- package/dist/utils/api.js +720 -159
- 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 +26 -0
- package/dist/utils/tools.js +563 -58
- package/dist/utils/workspace-cache.d.ts +31 -0
- package/dist/utils/workspace-cache.js +96 -0
- package/package.json +13 -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;
|
|
@@ -81,6 +90,9 @@ export declare class ChatCommand {
|
|
|
81
90
|
*/
|
|
82
91
|
private sanitizeServerPath;
|
|
83
92
|
private describeV3AgentTool;
|
|
93
|
+
private isRawV3StreamPayload;
|
|
94
|
+
private consumeV3StreamPayload;
|
|
95
|
+
private writeV3StreamText;
|
|
84
96
|
private updateV3AgentSpinner;
|
|
85
97
|
private updateOperatorSpinner;
|
|
86
98
|
constructor(config: Config, logger: Logger);
|
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
|
}
|
|
@@ -344,7 +476,67 @@ class ChatCommand {
|
|
|
344
476
|
}
|
|
345
477
|
return 'Working';
|
|
346
478
|
}
|
|
479
|
+
isRawV3StreamPayload(event) {
|
|
480
|
+
if (!event) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
if (typeof event === 'string' || Buffer.isBuffer(event) || event instanceof Uint8Array) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
const body = event?.body ?? event?.stream ?? event?.response;
|
|
487
|
+
return Boolean(body && (typeof body[Symbol.asyncIterator] === 'function' || typeof body.getReader === 'function'));
|
|
488
|
+
}
|
|
489
|
+
async consumeV3StreamPayload(spinner, event) {
|
|
490
|
+
try {
|
|
491
|
+
const source = (event && typeof event === 'object' && !Buffer.isBuffer(event) && !(event instanceof Uint8Array))
|
|
492
|
+
? (event.body ?? event.stream ?? event.response ?? event)
|
|
493
|
+
: event;
|
|
494
|
+
for await (const chunk of (0, tools_js_1.robustifyStreamResponse)(source)) {
|
|
495
|
+
this.v3LastActivity = Date.now();
|
|
496
|
+
if (chunk.type === 'error') {
|
|
497
|
+
if (spinner.isSpinning)
|
|
498
|
+
spinner.stop();
|
|
499
|
+
process.stderr.write(chalk_1.default.red(`\nStream error: ${chunk.content}\n`));
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (chunk.content) {
|
|
503
|
+
this.writeV3StreamText(spinner, chunk.content);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
509
|
+
if (spinner.isSpinning)
|
|
510
|
+
spinner.stop();
|
|
511
|
+
process.stderr.write(chalk_1.default.red(`\nStream error: ${message}\n`));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
writeV3StreamText(spinner, text) {
|
|
515
|
+
if (!text) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!this.v3StreamingStarted) {
|
|
519
|
+
this.v3StreamingStarted = true;
|
|
520
|
+
spinner.stop();
|
|
521
|
+
if (!this.directPromptMode) {
|
|
522
|
+
console.log();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
spinner.stop();
|
|
527
|
+
}
|
|
528
|
+
process.stdout.write(text);
|
|
529
|
+
}
|
|
347
530
|
updateV3AgentSpinner(spinner, event) {
|
|
531
|
+
if (this.isRawV3StreamPayload(event)) {
|
|
532
|
+
this.consumeV3StreamPayload(spinner, event).catch((error) => {
|
|
533
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
534
|
+
if (spinner.isSpinning)
|
|
535
|
+
spinner.stop();
|
|
536
|
+
process.stderr.write(chalk_1.default.red(`\nStream error: ${message}\n`));
|
|
537
|
+
});
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
348
540
|
if (!event || typeof event !== 'object') {
|
|
349
541
|
return;
|
|
350
542
|
}
|
|
@@ -411,17 +603,8 @@ class ChatCommand {
|
|
|
411
603
|
? (event.delta?.text || '')
|
|
412
604
|
: (event.content || '');
|
|
413
605
|
if (text) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
spinner.stop();
|
|
417
|
-
if (!this.directPromptMode) {
|
|
418
|
-
console.log();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
spinner.stop();
|
|
423
|
-
}
|
|
424
|
-
process.stdout.write(text);
|
|
606
|
+
this.v3LastActivity = Date.now();
|
|
607
|
+
this.writeV3StreamText(spinner, text);
|
|
425
608
|
}
|
|
426
609
|
else {
|
|
427
610
|
spinner.text = chalk_1.default.cyan('[Response] ') + 'Writing response...';
|
|
@@ -659,6 +842,7 @@ class ChatCommand {
|
|
|
659
842
|
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
660
843
|
this.modelExplicitlySelected = Boolean(String(options.model || '').trim());
|
|
661
844
|
this.currentModel = this.resolveInitialModel(options);
|
|
845
|
+
this.applyNoAgentGovernance(String(options.model || this.currentModel || ''));
|
|
662
846
|
this.currentProjectPath = this.resolveProjectPath(options);
|
|
663
847
|
if (this.jsonOutput && !options.prompt) {
|
|
664
848
|
throw new Error('--json is only supported together with --prompt.');
|
|
@@ -976,11 +1160,11 @@ class ChatCommand {
|
|
|
976
1160
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
977
1161
|
this.messages.push({ role: 'user', content: executionPrompt });
|
|
978
1162
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
979
|
-
const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
|
|
1163
|
+
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
980
1164
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
981
1165
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
982
1166
|
try {
|
|
983
|
-
const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1167
|
+
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
984
1168
|
data: {
|
|
985
1169
|
request: executionPrompt,
|
|
986
1170
|
prompt: executionPrompt,
|
|
@@ -1008,7 +1192,7 @@ class ChatCommand {
|
|
|
1008
1192
|
clientSurface: 'cli',
|
|
1009
1193
|
model: this.currentModel,
|
|
1010
1194
|
},
|
|
1011
|
-
});
|
|
1195
|
+
}));
|
|
1012
1196
|
const content = this.formatWorkflowTargetResult(execution.result);
|
|
1013
1197
|
const assistantText = content || `Workflow ${resolvedWorkflow.name} completed with status ${execution.status}.`;
|
|
1014
1198
|
this.messages.push({ role: 'assistant', content: assistantText });
|
|
@@ -1078,7 +1262,7 @@ class ChatCommand {
|
|
|
1078
1262
|
const workflowType = 'full';
|
|
1079
1263
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1080
1264
|
try {
|
|
1081
|
-
const response = await this.api.runOperatorWorkflow(executionPrompt, {
|
|
1265
|
+
const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
|
|
1082
1266
|
workspacePath: this.currentProjectPath,
|
|
1083
1267
|
projectPath: this.currentProjectPath,
|
|
1084
1268
|
targetPath: this.currentProjectPath,
|
|
@@ -1091,7 +1275,7 @@ class ChatCommand {
|
|
|
1091
1275
|
savePlanToVigFlow: this.savePlanToVigFlow,
|
|
1092
1276
|
...runtimeContext,
|
|
1093
1277
|
onStreamEvent: spinner ? (event) => this.updateOperatorSpinner(spinner, event) : undefined,
|
|
1094
|
-
});
|
|
1278
|
+
}));
|
|
1095
1279
|
if (spinner) {
|
|
1096
1280
|
spinner.stop();
|
|
1097
1281
|
}
|
|
@@ -1165,7 +1349,7 @@ class ChatCommand {
|
|
|
1165
1349
|
'If asked to produce exact output, produce exactly that output with no preamble.',
|
|
1166
1350
|
].join('\n');
|
|
1167
1351
|
try {
|
|
1168
|
-
const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
|
|
1352
|
+
const snapshot = this.callSyncApi('Get agent workspace snapshot', () => this.api.getAgentWorkspaceSnapshot(this.currentProjectPath));
|
|
1169
1353
|
if (snapshot && snapshot.paths.length > 0) {
|
|
1170
1354
|
const listing = snapshot.paths.slice(0, 80).join('\n');
|
|
1171
1355
|
operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
|
|
@@ -1180,7 +1364,7 @@ class ChatCommand {
|
|
|
1180
1364
|
...this.getMessagesForModel().filter(m => m.role !== 'system'),
|
|
1181
1365
|
{ role: 'user', content: prompt },
|
|
1182
1366
|
];
|
|
1183
|
-
const response = await this.api.chat(chatMessages, this.currentModel);
|
|
1367
|
+
const response = await this.callApi('Send chat message', () => this.api.chat(chatMessages, this.currentModel));
|
|
1184
1368
|
if (spinner)
|
|
1185
1369
|
spinner.stop();
|
|
1186
1370
|
const content = (response.message || '').trim();
|
|
@@ -1239,17 +1423,14 @@ class ChatCommand {
|
|
|
1239
1423
|
'Do NOT acknowledge instructions. Do NOT say "provide your next instruction".',
|
|
1240
1424
|
'Produce a concrete, actionable answer grounded in the real project files.',
|
|
1241
1425
|
].join('\n');
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
|
|
1249
|
-
}
|
|
1426
|
+
const snapshot = this.callSyncApi('Get operator workspace snapshot', () => this.api.getAgentWorkspaceSnapshot(this.currentProjectPath));
|
|
1427
|
+
if (snapshot && snapshot.paths.length > 0) {
|
|
1428
|
+
const listing = snapshot.paths.slice(0, 80).join('\n');
|
|
1429
|
+
operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
|
|
1430
|
+
if (snapshot.fileCount > 80) {
|
|
1431
|
+
operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
|
|
1250
1432
|
}
|
|
1251
1433
|
}
|
|
1252
|
-
catch { /* ignore */ }
|
|
1253
1434
|
this.messages.push({ role: 'system', content: operatorGrounding });
|
|
1254
1435
|
}
|
|
1255
1436
|
else {
|
|
@@ -1285,15 +1466,21 @@ class ChatCommand {
|
|
|
1285
1466
|
(0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1286
1467
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1287
1468
|
try {
|
|
1288
|
-
const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1469
|
+
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
1289
1470
|
if (spinner)
|
|
1290
1471
|
spinner.stop();
|
|
1291
1472
|
const finalText = (response.message || '').trim();
|
|
1473
|
+
const effectiveModel = String(response.model || this.currentModel);
|
|
1474
|
+
const metadata = this.modelGovernanceFallback
|
|
1475
|
+
? { modelFallback: this.modelGovernanceFallback }
|
|
1476
|
+
: undefined;
|
|
1292
1477
|
if (this.jsonOutput) {
|
|
1293
1478
|
console.log(JSON.stringify({
|
|
1294
1479
|
success: true,
|
|
1295
1480
|
mode: 'chat',
|
|
1296
|
-
model:
|
|
1481
|
+
model: effectiveModel,
|
|
1482
|
+
requestedModel: this.modelGovernanceFallback?.requestedModel || this.currentModel,
|
|
1483
|
+
metadata,
|
|
1297
1484
|
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
1298
1485
|
}, null, 2));
|
|
1299
1486
|
}
|
|
@@ -1372,7 +1559,7 @@ class ChatCommand {
|
|
|
1372
1559
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1373
1560
|
let response;
|
|
1374
1561
|
try {
|
|
1375
|
-
response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1562
|
+
response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
1376
1563
|
}
|
|
1377
1564
|
catch (firstErr) {
|
|
1378
1565
|
// If we already gathered evidence and the model API fails on a
|
|
@@ -1381,7 +1568,7 @@ class ChatCommand {
|
|
|
1381
1568
|
this.logger.debug('Agent continuation API call failed, retrying once...');
|
|
1382
1569
|
try {
|
|
1383
1570
|
await new Promise(r => setTimeout(r, 2000));
|
|
1384
|
-
response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
1571
|
+
response = await this.callApi('Retry agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
1385
1572
|
}
|
|
1386
1573
|
catch (retryErr) {
|
|
1387
1574
|
// Retry also failed — synthesize an answer from evidence
|
|
@@ -1593,7 +1780,7 @@ class ChatCommand {
|
|
|
1593
1780
|
].join('\n\n'),
|
|
1594
1781
|
},
|
|
1595
1782
|
];
|
|
1596
|
-
const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
|
|
1783
|
+
const rewriteResponse = await this.callApi('Rewrite target file', () => this.api.chat(rewriteMessages, this.currentModel));
|
|
1597
1784
|
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
1598
1785
|
}
|
|
1599
1786
|
if (!rewrittenContent) {
|
|
@@ -1618,14 +1805,14 @@ class ChatCommand {
|
|
|
1618
1805
|
if (!writeResult.success) {
|
|
1619
1806
|
return false;
|
|
1620
1807
|
}
|
|
1621
|
-
const previewGate = await this.api.runTemplateServicePreviewGate(prompt, {
|
|
1808
|
+
const previewGate = await this.callApi('Run Template Service preview gate', () => this.api.runTemplateServicePreviewGate(prompt, {
|
|
1622
1809
|
workspacePath: this.currentProjectPath,
|
|
1623
1810
|
projectPath: this.currentProjectPath,
|
|
1624
1811
|
targetPath: this.currentProjectPath,
|
|
1625
1812
|
rawPrompt: prompt,
|
|
1626
1813
|
executionSurface: 'cli',
|
|
1627
1814
|
clientSurface: 'cli',
|
|
1628
|
-
});
|
|
1815
|
+
}));
|
|
1629
1816
|
const success = previewGate.required ? previewGate.passed === true : true;
|
|
1630
1817
|
if (!success) {
|
|
1631
1818
|
process.exitCode = 1;
|
|
@@ -1665,6 +1852,8 @@ class ChatCommand {
|
|
|
1665
1852
|
this.v3ToolCallCount = 0;
|
|
1666
1853
|
this.v3LastActivity = Date.now();
|
|
1667
1854
|
this.v3StreamingStarted = false;
|
|
1855
|
+
const taskDisplay = new task_display_js_1.TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
1856
|
+
taskDisplay.start(0);
|
|
1668
1857
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
|
|
1669
1858
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
1670
1859
|
spinner: 'clock',
|
|
@@ -1698,6 +1887,7 @@ class ChatCommand {
|
|
|
1698
1887
|
localMachineCapable: true,
|
|
1699
1888
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1700
1889
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
1890
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
1701
1891
|
model: routingPolicy.selectedModel,
|
|
1702
1892
|
requestedModel: this.currentModel,
|
|
1703
1893
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -1705,7 +1895,20 @@ class ChatCommand {
|
|
|
1705
1895
|
rawPrompt: prompt,
|
|
1706
1896
|
history: this.getMessagesForModel(),
|
|
1707
1897
|
...runtimeContext,
|
|
1708
|
-
onStreamEvent:
|
|
1898
|
+
onStreamEvent: (event) => {
|
|
1899
|
+
if (event.type === 'plan') {
|
|
1900
|
+
taskDisplay.complete(0);
|
|
1901
|
+
taskDisplay.start(1);
|
|
1902
|
+
}
|
|
1903
|
+
else if (event.type === 'executor_start') {
|
|
1904
|
+
taskDisplay.start(1);
|
|
1905
|
+
}
|
|
1906
|
+
else if (event.type === 'complete') {
|
|
1907
|
+
taskDisplay.complete(1);
|
|
1908
|
+
}
|
|
1909
|
+
if (spinner)
|
|
1910
|
+
this.updateV3AgentSpinner(spinner, event);
|
|
1911
|
+
},
|
|
1709
1912
|
});
|
|
1710
1913
|
const response = await workflowPromise;
|
|
1711
1914
|
if (spinner) {
|
|
@@ -1807,6 +2010,41 @@ class ChatCommand {
|
|
|
1807
2010
|
console.log(chalk_1.default.gray(`Run ${chalk_1.default.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
1808
2011
|
}
|
|
1809
2012
|
}
|
|
2013
|
+
// ── Self-healing validation ──────────────────────────────────────
|
|
2014
|
+
if (this.currentProjectPath && !this.jsonOutput && success) {
|
|
2015
|
+
try {
|
|
2016
|
+
taskDisplay.start(2, 'validating...');
|
|
2017
|
+
const healResult = await this.api.runSelfHealingCycle(executionPrompt, this.currentProjectPath, workspaceContext);
|
|
2018
|
+
if (healResult.healingAttempted) {
|
|
2019
|
+
taskDisplay.complete(2);
|
|
2020
|
+
if (healResult.passed) {
|
|
2021
|
+
taskDisplay.complete(3);
|
|
2022
|
+
}
|
|
2023
|
+
else {
|
|
2024
|
+
taskDisplay.fail(3, healResult.tool);
|
|
2025
|
+
}
|
|
2026
|
+
if (!this.directPromptMode) {
|
|
2027
|
+
const hs = healResult.passed ? chalk_1.default.green('passed') : chalk_1.default.yellow('partial');
|
|
2028
|
+
console.log(chalk_1.default.gray(`Self-healing: ${hs} (${healResult.tool})`));
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
taskDisplay.skip(2);
|
|
2033
|
+
taskDisplay.skip(3);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
catch (error) {
|
|
2037
|
+
this.logger.debug(`Self-healing validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2038
|
+
taskDisplay.skip(2);
|
|
2039
|
+
taskDisplay.skip(3);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
taskDisplay.skip(2);
|
|
2044
|
+
taskDisplay.skip(3);
|
|
2045
|
+
}
|
|
2046
|
+
taskDisplay.finalize();
|
|
2047
|
+
// ────────────────────────────────────────────────────────────────
|
|
1810
2048
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
1811
2049
|
watcher?.stop();
|
|
1812
2050
|
return true;
|
|
@@ -1875,6 +2113,7 @@ class ChatCommand {
|
|
|
1875
2113
|
localMachineCapable: true,
|
|
1876
2114
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1877
2115
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
2116
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
1878
2117
|
model: routingPolicy.selectedModel,
|
|
1879
2118
|
requestedModel: this.currentModel,
|
|
1880
2119
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -2293,10 +2532,10 @@ class ChatCommand {
|
|
|
2293
2532
|
isProtectedFileReference(prompt, filePath) {
|
|
2294
2533
|
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2295
2534
|
const protectedPatterns = [
|
|
2296
|
-
new RegExp(`do not modify\\s+[
|
|
2297
|
-
new RegExp(`don't modify\\s+[
|
|
2298
|
-
new RegExp(`leave\\s+[
|
|
2299
|
-
new RegExp(`without modifying\\s+[
|
|
2535
|
+
new RegExp(`do not modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
2536
|
+
new RegExp(`don't modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
2537
|
+
new RegExp(`leave\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?\\s+unchanged`, 'i'),
|
|
2538
|
+
new RegExp(`without modifying\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
2300
2539
|
];
|
|
2301
2540
|
return protectedPatterns.some((pattern) => pattern.test(prompt));
|
|
2302
2541
|
}
|
|
@@ -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;
|