vigthoria-cli 1.8.15 → 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 +10 -2
- package/dist/commands/chat.js +328 -93
- 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 +506 -28
- package/dist/utils/api.d.ts +74 -18
- package/dist/utils/api.js +701 -805
- 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;
|
|
@@ -118,8 +127,7 @@ export declare class ChatCommand {
|
|
|
118
127
|
private runLocalAgentLoop;
|
|
119
128
|
private tryDirectSingleFileFlow;
|
|
120
129
|
private tryV3AgentWorkflow;
|
|
121
|
-
private
|
|
122
|
-
private tryCommandLevelSaaSRescue;
|
|
130
|
+
private tryRecoverV3ServiceAndRetry;
|
|
123
131
|
private startInteractiveChat;
|
|
124
132
|
private showHelp;
|
|
125
133
|
private showContext;
|
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
|
}
|
|
@@ -494,6 +626,43 @@ class ChatCommand {
|
|
|
494
626
|
spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
|
|
495
627
|
return;
|
|
496
628
|
}
|
|
629
|
+
if (event.type === 'executor_start') {
|
|
630
|
+
if (spinner.isSpinning)
|
|
631
|
+
spinner.stop();
|
|
632
|
+
process.stderr.write(chalk_1.default.cyan(' [Executor] ') + `Starting ${event.task_id || 'task'}${event.title ? ` - ${event.title}` : ''}
|
|
633
|
+
`);
|
|
634
|
+
spinner.start();
|
|
635
|
+
spinner.text = 'Vigthoria Executor running...';
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (event.type === 'executor_error') {
|
|
639
|
+
if (spinner.isSpinning)
|
|
640
|
+
spinner.stop();
|
|
641
|
+
const msg = (0, api_js_1.sanitizeUserFacingErrorText)(String(event.error || 'Executor error')) || 'Executor error';
|
|
642
|
+
process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
|
|
643
|
+
`);
|
|
644
|
+
spinner.start();
|
|
645
|
+
spinner.text = 'Recovering executor...';
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (event.type === 'executor_complete') {
|
|
649
|
+
if (spinner.isSpinning)
|
|
650
|
+
spinner.stop();
|
|
651
|
+
const summary = event.summary || {};
|
|
652
|
+
const status = String(summary.status || 'completed');
|
|
653
|
+
const changed = Array.isArray(summary.changed_files) ? summary.changed_files.length : 0;
|
|
654
|
+
if (status === 'failed') {
|
|
655
|
+
process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
|
|
656
|
+
`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
process.stderr.write(chalk_1.default.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
|
|
660
|
+
`);
|
|
661
|
+
}
|
|
662
|
+
spinner.start();
|
|
663
|
+
spinner.text = 'Continuing plan...';
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
497
666
|
if (event.type === 'file_mutation') {
|
|
498
667
|
const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
|
|
499
668
|
const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
|
|
@@ -516,7 +685,18 @@ class ChatCommand {
|
|
|
516
685
|
else {
|
|
517
686
|
if (spinner.isSpinning)
|
|
518
687
|
spinner.stop();
|
|
519
|
-
|
|
688
|
+
const message = (0, api_js_1.sanitizeUserFacingErrorText)(String(event.message || 'Agent error')) || 'Agent error';
|
|
689
|
+
const plannerLike = /plan|planner|dependency graph/i.test(message);
|
|
690
|
+
const executorLike = /executor|task failed|iteration/i.test(message);
|
|
691
|
+
if (plannerLike) {
|
|
692
|
+
process.stderr.write(chalk_1.default.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${message}\n`);
|
|
693
|
+
}
|
|
694
|
+
else if (executorLike) {
|
|
695
|
+
process.stderr.write(chalk_1.default.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${message}\n`);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
process.stderr.write(chalk_1.default.red(' [Error] ') + message + '\n');
|
|
699
|
+
}
|
|
520
700
|
}
|
|
521
701
|
return;
|
|
522
702
|
}
|
|
@@ -611,6 +791,7 @@ class ChatCommand {
|
|
|
611
791
|
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
612
792
|
this.modelExplicitlySelected = Boolean(String(options.model || '').trim());
|
|
613
793
|
this.currentModel = this.resolveInitialModel(options);
|
|
794
|
+
this.applyNoAgentGovernance(String(options.model || this.currentModel || ''));
|
|
614
795
|
this.currentProjectPath = this.resolveProjectPath(options);
|
|
615
796
|
if (this.jsonOutput && !options.prompt) {
|
|
616
797
|
throw new Error('--json is only supported together with --prompt.');
|
|
@@ -928,11 +1109,11 @@ class ChatCommand {
|
|
|
928
1109
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
929
1110
|
this.messages.push({ role: 'user', content: executionPrompt });
|
|
930
1111
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
931
|
-
const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
|
|
1112
|
+
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
932
1113
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
933
1114
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
934
1115
|
try {
|
|
935
|
-
const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1116
|
+
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
936
1117
|
data: {
|
|
937
1118
|
request: executionPrompt,
|
|
938
1119
|
prompt: executionPrompt,
|
|
@@ -960,7 +1141,7 @@ class ChatCommand {
|
|
|
960
1141
|
clientSurface: 'cli',
|
|
961
1142
|
model: this.currentModel,
|
|
962
1143
|
},
|
|
963
|
-
});
|
|
1144
|
+
}));
|
|
964
1145
|
const content = this.formatWorkflowTargetResult(execution.result);
|
|
965
1146
|
const assistantText = content || `Workflow ${resolvedWorkflow.name} completed with status ${execution.status}.`;
|
|
966
1147
|
this.messages.push({ role: 'assistant', content: assistantText });
|
|
@@ -1030,7 +1211,7 @@ class ChatCommand {
|
|
|
1030
1211
|
const workflowType = 'full';
|
|
1031
1212
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1032
1213
|
try {
|
|
1033
|
-
const response = await this.api.runOperatorWorkflow(executionPrompt, {
|
|
1214
|
+
const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
|
|
1034
1215
|
workspacePath: this.currentProjectPath,
|
|
1035
1216
|
projectPath: this.currentProjectPath,
|
|
1036
1217
|
targetPath: this.currentProjectPath,
|
|
@@ -1043,7 +1224,7 @@ class ChatCommand {
|
|
|
1043
1224
|
savePlanToVigFlow: this.savePlanToVigFlow,
|
|
1044
1225
|
...runtimeContext,
|
|
1045
1226
|
onStreamEvent: spinner ? (event) => this.updateOperatorSpinner(spinner, event) : undefined,
|
|
1046
|
-
});
|
|
1227
|
+
}));
|
|
1047
1228
|
if (spinner) {
|
|
1048
1229
|
spinner.stop();
|
|
1049
1230
|
}
|
|
@@ -1117,7 +1298,7 @@ class ChatCommand {
|
|
|
1117
1298
|
'If asked to produce exact output, produce exactly that output with no preamble.',
|
|
1118
1299
|
].join('\n');
|
|
1119
1300
|
try {
|
|
1120
|
-
const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
|
|
1301
|
+
const snapshot = this.callSyncApi('Get agent workspace snapshot', () => this.api.getAgentWorkspaceSnapshot(this.currentProjectPath));
|
|
1121
1302
|
if (snapshot && snapshot.paths.length > 0) {
|
|
1122
1303
|
const listing = snapshot.paths.slice(0, 80).join('\n');
|
|
1123
1304
|
operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
|
|
@@ -1132,7 +1313,7 @@ class ChatCommand {
|
|
|
1132
1313
|
...this.getMessagesForModel().filter(m => m.role !== 'system'),
|
|
1133
1314
|
{ role: 'user', content: prompt },
|
|
1134
1315
|
];
|
|
1135
|
-
const response = await this.api.chat(chatMessages, this.currentModel);
|
|
1316
|
+
const response = await this.callApi('Send chat message', () => this.api.chat(chatMessages, this.currentModel));
|
|
1136
1317
|
if (spinner)
|
|
1137
1318
|
spinner.stop();
|
|
1138
1319
|
const content = (response.message || '').trim();
|
|
@@ -1191,17 +1372,14 @@ class ChatCommand {
|
|
|
1191
1372
|
'Do NOT acknowledge instructions. Do NOT say "provide your next instruction".',
|
|
1192
1373
|
'Produce a concrete, actionable answer grounded in the real project files.',
|
|
1193
1374
|
].join('\n');
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
|
|
1201
|
-
}
|
|
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.`;
|
|
1202
1381
|
}
|
|
1203
1382
|
}
|
|
1204
|
-
catch { /* ignore */ }
|
|
1205
1383
|
this.messages.push({ role: 'system', content: operatorGrounding });
|
|
1206
1384
|
}
|
|
1207
1385
|
else {
|
|
@@ -1237,15 +1415,21 @@ class ChatCommand {
|
|
|
1237
1415
|
(0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1238
1416
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1239
1417
|
try {
|
|
1240
|
-
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));
|
|
1241
1419
|
if (spinner)
|
|
1242
1420
|
spinner.stop();
|
|
1243
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;
|
|
1244
1426
|
if (this.jsonOutput) {
|
|
1245
1427
|
console.log(JSON.stringify({
|
|
1246
1428
|
success: true,
|
|
1247
1429
|
mode: 'chat',
|
|
1248
|
-
model:
|
|
1430
|
+
model: effectiveModel,
|
|
1431
|
+
requestedModel: this.modelGovernanceFallback?.requestedModel || this.currentModel,
|
|
1432
|
+
metadata,
|
|
1249
1433
|
content: finalText || 'The model returned an empty response. Try rephrasing or use --agent for grounded file analysis.',
|
|
1250
1434
|
}, null, 2));
|
|
1251
1435
|
}
|
|
@@ -1324,7 +1508,7 @@ class ChatCommand {
|
|
|
1324
1508
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1325
1509
|
let response;
|
|
1326
1510
|
try {
|
|
1327
|
-
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);
|
|
1328
1512
|
}
|
|
1329
1513
|
catch (firstErr) {
|
|
1330
1514
|
// If we already gathered evidence and the model API fails on a
|
|
@@ -1333,7 +1517,7 @@ class ChatCommand {
|
|
|
1333
1517
|
this.logger.debug('Agent continuation API call failed, retrying once...');
|
|
1334
1518
|
try {
|
|
1335
1519
|
await new Promise(r => setTimeout(r, 2000));
|
|
1336
|
-
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);
|
|
1337
1521
|
}
|
|
1338
1522
|
catch (retryErr) {
|
|
1339
1523
|
// Retry also failed — synthesize an answer from evidence
|
|
@@ -1545,7 +1729,7 @@ class ChatCommand {
|
|
|
1545
1729
|
].join('\n\n'),
|
|
1546
1730
|
},
|
|
1547
1731
|
];
|
|
1548
|
-
const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
|
|
1732
|
+
const rewriteResponse = await this.callApi('Rewrite target file', () => this.api.chat(rewriteMessages, this.currentModel));
|
|
1549
1733
|
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
1550
1734
|
}
|
|
1551
1735
|
if (!rewrittenContent) {
|
|
@@ -1570,14 +1754,14 @@ class ChatCommand {
|
|
|
1570
1754
|
if (!writeResult.success) {
|
|
1571
1755
|
return false;
|
|
1572
1756
|
}
|
|
1573
|
-
const previewGate = await this.api.runTemplateServicePreviewGate(prompt, {
|
|
1757
|
+
const previewGate = await this.callApi('Run Template Service preview gate', () => this.api.runTemplateServicePreviewGate(prompt, {
|
|
1574
1758
|
workspacePath: this.currentProjectPath,
|
|
1575
1759
|
projectPath: this.currentProjectPath,
|
|
1576
1760
|
targetPath: this.currentProjectPath,
|
|
1577
1761
|
rawPrompt: prompt,
|
|
1578
1762
|
executionSurface: 'cli',
|
|
1579
1763
|
clientSurface: 'cli',
|
|
1580
|
-
});
|
|
1764
|
+
}));
|
|
1581
1765
|
const success = previewGate.required ? previewGate.passed === true : true;
|
|
1582
1766
|
if (!success) {
|
|
1583
1767
|
process.exitCode = 1;
|
|
@@ -1612,12 +1796,13 @@ class ChatCommand {
|
|
|
1612
1796
|
async tryV3AgentWorkflow(prompt) {
|
|
1613
1797
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1614
1798
|
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
1615
|
-
const rescueEligible = this.isSaaSRescuePrompt(prompt);
|
|
1616
1799
|
// Reset streaming counters for new workflow
|
|
1617
1800
|
this.v3IterationCount = 0;
|
|
1618
1801
|
this.v3ToolCallCount = 0;
|
|
1619
1802
|
this.v3LastActivity = Date.now();
|
|
1620
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);
|
|
1621
1806
|
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
|
|
1622
1807
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
1623
1808
|
spinner: 'clock',
|
|
@@ -1651,6 +1836,7 @@ class ChatCommand {
|
|
|
1651
1836
|
localMachineCapable: true,
|
|
1652
1837
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
1653
1838
|
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
1839
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
1654
1840
|
model: routingPolicy.selectedModel,
|
|
1655
1841
|
requestedModel: this.currentModel,
|
|
1656
1842
|
agentExecutionPolicy: routingPolicy,
|
|
@@ -1658,16 +1844,22 @@ class ChatCommand {
|
|
|
1658
1844
|
rawPrompt: prompt,
|
|
1659
1845
|
history: this.getMessagesForModel(),
|
|
1660
1846
|
...runtimeContext,
|
|
1661
|
-
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
|
+
},
|
|
1662
1861
|
});
|
|
1663
|
-
const response = await
|
|
1664
|
-
? Promise.race([
|
|
1665
|
-
workflowPromise,
|
|
1666
|
-
new Promise((_, reject) => {
|
|
1667
|
-
setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')), DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS);
|
|
1668
|
-
}),
|
|
1669
|
-
])
|
|
1670
|
-
: workflowPromise);
|
|
1862
|
+
const response = await workflowPromise;
|
|
1671
1863
|
if (spinner) {
|
|
1672
1864
|
spinner.stop();
|
|
1673
1865
|
}
|
|
@@ -1767,15 +1959,50 @@ class ChatCommand {
|
|
|
1767
1959
|
console.log(chalk_1.default.gray(`Run ${chalk_1.default.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
1768
1960
|
}
|
|
1769
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
|
+
// ────────────────────────────────────────────────────────────────
|
|
1770
1997
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
1771
1998
|
watcher?.stop();
|
|
1772
1999
|
return true;
|
|
1773
2000
|
}
|
|
1774
2001
|
catch (error) {
|
|
1775
2002
|
watcher?.stop();
|
|
1776
|
-
if (
|
|
1777
|
-
const
|
|
1778
|
-
if (
|
|
2003
|
+
if (!this.api.hasAgentWorkspaceOutput(workspaceContext)) {
|
|
2004
|
+
const recovered = await this.tryRecoverV3ServiceAndRetry(executionPrompt, prompt, workspaceContext, routingPolicy, spinner, error);
|
|
2005
|
+
if (recovered) {
|
|
1779
2006
|
return true;
|
|
1780
2007
|
}
|
|
1781
2008
|
}
|
|
@@ -1811,60 +2038,68 @@ class ChatCommand {
|
|
|
1811
2038
|
return true;
|
|
1812
2039
|
}
|
|
1813
2040
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
async tryCommandLevelSaaSRescue(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
1818
|
-
const apiAny = this.api;
|
|
1819
|
-
if (typeof apiAny.materializeEmergencySaaSWorkspace !== 'function') {
|
|
2041
|
+
async tryRecoverV3ServiceAndRetry(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
2042
|
+
const recovery = await this.api.attemptV3ServiceRecovery((error && error.message) || '');
|
|
2043
|
+
if (!recovery.recovered) {
|
|
1820
2044
|
return false;
|
|
1821
2045
|
}
|
|
1822
|
-
|
|
1823
|
-
if (!appName) {
|
|
1824
|
-
return false;
|
|
1825
|
-
}
|
|
1826
|
-
if (typeof apiAny.ensureAgentFrontendPolish === 'function') {
|
|
1827
|
-
await apiAny.ensureAgentFrontendPolish(executionPrompt, workspaceContext);
|
|
1828
|
-
}
|
|
1829
|
-
const previewGate = await this.api.runTemplateServicePreviewGate(executionPrompt, {
|
|
1830
|
-
...workspaceContext,
|
|
1831
|
-
rawPrompt,
|
|
1832
|
-
});
|
|
1833
|
-
if (spinner) {
|
|
2046
|
+
if (spinner && spinner.isSpinning) {
|
|
1834
2047
|
spinner.stop();
|
|
1835
2048
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
console.log(JSON.stringify({
|
|
1839
|
-
success: true,
|
|
1840
|
-
mode: 'agent',
|
|
1841
|
-
model: routingPolicy.selectedModel,
|
|
1842
|
-
routingPolicy,
|
|
1843
|
-
taskId: null,
|
|
1844
|
-
contextId: null,
|
|
1845
|
-
partial: true,
|
|
1846
|
-
content: rescueMessage,
|
|
1847
|
-
metadata: {
|
|
1848
|
-
source: 'cli-saas-rescue',
|
|
1849
|
-
previewGate,
|
|
1850
|
-
rescueReason: error.message,
|
|
1851
|
-
},
|
|
1852
|
-
}, null, 2));
|
|
2049
|
+
if (!this.jsonOutput) {
|
|
2050
|
+
console.log(chalk_1.default.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
1853
2051
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2052
|
+
this.v3IterationCount = 0;
|
|
2053
|
+
this.v3ToolCallCount = 0;
|
|
2054
|
+
this.v3LastActivity = Date.now();
|
|
2055
|
+
this.v3StreamingStarted = false;
|
|
2056
|
+
try {
|
|
2057
|
+
const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
|
|
2058
|
+
workspace: { path: this.currentProjectPath },
|
|
2059
|
+
...workspaceContext,
|
|
2060
|
+
executionSurface: 'cli',
|
|
2061
|
+
clientSurface: 'cli',
|
|
2062
|
+
localMachineCapable: true,
|
|
2063
|
+
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
2064
|
+
agentIdleTimeoutMs: DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
|
|
2065
|
+
agentSoftTimeoutMs: DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS,
|
|
2066
|
+
model: routingPolicy.selectedModel,
|
|
2067
|
+
requestedModel: this.currentModel,
|
|
2068
|
+
agentExecutionPolicy: routingPolicy,
|
|
2069
|
+
legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
|
|
2070
|
+
rawPrompt,
|
|
2071
|
+
history: this.getMessagesForModel(),
|
|
2072
|
+
onStreamEvent: spinner ? (event) => this.updateV3AgentSpinner(spinner, event) : undefined,
|
|
2073
|
+
});
|
|
2074
|
+
if (spinner && spinner.isSpinning) {
|
|
2075
|
+
spinner.stop();
|
|
1864
2076
|
}
|
|
2077
|
+
if (this.v3StreamingStarted) {
|
|
2078
|
+
process.stdout.write('\n');
|
|
2079
|
+
}
|
|
2080
|
+
if (this.jsonOutput) {
|
|
2081
|
+
console.log(JSON.stringify({
|
|
2082
|
+
success: true,
|
|
2083
|
+
mode: 'agent',
|
|
2084
|
+
model: routingPolicy.selectedModel,
|
|
2085
|
+
routingPolicy,
|
|
2086
|
+
taskId: retryResponse.taskId || null,
|
|
2087
|
+
contextId: retryResponse.contextId || null,
|
|
2088
|
+
partial: retryResponse.partial === true,
|
|
2089
|
+
content: retryResponse.content || 'V3 agent workflow completed after recovery.',
|
|
2090
|
+
metadata: { ...(retryResponse.metadata || {}), recoveryAttempted: true },
|
|
2091
|
+
}, null, 2));
|
|
2092
|
+
}
|
|
2093
|
+
else if (!this.v3StreamingStarted && retryResponse.content) {
|
|
2094
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2095
|
+
console.log(retryResponse.content);
|
|
2096
|
+
}
|
|
2097
|
+
this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
|
|
2098
|
+
return true;
|
|
2099
|
+
}
|
|
2100
|
+
catch {
|
|
2101
|
+
return false;
|
|
1865
2102
|
}
|
|
1866
|
-
this.messages.push({ role: 'assistant', content: rescueMessage });
|
|
1867
|
-
return true;
|
|
1868
2103
|
}
|
|
1869
2104
|
async startInteractiveChat() {
|
|
1870
2105
|
const chatTitle = this.operatorMode
|