vigthoria-cli 1.6.8 → 1.6.13
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 +1 -1
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +34 -2
- package/dist/commands/bridge.d.ts +7 -0
- package/dist/commands/bridge.js +31 -0
- package/dist/commands/chat.d.ts +50 -3
- package/dist/commands/chat.js +1033 -70
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -1
- package/dist/commands/deploy.d.ts +0 -1
- package/dist/commands/deploy.js +0 -1
- package/dist/commands/edit.d.ts +0 -1
- package/dist/commands/edit.js +0 -1
- package/dist/commands/explain.d.ts +0 -1
- package/dist/commands/explain.js +0 -1
- package/dist/commands/generate.d.ts +0 -1
- package/dist/commands/generate.js +0 -1
- package/dist/commands/hub.d.ts +0 -1
- package/dist/commands/hub.js +0 -1
- package/dist/commands/repo.d.ts +0 -1
- package/dist/commands/repo.js +0 -1
- package/dist/commands/review.d.ts +0 -1
- package/dist/commands/review.js +0 -1
- package/dist/commands/workflow.d.ts +31 -0
- package/dist/commands/workflow.js +140 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +137 -17
- package/dist/utils/api.d.ts +172 -2
- package/dist/utils/api.js +2046 -69
- package/dist/utils/config.d.ts +14 -7
- package/dist/utils/config.js +27 -12
- package/dist/utils/files.d.ts +0 -1
- package/dist/utils/files.js +0 -1
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -1
- package/dist/utils/session.d.ts +2 -2
- package/dist/utils/session.js +2 -2
- package/dist/utils/tools.d.ts +0 -1
- package/dist/utils/tools.js +33 -7
- package/package.json +21 -3
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/deploy.d.ts.map +0 -1
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/edit.d.ts.map +0 -1
- package/dist/commands/edit.js.map +0 -1
- package/dist/commands/explain.d.ts.map +0 -1
- package/dist/commands/explain.js.map +0 -1
- package/dist/commands/generate.d.ts.map +0 -1
- package/dist/commands/generate.js.map +0 -1
- package/dist/commands/hub.d.ts.map +0 -1
- package/dist/commands/hub.js.map +0 -1
- package/dist/commands/repo.d.ts.map +0 -1
- package/dist/commands/repo.js.map +0 -1
- package/dist/commands/review.d.ts.map +0 -1
- package/dist/commands/review.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/api.d.ts.map +0 -1
- package/dist/utils/api.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/files.d.ts.map +0 -1
- package/dist/utils/files.js.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js.map +0 -1
- package/dist/utils/tools.d.ts.map +0 -1
- package/dist/utils/tools.js.map +0 -1
package/dist/commands/chat.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.ChatCommand = void 0;
|
|
|
40
40
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
41
|
const ora_1 = __importDefault(require("ora"));
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const readline = __importStar(require("readline"));
|
|
45
46
|
const api_js_1 = require("../utils/api.js");
|
|
@@ -64,7 +65,103 @@ class ChatCommand {
|
|
|
64
65
|
directPromptMode = false;
|
|
65
66
|
directToolContinuationCount = 0;
|
|
66
67
|
currentModel = 'code';
|
|
68
|
+
modelExplicitlySelected = false;
|
|
67
69
|
autoApprove = false;
|
|
70
|
+
operatorMode = false;
|
|
71
|
+
workflowTarget = null;
|
|
72
|
+
savePlanToVigFlow = false;
|
|
73
|
+
jsonOutput = false;
|
|
74
|
+
syncInteractiveModeModel(enabledMode) {
|
|
75
|
+
if (enabledMode === 'agent') {
|
|
76
|
+
if (!this.modelExplicitlySelected || this.currentModel === 'code' || !this.currentModel) {
|
|
77
|
+
this.currentModel = 'agent';
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (enabledMode === 'operator') {
|
|
82
|
+
if (this.currentModel === 'agent' || !this.currentModel) {
|
|
83
|
+
this.currentModel = 'code-8b';
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || !this.currentModel) {
|
|
88
|
+
this.currentModel = 'code';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
hasOperatorAccess() {
|
|
92
|
+
return this.config.hasOperatorAccess();
|
|
93
|
+
}
|
|
94
|
+
operatorAccessMessage() {
|
|
95
|
+
const currentPlan = this.config.get('subscription').plan || 'free';
|
|
96
|
+
return `Operator mode requires Enterprise or admin access. Current plan: ${currentPlan}.`;
|
|
97
|
+
}
|
|
98
|
+
getDefaultChatModel() {
|
|
99
|
+
const preferredModel = String(this.config.get('preferences').defaultModel || '').trim().toLowerCase();
|
|
100
|
+
if (!preferredModel || preferredModel === 'vigthoria-code' || preferredModel === 'vigthoria-agent') {
|
|
101
|
+
return 'code';
|
|
102
|
+
}
|
|
103
|
+
return preferredModel;
|
|
104
|
+
}
|
|
105
|
+
resolveInitialModel(options) {
|
|
106
|
+
const requestedModel = String(options.model || '').trim();
|
|
107
|
+
if (requestedModel) {
|
|
108
|
+
return requestedModel;
|
|
109
|
+
}
|
|
110
|
+
if (options.operator === true) {
|
|
111
|
+
return 'code-8b';
|
|
112
|
+
}
|
|
113
|
+
if (options.agent === true) {
|
|
114
|
+
return 'agent';
|
|
115
|
+
}
|
|
116
|
+
return this.getDefaultChatModel();
|
|
117
|
+
}
|
|
118
|
+
isLegacyAgentFallbackAllowed() {
|
|
119
|
+
return process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '1';
|
|
120
|
+
}
|
|
121
|
+
resolveAgentExecutionPolicy(prompt) {
|
|
122
|
+
const explicitModel = this.modelExplicitlySelected;
|
|
123
|
+
const heavyTask = this.config.isComplexTask(prompt);
|
|
124
|
+
const cloudEligible = this.config.hasCloudAccess() && this.hasOperatorAccess();
|
|
125
|
+
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
126
|
+
if (explicitModel) {
|
|
127
|
+
return {
|
|
128
|
+
selectedModel: this.currentModel,
|
|
129
|
+
explicitModel: true,
|
|
130
|
+
heavyTask,
|
|
131
|
+
cloudEligible,
|
|
132
|
+
cloudSelected: this.currentModel === 'cloud' || this.currentModel === 'cloud-reason' || this.currentModel === 'ultra',
|
|
133
|
+
routeReason: 'explicit-model-selection',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (requiresV3Workflow) {
|
|
137
|
+
return {
|
|
138
|
+
selectedModel: 'agent',
|
|
139
|
+
explicitModel: false,
|
|
140
|
+
heavyTask,
|
|
141
|
+
cloudEligible,
|
|
142
|
+
cloudSelected: false,
|
|
143
|
+
routeReason: 'v3-required-frontend-task',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (this.config.shouldUseCloudForHeavyTask(prompt)) {
|
|
147
|
+
return {
|
|
148
|
+
selectedModel: 'cloud',
|
|
149
|
+
explicitModel: false,
|
|
150
|
+
heavyTask: true,
|
|
151
|
+
cloudEligible: true,
|
|
152
|
+
cloudSelected: true,
|
|
153
|
+
routeReason: 'heavy-enterprise-task',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
selectedModel: 'agent',
|
|
158
|
+
explicitModel: false,
|
|
159
|
+
heavyTask,
|
|
160
|
+
cloudEligible,
|
|
161
|
+
cloudSelected: false,
|
|
162
|
+
routeReason: 'default-v3-agent',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
68
165
|
getMessagesForModel() {
|
|
69
166
|
const memoryContext = this.sessionManager.buildMemoryContext(this.currentSession);
|
|
70
167
|
const messages = [...this.messages];
|
|
@@ -90,29 +187,105 @@ class ChatCommand {
|
|
|
90
187
|
isDiagnosticPrompt(prompt) {
|
|
91
188
|
return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
|
|
92
189
|
}
|
|
190
|
+
isBrowserTaskPrompt(prompt) {
|
|
191
|
+
return /(browser|chrome|devtools|console|dom|network tab|network request|frontend runtime|client-side|client side|rendering|page load|websocket|ui bug|inspect element)/i.test(prompt);
|
|
192
|
+
}
|
|
93
193
|
inferAgentTaskType(prompt) {
|
|
94
194
|
return this.isDiagnosticPrompt(prompt) ? 'debugging' : 'implementation';
|
|
95
195
|
}
|
|
96
196
|
buildTaskShapingInstructions(prompt) {
|
|
97
|
-
|
|
197
|
+
const instructions = [];
|
|
198
|
+
if (this.isDiagnosticPrompt(prompt)) {
|
|
199
|
+
instructions.push('Diagnostic mode is active.', 'Treat this as a debugging task, not a generic code review or feature request.', 'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.', 'If log files exist, inspect them before proposing fixes.', 'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.', 'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.', 'Prefer grep plus read_file around the exact references involved in the failure.', 'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.', 'Do not suggest speculative fixes when the current evidence contradicts them.');
|
|
200
|
+
}
|
|
201
|
+
if (this.isBrowserTaskPrompt(prompt)) {
|
|
202
|
+
instructions.push('Browser-debug mode is active.', 'Prefer concrete browser evidence such as console errors, network failures, DOM state, and websocket behavior.', 'If a DevTools Bridge is available, use it as the primary browser observability path.');
|
|
203
|
+
}
|
|
204
|
+
if (instructions.length === 0) {
|
|
98
205
|
return '';
|
|
99
206
|
}
|
|
100
|
-
return
|
|
101
|
-
'Diagnostic mode is active.',
|
|
102
|
-
'Treat this as a debugging task, not a generic code review or feature request.',
|
|
103
|
-
'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.',
|
|
104
|
-
'If log files exist, inspect them before proposing fixes.',
|
|
105
|
-
'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.',
|
|
106
|
-
'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.',
|
|
107
|
-
'Prefer grep plus read_file around the exact references involved in the failure.',
|
|
108
|
-
'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.',
|
|
109
|
-
'Do not suggest speculative fixes when the current evidence contradicts them.',
|
|
110
|
-
].join('\n');
|
|
207
|
+
return instructions.join('\n');
|
|
111
208
|
}
|
|
112
209
|
buildExecutionPrompt(prompt) {
|
|
113
210
|
const shaping = this.buildTaskShapingInstructions(prompt);
|
|
114
211
|
return shaping ? `${prompt}\n\n${shaping}` : prompt;
|
|
115
212
|
}
|
|
213
|
+
async getPromptRuntimeContext(prompt) {
|
|
214
|
+
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
215
|
+
return {};
|
|
216
|
+
}
|
|
217
|
+
const bridgeStatus = await this.api.getDevtoolsBridgeStatus();
|
|
218
|
+
if (!this.jsonOutput && bridgeStatus.ok) {
|
|
219
|
+
console.log(chalk_1.default.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
|
|
220
|
+
}
|
|
221
|
+
else if (!this.jsonOutput) {
|
|
222
|
+
console.log(chalk_1.default.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
browserTask: true,
|
|
226
|
+
devtoolsBridgeAvailable: bridgeStatus.ok,
|
|
227
|
+
devtoolsBridgeEndpoint: bridgeStatus.endpoint,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
describeV3AgentTool(toolName) {
|
|
231
|
+
const normalized = String(toolName || '').toLowerCase();
|
|
232
|
+
if (/read|grep|search|list|find|glob/.test(normalized)) {
|
|
233
|
+
return 'Planning... gathering project context';
|
|
234
|
+
}
|
|
235
|
+
if (/write|edit|patch|create|delete|replace|rename/.test(normalized)) {
|
|
236
|
+
return 'Applying changes...';
|
|
237
|
+
}
|
|
238
|
+
if (/test|lint|build|run|exec|terminal/.test(normalized)) {
|
|
239
|
+
return 'Validating changes...';
|
|
240
|
+
}
|
|
241
|
+
return 'Working...';
|
|
242
|
+
}
|
|
243
|
+
updateV3AgentSpinner(spinner, event) {
|
|
244
|
+
if (!event || typeof event !== 'object') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (event.type === 'tool_call') {
|
|
248
|
+
spinner.text = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (event.type === 'message') {
|
|
252
|
+
spinner.text = 'Writing response...';
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (event.type === 'complete') {
|
|
256
|
+
spinner.text = 'Finishing...';
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (event.type === 'plan' || event.type === 'analysis' || event.type === 'thinking') {
|
|
260
|
+
spinner.text = 'Planning...';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
updateOperatorSpinner(spinner, event) {
|
|
264
|
+
if (!event || typeof event !== 'object') {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (event.type === 'started') {
|
|
268
|
+
spinner.text = 'Starting BMAD workflow...';
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (event.type === 'connected') {
|
|
272
|
+
spinner.text = 'Connected to BMAD stream...';
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (event.type === 'agent') {
|
|
276
|
+
spinner.text = `Running ${event.agent || 'BMAD agent'}...`;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (event.type === 'status') {
|
|
280
|
+
const progress = Number.isFinite(Number(event.progress)) ? ` (${event.progress}%)` : '';
|
|
281
|
+
spinner.text = `${event.status || 'Working'}${progress}`;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (event.type === 'result') {
|
|
285
|
+
spinner.text = 'Finishing operator workflow...';
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
116
289
|
constructor(config, logger) {
|
|
117
290
|
this.config = config;
|
|
118
291
|
this.logger = logger;
|
|
@@ -125,14 +298,33 @@ class ChatCommand {
|
|
|
125
298
|
return;
|
|
126
299
|
}
|
|
127
300
|
this.agentMode = options.agent === true;
|
|
128
|
-
this.
|
|
129
|
-
this.
|
|
130
|
-
|
|
301
|
+
this.operatorMode = options.operator === true;
|
|
302
|
+
this.workflowTarget = typeof options.workflow === 'string' && options.workflow.trim()
|
|
303
|
+
? options.workflow.trim()
|
|
304
|
+
: null;
|
|
305
|
+
this.savePlanToVigFlow = options.savePlan === true;
|
|
306
|
+
this.jsonOutput = options.json === true;
|
|
307
|
+
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
308
|
+
this.modelExplicitlySelected = Boolean(String(options.model || '').trim());
|
|
309
|
+
this.currentModel = this.resolveInitialModel(options);
|
|
310
|
+
this.currentProjectPath = this.resolveProjectPath(options);
|
|
311
|
+
if (this.jsonOutput && !options.prompt) {
|
|
312
|
+
throw new Error('--json is only supported together with --prompt.');
|
|
313
|
+
}
|
|
131
314
|
this.ensureProjectWorkspace();
|
|
132
315
|
this.directPromptMode = Boolean(options.prompt);
|
|
133
316
|
this.directToolContinuationCount = 0;
|
|
134
317
|
this.tools = new tools_js_1.AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
|
|
135
318
|
this.initializeSession(options.resume === true);
|
|
319
|
+
if (this.operatorMode && !this.hasOperatorAccess()) {
|
|
320
|
+
if (this.currentSession) {
|
|
321
|
+
this.currentSession.operatorMode = false;
|
|
322
|
+
this.saveSession();
|
|
323
|
+
}
|
|
324
|
+
this.operatorMode = false;
|
|
325
|
+
this.logger.error(this.operatorAccessMessage());
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
136
328
|
if (options.prompt) {
|
|
137
329
|
await this.handleDirectPrompt(options.prompt);
|
|
138
330
|
return;
|
|
@@ -148,30 +340,270 @@ class ChatCommand {
|
|
|
148
340
|
}
|
|
149
341
|
fs.mkdirSync(this.currentProjectPath, { recursive: true });
|
|
150
342
|
}
|
|
343
|
+
resolveProjectPath(options) {
|
|
344
|
+
const requestedProject = String(options.project || '').trim();
|
|
345
|
+
if (requestedProject) {
|
|
346
|
+
return path.resolve(requestedProject);
|
|
347
|
+
}
|
|
348
|
+
if (this.shouldUseManagedWorkspace(options)) {
|
|
349
|
+
const rootPath = this.getManagedWorkspaceRoot();
|
|
350
|
+
fs.mkdirSync(rootPath, { recursive: true });
|
|
351
|
+
const folderName = this.resolveManagedWorkspaceName(options);
|
|
352
|
+
const projectPath = this.allocateManagedWorkspacePath(rootPath, folderName);
|
|
353
|
+
this.config.set('project', {
|
|
354
|
+
...this.config.get('project'),
|
|
355
|
+
rootPath,
|
|
356
|
+
});
|
|
357
|
+
return projectPath;
|
|
358
|
+
}
|
|
359
|
+
return process.cwd();
|
|
360
|
+
}
|
|
361
|
+
shouldUseManagedWorkspace(options) {
|
|
362
|
+
if (options.projectProvided) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
if (options.newProject) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
return Boolean(options.prompt) && (options.agent === true || options.operator === true);
|
|
369
|
+
}
|
|
370
|
+
getManagedWorkspaceRoot() {
|
|
371
|
+
const envRoot = String(process.env.VIGTHORIA_DEFAULT_WORKSPACE_ROOT || '').trim();
|
|
372
|
+
const configuredRoot = String(this.config.get('project').rootPath || '').trim();
|
|
373
|
+
return path.resolve(envRoot || configuredRoot || path.join(os.homedir(), 'VigthoriaProjects'));
|
|
374
|
+
}
|
|
375
|
+
resolveManagedWorkspaceName(options) {
|
|
376
|
+
const explicitName = typeof options.newProject === 'string' ? options.newProject.trim() : '';
|
|
377
|
+
if (explicitName) {
|
|
378
|
+
return this.slugifyWorkspaceName(explicitName);
|
|
379
|
+
}
|
|
380
|
+
if (options.prompt) {
|
|
381
|
+
const fromPrompt = this.slugifyWorkspaceName(options.prompt.split(/[.!?\n]/)[0] || 'project');
|
|
382
|
+
if (fromPrompt) {
|
|
383
|
+
return fromPrompt;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return `project-${new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14)}`;
|
|
387
|
+
}
|
|
388
|
+
slugifyWorkspaceName(input) {
|
|
389
|
+
return String(input || '')
|
|
390
|
+
.toLowerCase()
|
|
391
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
392
|
+
.replace(/^-+|-+$/g, '')
|
|
393
|
+
.slice(0, 48) || 'project';
|
|
394
|
+
}
|
|
395
|
+
allocateManagedWorkspacePath(rootPath, folderName) {
|
|
396
|
+
const basePath = path.join(rootPath, folderName);
|
|
397
|
+
if (!fs.existsSync(basePath)) {
|
|
398
|
+
return basePath;
|
|
399
|
+
}
|
|
400
|
+
const existingEntries = fs.readdirSync(basePath);
|
|
401
|
+
if (existingEntries.length === 0) {
|
|
402
|
+
return basePath;
|
|
403
|
+
}
|
|
404
|
+
let counter = 2;
|
|
405
|
+
while (counter < 1000) {
|
|
406
|
+
const candidate = path.join(rootPath, `${folderName}-${counter}`);
|
|
407
|
+
if (!fs.existsSync(candidate)) {
|
|
408
|
+
return candidate;
|
|
409
|
+
}
|
|
410
|
+
counter += 1;
|
|
411
|
+
}
|
|
412
|
+
return path.join(rootPath, `${folderName}-${Date.now()}`);
|
|
413
|
+
}
|
|
151
414
|
initializeSession(resume) {
|
|
152
415
|
if (resume) {
|
|
153
416
|
this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
|
|
154
417
|
if (this.currentSession) {
|
|
155
418
|
this.messages = [...this.currentSession.messages];
|
|
156
419
|
this.agentMode = this.currentSession.agentMode || this.agentMode;
|
|
420
|
+
this.operatorMode = this.currentSession.operatorMode || this.operatorMode;
|
|
157
421
|
this.currentModel = this.currentSession.model || this.currentModel;
|
|
158
422
|
return;
|
|
159
423
|
}
|
|
160
424
|
}
|
|
161
|
-
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode);
|
|
425
|
+
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
|
|
162
426
|
this.messages = [...this.currentSession.messages];
|
|
163
427
|
}
|
|
164
428
|
async handleDirectPrompt(prompt) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
429
|
+
if (!this.jsonOutput) {
|
|
430
|
+
console.log(chalk_1.default.cyan('Running single prompt in direct mode.'));
|
|
431
|
+
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
432
|
+
console.log(chalk_1.default.gray(`Project: ${this.currentProjectPath}`));
|
|
433
|
+
if (this.workflowTarget) {
|
|
434
|
+
console.log(chalk_1.default.gray(`Workflow target: ${this.workflowTarget}`));
|
|
435
|
+
}
|
|
436
|
+
console.log();
|
|
437
|
+
}
|
|
438
|
+
if (this.workflowTarget) {
|
|
439
|
+
await this.runWorkflowTargetPrompt(prompt);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
169
442
|
if (this.agentMode) {
|
|
170
443
|
await this.runAgentTurn(prompt);
|
|
171
444
|
return;
|
|
172
445
|
}
|
|
446
|
+
if (this.operatorMode) {
|
|
447
|
+
if (!this.hasOperatorAccess()) {
|
|
448
|
+
this.logger.error(this.operatorAccessMessage());
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
await this.runOperatorTurn(prompt);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
173
454
|
await this.runSimplePrompt(prompt);
|
|
174
455
|
}
|
|
456
|
+
formatWorkflowTargetResult(result) {
|
|
457
|
+
if (typeof result === 'string') {
|
|
458
|
+
return result.trim();
|
|
459
|
+
}
|
|
460
|
+
if (result == null) {
|
|
461
|
+
return '';
|
|
462
|
+
}
|
|
463
|
+
if (typeof result !== 'object') {
|
|
464
|
+
return String(result);
|
|
465
|
+
}
|
|
466
|
+
const entries = Object.entries(result)
|
|
467
|
+
.filter(([, value]) => typeof value === 'string' && String(value).trim());
|
|
468
|
+
if (entries.length === 1) {
|
|
469
|
+
return String(entries[0][1]).trim();
|
|
470
|
+
}
|
|
471
|
+
if (entries.length > 1) {
|
|
472
|
+
return entries
|
|
473
|
+
.map(([key, value]) => `${key}:\n${String(value).trim()}`)
|
|
474
|
+
.join('\n\n');
|
|
475
|
+
}
|
|
476
|
+
const serialized = JSON.stringify(result, null, 2);
|
|
477
|
+
return serialized === '{}' ? '' : serialized;
|
|
478
|
+
}
|
|
479
|
+
async runWorkflowTargetPrompt(prompt) {
|
|
480
|
+
const selector = this.workflowTarget;
|
|
481
|
+
if (!selector) {
|
|
482
|
+
throw new Error('Workflow target is not configured.');
|
|
483
|
+
}
|
|
484
|
+
this.lastActionableUserInput = prompt;
|
|
485
|
+
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
486
|
+
this.messages.push({ role: 'user', content: executionPrompt });
|
|
487
|
+
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
488
|
+
const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
|
|
489
|
+
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
490
|
+
const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
491
|
+
try {
|
|
492
|
+
const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
493
|
+
data: {
|
|
494
|
+
request: executionPrompt,
|
|
495
|
+
prompt: executionPrompt,
|
|
496
|
+
rawPrompt: prompt,
|
|
497
|
+
workspacePath: this.currentProjectPath,
|
|
498
|
+
projectPath: this.currentProjectPath,
|
|
499
|
+
targetPath: this.currentProjectPath,
|
|
500
|
+
model: this.currentModel,
|
|
501
|
+
history: this.getMessagesForModel(),
|
|
502
|
+
executionSurface: invocationMode,
|
|
503
|
+
clientSurface: 'cli',
|
|
504
|
+
workflowTarget: {
|
|
505
|
+
id: resolvedWorkflow.id,
|
|
506
|
+
name: resolvedWorkflow.name,
|
|
507
|
+
selector: resolvedWorkflow.selector,
|
|
508
|
+
matchedBy: resolvedWorkflow.matchedBy,
|
|
509
|
+
},
|
|
510
|
+
...runtimeContext,
|
|
511
|
+
},
|
|
512
|
+
executionOptions: {
|
|
513
|
+
targetType: 'vigflow-workflow',
|
|
514
|
+
targetSelector: resolvedWorkflow.selector,
|
|
515
|
+
targetMatchedBy: resolvedWorkflow.matchedBy,
|
|
516
|
+
executionSurface: invocationMode,
|
|
517
|
+
clientSurface: 'cli',
|
|
518
|
+
model: this.currentModel,
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
const content = this.formatWorkflowTargetResult(execution.result);
|
|
522
|
+
const assistantText = content || `Workflow ${resolvedWorkflow.name} completed with status ${execution.status}.`;
|
|
523
|
+
this.messages.push({ role: 'assistant', content: assistantText });
|
|
524
|
+
this.saveSession();
|
|
525
|
+
if (spinner) {
|
|
526
|
+
spinner.stop();
|
|
527
|
+
}
|
|
528
|
+
if (this.jsonOutput) {
|
|
529
|
+
console.log(JSON.stringify({
|
|
530
|
+
success: true,
|
|
531
|
+
workflow: resolvedWorkflow,
|
|
532
|
+
mode: invocationMode,
|
|
533
|
+
execution,
|
|
534
|
+
content,
|
|
535
|
+
}, null, 2));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
539
|
+
console.log(chalk_1.default.gray(`Workflow ID: ${resolvedWorkflow.id}`));
|
|
540
|
+
console.log(chalk_1.default.gray(`Execution ID: ${execution.executionId}`));
|
|
541
|
+
console.log(chalk_1.default.gray(`Mode: ${invocationMode}`));
|
|
542
|
+
if (content) {
|
|
543
|
+
console.log(content);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
if (spinner) {
|
|
548
|
+
spinner.fail('Workflow target execution failed');
|
|
549
|
+
}
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async runOperatorTurn(prompt) {
|
|
554
|
+
if (!this.hasOperatorAccess()) {
|
|
555
|
+
this.logger.error(this.operatorAccessMessage());
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
559
|
+
const spinner = this.jsonOutput ? null : (0, ora_1.default)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
560
|
+
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
561
|
+
const workflowType = this.isDiagnosticPrompt(prompt) ? 'analysis_only' : 'full_autonomy';
|
|
562
|
+
try {
|
|
563
|
+
const response = await this.api.runOperatorWorkflow(executionPrompt, {
|
|
564
|
+
workspacePath: this.currentProjectPath,
|
|
565
|
+
projectPath: this.currentProjectPath,
|
|
566
|
+
targetPath: this.currentProjectPath,
|
|
567
|
+
rawPrompt: prompt,
|
|
568
|
+
executionSurface: 'cli',
|
|
569
|
+
clientSurface: 'cli',
|
|
570
|
+
history: this.getMessagesForModel(),
|
|
571
|
+
workflowType,
|
|
572
|
+
model: this.currentModel,
|
|
573
|
+
savePlanToVigFlow: this.savePlanToVigFlow,
|
|
574
|
+
...runtimeContext,
|
|
575
|
+
onStreamEvent: spinner ? (event) => this.updateOperatorSpinner(spinner, event) : undefined,
|
|
576
|
+
});
|
|
577
|
+
if (spinner) {
|
|
578
|
+
spinner.stop();
|
|
579
|
+
}
|
|
580
|
+
if (this.jsonOutput) {
|
|
581
|
+
console.log(JSON.stringify({
|
|
582
|
+
success: true,
|
|
583
|
+
mode: 'operator',
|
|
584
|
+
workflowId: response.workflowId,
|
|
585
|
+
savedWorkflow: response.savedWorkflow || null,
|
|
586
|
+
contextId: response.contextId || null,
|
|
587
|
+
content: response.content || 'Operator workflow completed.',
|
|
588
|
+
metadata: response.metadata || {},
|
|
589
|
+
}, null, 2));
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
console.log(response.content || 'Operator workflow completed.');
|
|
593
|
+
if (response.savedWorkflow?.id) {
|
|
594
|
+
console.log(chalk_1.default.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
this.messages.push({ role: 'assistant', content: response.content || 'Operator workflow completed.' });
|
|
598
|
+
this.saveSession();
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
if (spinner) {
|
|
602
|
+
spinner.fail('Operator workflow failed');
|
|
603
|
+
}
|
|
604
|
+
this.logger.error(error.message);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
175
607
|
async runSimplePrompt(prompt) {
|
|
176
608
|
this.lastActionableUserInput = prompt;
|
|
177
609
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
@@ -195,16 +627,34 @@ class ChatCommand {
|
|
|
195
627
|
if (!this.tools) {
|
|
196
628
|
throw new Error('Agent tools are not initialized.');
|
|
197
629
|
}
|
|
630
|
+
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
631
|
+
const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(prompt);
|
|
632
|
+
if (handledByDirectFileFlow) {
|
|
633
|
+
this.saveSession();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (this.shouldPreferLocalAgentLoop(prompt)) {
|
|
637
|
+
await this.runLocalAgentLoop(prompt);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
198
640
|
const handledByV3Workflow = await this.tryV3AgentWorkflow(prompt);
|
|
199
641
|
if (handledByV3Workflow) {
|
|
200
642
|
this.saveSession();
|
|
201
643
|
return;
|
|
202
644
|
}
|
|
203
|
-
|
|
204
|
-
|
|
645
|
+
if (requiresV3Workflow) {
|
|
646
|
+
const errorMessage = 'This task requires the V3 agent workflow and the CLI will not fall back to the legacy local agent loop.';
|
|
647
|
+
this.logger.error(errorMessage);
|
|
648
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
205
649
|
this.saveSession();
|
|
206
650
|
return;
|
|
207
651
|
}
|
|
652
|
+
await this.runLocalAgentLoop(prompt);
|
|
653
|
+
}
|
|
654
|
+
async runLocalAgentLoop(prompt) {
|
|
655
|
+
if (!this.tools) {
|
|
656
|
+
throw new Error('Agent tools are not initialized.');
|
|
657
|
+
}
|
|
208
658
|
this.lastActionableUserInput = prompt;
|
|
209
659
|
this.directToolContinuationCount = 0;
|
|
210
660
|
this.tools.clearSessionApprovals();
|
|
@@ -222,7 +672,22 @@ class ChatCommand {
|
|
|
222
672
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
223
673
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
224
674
|
if (toolCalls.length === 0) {
|
|
225
|
-
|
|
675
|
+
const finalContent = this.resolveDirectModeCompletion(prompt, visibleText);
|
|
676
|
+
if (this.jsonOutput) {
|
|
677
|
+
console.log(JSON.stringify({
|
|
678
|
+
success: true,
|
|
679
|
+
mode: 'agent',
|
|
680
|
+
model: this.currentModel,
|
|
681
|
+
partial: false,
|
|
682
|
+
content: finalContent,
|
|
683
|
+
metadata: {
|
|
684
|
+
executionPath: 'local-agent-loop',
|
|
685
|
+
},
|
|
686
|
+
}, null, 2));
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
console.log(finalContent);
|
|
690
|
+
}
|
|
226
691
|
this.saveSession();
|
|
227
692
|
return;
|
|
228
693
|
}
|
|
@@ -236,11 +701,41 @@ class ChatCommand {
|
|
|
236
701
|
}
|
|
237
702
|
catch (error) {
|
|
238
703
|
spinner.fail('Agent request failed');
|
|
704
|
+
if (this.jsonOutput) {
|
|
705
|
+
process.exitCode = 1;
|
|
706
|
+
console.log(JSON.stringify({
|
|
707
|
+
success: false,
|
|
708
|
+
mode: 'agent',
|
|
709
|
+
model: this.currentModel,
|
|
710
|
+
partial: false,
|
|
711
|
+
content: '',
|
|
712
|
+
error: error.message,
|
|
713
|
+
metadata: {
|
|
714
|
+
executionPath: 'local-agent-loop',
|
|
715
|
+
},
|
|
716
|
+
}, null, 2));
|
|
717
|
+
}
|
|
239
718
|
this.logger.error(error.message);
|
|
240
719
|
return;
|
|
241
720
|
}
|
|
242
721
|
}
|
|
243
|
-
|
|
722
|
+
if (this.jsonOutput) {
|
|
723
|
+
process.exitCode = 1;
|
|
724
|
+
console.log(JSON.stringify({
|
|
725
|
+
success: false,
|
|
726
|
+
mode: 'agent',
|
|
727
|
+
model: this.currentModel,
|
|
728
|
+
partial: true,
|
|
729
|
+
content: 'Task complete.',
|
|
730
|
+
error: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
731
|
+
metadata: {
|
|
732
|
+
executionPath: 'local-agent-loop',
|
|
733
|
+
},
|
|
734
|
+
}, null, 2));
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
console.log('Task complete.');
|
|
738
|
+
}
|
|
244
739
|
this.saveSession();
|
|
245
740
|
}
|
|
246
741
|
async tryDirectSingleFileFlow(prompt) {
|
|
@@ -251,41 +746,51 @@ class ChatCommand {
|
|
|
251
746
|
if (!targetFile) {
|
|
252
747
|
return false;
|
|
253
748
|
}
|
|
749
|
+
if (this.shouldBypassDirectSingleFileFlow(targetFile, prompt)) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
254
752
|
const readCall = {
|
|
255
753
|
tool: 'read_file',
|
|
256
754
|
args: { path: targetFile },
|
|
257
755
|
};
|
|
258
|
-
|
|
756
|
+
if (!this.jsonOutput) {
|
|
757
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
758
|
+
}
|
|
259
759
|
const readResult = await this.tools.execute(readCall);
|
|
260
760
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
261
|
-
|
|
761
|
+
if (!this.jsonOutput) {
|
|
762
|
+
console.log(readResult.success ? chalk_1.default.gray(readSummary) : chalk_1.default.red(readSummary));
|
|
763
|
+
}
|
|
262
764
|
this.messages.push({ role: 'system', content: readSummary });
|
|
263
765
|
if (!readResult.success || !readResult.output) {
|
|
264
766
|
return false;
|
|
265
767
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
768
|
+
let rewrittenContent = this.tryDeterministicSingleFileRewrite(prompt, targetFile, readResult.output);
|
|
769
|
+
if (!rewrittenContent) {
|
|
770
|
+
const rewriteMessages = [
|
|
771
|
+
{
|
|
772
|
+
role: 'system',
|
|
773
|
+
content: [
|
|
774
|
+
'You are repairing a single file for a CLI agent task.',
|
|
775
|
+
`Return only the final contents for ${targetFile}.`,
|
|
776
|
+
'Do not use Markdown fences.',
|
|
777
|
+
'Do not add explanations before or after the file contents.',
|
|
778
|
+
'Produce complete, runnable code or markup.',
|
|
779
|
+
].join('\n'),
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
role: 'user',
|
|
783
|
+
content: [
|
|
784
|
+
`Task: ${prompt}`,
|
|
785
|
+
`Target file: ${targetFile}`,
|
|
786
|
+
'Current file contents:',
|
|
787
|
+
readResult.output,
|
|
788
|
+
].join('\n\n'),
|
|
789
|
+
},
|
|
790
|
+
];
|
|
791
|
+
const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
|
|
792
|
+
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
793
|
+
}
|
|
289
794
|
if (!rewrittenContent) {
|
|
290
795
|
return false;
|
|
291
796
|
}
|
|
@@ -296,54 +801,248 @@ class ChatCommand {
|
|
|
296
801
|
content: rewrittenContent,
|
|
297
802
|
},
|
|
298
803
|
};
|
|
299
|
-
|
|
804
|
+
if (!this.jsonOutput) {
|
|
805
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
806
|
+
}
|
|
300
807
|
const writeResult = await this.tools.execute(writeCall);
|
|
301
808
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
302
|
-
|
|
809
|
+
if (!this.jsonOutput) {
|
|
810
|
+
console.log(writeResult.success ? chalk_1.default.gray(writeSummary) : chalk_1.default.red(writeSummary));
|
|
811
|
+
}
|
|
303
812
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
304
813
|
if (!writeResult.success) {
|
|
305
814
|
return false;
|
|
306
815
|
}
|
|
307
|
-
|
|
816
|
+
const previewGate = await this.api.runTemplateServicePreviewGate(prompt, {
|
|
817
|
+
workspacePath: this.currentProjectPath,
|
|
818
|
+
projectPath: this.currentProjectPath,
|
|
819
|
+
targetPath: this.currentProjectPath,
|
|
820
|
+
rawPrompt: prompt,
|
|
821
|
+
executionSurface: 'cli',
|
|
822
|
+
clientSurface: 'cli',
|
|
823
|
+
});
|
|
824
|
+
const success = previewGate.required ? previewGate.passed === true : true;
|
|
825
|
+
if (!success) {
|
|
826
|
+
process.exitCode = 1;
|
|
827
|
+
}
|
|
828
|
+
if (this.jsonOutput) {
|
|
829
|
+
console.log(JSON.stringify({
|
|
830
|
+
success,
|
|
831
|
+
mode: 'agent',
|
|
832
|
+
model: this.currentModel,
|
|
833
|
+
partial: false,
|
|
834
|
+
content: `Updated ${targetFile}.`,
|
|
835
|
+
metadata: {
|
|
836
|
+
executionPath: 'direct-single-file',
|
|
837
|
+
targetFile,
|
|
838
|
+
previewGate,
|
|
839
|
+
},
|
|
840
|
+
}, null, 2));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
console.log(`Updated ${targetFile}.`);
|
|
844
|
+
if (previewGate.required) {
|
|
845
|
+
if (previewGate.passed) {
|
|
846
|
+
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
308
853
|
return true;
|
|
309
854
|
}
|
|
310
855
|
async tryV3AgentWorkflow(prompt) {
|
|
311
|
-
const
|
|
856
|
+
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
857
|
+
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
858
|
+
const rescueEligible = this.isSaaSRescuePrompt(prompt);
|
|
859
|
+
const spinner = this.jsonOutput ? null : (0, ora_1.default)({
|
|
860
|
+
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
861
|
+
spinner: 'clock',
|
|
862
|
+
}).start();
|
|
312
863
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
313
864
|
const agentTaskType = this.inferAgentTaskType(prompt);
|
|
865
|
+
const workspaceContext = {
|
|
866
|
+
workspacePath: this.currentProjectPath,
|
|
867
|
+
projectPath: this.currentProjectPath,
|
|
868
|
+
targetPath: this.currentProjectPath,
|
|
869
|
+
...runtimeContext,
|
|
870
|
+
};
|
|
314
871
|
try {
|
|
315
|
-
const
|
|
872
|
+
const workflowPromise = this.api.runV3AgentWorkflow(executionPrompt, {
|
|
316
873
|
workspace: { path: this.currentProjectPath },
|
|
317
|
-
|
|
318
|
-
projectPath: this.currentProjectPath,
|
|
319
|
-
targetPath: this.currentProjectPath,
|
|
874
|
+
...workspaceContext,
|
|
320
875
|
agentTaskType,
|
|
321
876
|
executionSurface: 'cli',
|
|
322
877
|
clientSurface: 'cli',
|
|
323
878
|
localMachineCapable: true,
|
|
324
879
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
325
880
|
agentIdleTimeoutMs: 90000,
|
|
881
|
+
model: routingPolicy.selectedModel,
|
|
882
|
+
requestedModel: this.currentModel,
|
|
883
|
+
agentExecutionPolicy: routingPolicy,
|
|
884
|
+
legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
|
|
885
|
+
rawPrompt: prompt,
|
|
326
886
|
history: this.getMessagesForModel(),
|
|
887
|
+
...runtimeContext,
|
|
888
|
+
onStreamEvent: spinner ? (event) => this.updateV3AgentSpinner(spinner, event) : undefined,
|
|
327
889
|
});
|
|
328
|
-
|
|
329
|
-
|
|
890
|
+
const response = await (rescueEligible
|
|
891
|
+
? Promise.race([
|
|
892
|
+
workflowPromise,
|
|
893
|
+
new Promise((_, reject) => {
|
|
894
|
+
setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')), 240000);
|
|
895
|
+
}),
|
|
896
|
+
])
|
|
897
|
+
: workflowPromise);
|
|
898
|
+
if (spinner) {
|
|
899
|
+
spinner.stop();
|
|
900
|
+
}
|
|
901
|
+
const previewGate = (response.metadata?.previewGate || null);
|
|
902
|
+
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
903
|
+
const success = previewGate?.required === true
|
|
904
|
+
? (previewGate?.passed === true || workspaceHasOutput)
|
|
905
|
+
: true;
|
|
906
|
+
if (!success) {
|
|
907
|
+
if (this.isLegacyAgentFallbackAllowed()) {
|
|
908
|
+
if (spinner) {
|
|
909
|
+
spinner.warn('Falling back to legacy CLI agent loop');
|
|
910
|
+
}
|
|
911
|
+
this.logger.debug(`V3 agent workflow returned an incomplete result: ${previewGate?.error || 'workspace changes were not fully validated'}`);
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
|
|
915
|
+
if (spinner) {
|
|
916
|
+
spinner.fail('V3 agent workflow rejected the result');
|
|
917
|
+
}
|
|
918
|
+
this.logger.error(errorMessage);
|
|
919
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
923
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
|
|
924
|
+
}
|
|
925
|
+
if (this.jsonOutput) {
|
|
926
|
+
console.log(JSON.stringify({
|
|
927
|
+
success,
|
|
928
|
+
mode: 'agent',
|
|
929
|
+
model: routingPolicy.selectedModel,
|
|
930
|
+
routingPolicy,
|
|
931
|
+
taskId: response.taskId || null,
|
|
932
|
+
contextId: response.contextId || null,
|
|
933
|
+
partial: response.partial === true,
|
|
934
|
+
content: response.content || 'V3 agent workflow completed.',
|
|
935
|
+
metadata: response.metadata || {},
|
|
936
|
+
}, null, 2));
|
|
937
|
+
}
|
|
938
|
+
else if (response.content) {
|
|
939
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
330
940
|
console.log(response.content);
|
|
331
941
|
}
|
|
332
942
|
else {
|
|
943
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
333
944
|
console.log('V3 agent workflow completed.');
|
|
334
945
|
}
|
|
946
|
+
if (!this.jsonOutput && previewGate?.required) {
|
|
947
|
+
if (previewGate.passed) {
|
|
948
|
+
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
335
954
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
336
955
|
return true;
|
|
337
956
|
}
|
|
338
957
|
catch (error) {
|
|
339
|
-
|
|
340
|
-
|
|
958
|
+
if (rescueEligible && !this.api.hasAgentWorkspaceOutput(workspaceContext)) {
|
|
959
|
+
const rescued = await this.tryCommandLevelSaaSRescue(executionPrompt, prompt, workspaceContext, routingPolicy, spinner, error);
|
|
960
|
+
if (rescued) {
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (this.isLegacyAgentFallbackAllowed()) {
|
|
965
|
+
if (spinner) {
|
|
966
|
+
spinner.warn('Falling back to legacy CLI agent loop');
|
|
967
|
+
}
|
|
968
|
+
this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
if (spinner) {
|
|
972
|
+
spinner.fail('V3 agent workflow failed');
|
|
973
|
+
}
|
|
974
|
+
const errorMessage = `Agent mode requires the V3 workflow and will not fall back to the legacy CLI loop. ${error.message}`;
|
|
975
|
+
this.logger.error(errorMessage);
|
|
976
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
isSaaSRescuePrompt(prompt) {
|
|
981
|
+
return /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(prompt);
|
|
982
|
+
}
|
|
983
|
+
async tryCommandLevelSaaSRescue(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
984
|
+
const apiAny = this.api;
|
|
985
|
+
if (typeof apiAny.materializeEmergencySaaSWorkspace !== 'function') {
|
|
341
986
|
return false;
|
|
342
987
|
}
|
|
988
|
+
const appName = apiAny.materializeEmergencySaaSWorkspace(executionPrompt, workspaceContext);
|
|
989
|
+
if (!appName) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
if (typeof apiAny.ensureAgentFrontendPolish === 'function') {
|
|
993
|
+
await apiAny.ensureAgentFrontendPolish(executionPrompt, workspaceContext);
|
|
994
|
+
}
|
|
995
|
+
const previewGate = await this.api.runTemplateServicePreviewGate(executionPrompt, {
|
|
996
|
+
...workspaceContext,
|
|
997
|
+
rawPrompt,
|
|
998
|
+
});
|
|
999
|
+
if (spinner) {
|
|
1000
|
+
spinner.stop();
|
|
1001
|
+
}
|
|
1002
|
+
const rescueMessage = `Recovered a local SaaS workspace scaffold for ${appName} after the V3 workflow stalled without writing files.`;
|
|
1003
|
+
if (this.jsonOutput) {
|
|
1004
|
+
console.log(JSON.stringify({
|
|
1005
|
+
success: true,
|
|
1006
|
+
mode: 'agent',
|
|
1007
|
+
model: routingPolicy.selectedModel,
|
|
1008
|
+
routingPolicy,
|
|
1009
|
+
taskId: null,
|
|
1010
|
+
contextId: null,
|
|
1011
|
+
partial: true,
|
|
1012
|
+
content: rescueMessage,
|
|
1013
|
+
metadata: {
|
|
1014
|
+
source: 'cli-saas-rescue',
|
|
1015
|
+
previewGate,
|
|
1016
|
+
rescueReason: error.message,
|
|
1017
|
+
},
|
|
1018
|
+
}, null, 2));
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
1022
|
+
console.log(rescueMessage);
|
|
1023
|
+
if (previewGate.required) {
|
|
1024
|
+
if (previewGate.passed) {
|
|
1025
|
+
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
this.messages.push({ role: 'assistant', content: rescueMessage });
|
|
1033
|
+
return true;
|
|
343
1034
|
}
|
|
344
1035
|
async startInteractiveChat() {
|
|
345
|
-
|
|
1036
|
+
const chatTitle = this.operatorMode
|
|
1037
|
+
? 'Interactive Operator Chat'
|
|
1038
|
+
: this.agentMode
|
|
1039
|
+
? 'Interactive Agent Chat'
|
|
1040
|
+
: 'Interactive Chat';
|
|
1041
|
+
this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
|
|
346
1042
|
console.log(chalk_1.default.gray('Type /help for commands. Type /exit to quit.'));
|
|
1043
|
+
if (this.workflowTarget) {
|
|
1044
|
+
console.log(chalk_1.default.gray(`Workflow target: ${this.workflowTarget}`));
|
|
1045
|
+
}
|
|
347
1046
|
const rl = readline.createInterface({
|
|
348
1047
|
input: process.stdin,
|
|
349
1048
|
output: process.stdout,
|
|
@@ -374,9 +1073,42 @@ class ChatCommand {
|
|
|
374
1073
|
}
|
|
375
1074
|
if (trimmed === '/agent') {
|
|
376
1075
|
this.agentMode = !this.agentMode;
|
|
1076
|
+
if (this.agentMode) {
|
|
1077
|
+
this.operatorMode = false;
|
|
1078
|
+
this.syncInteractiveModeModel('agent');
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
this.syncInteractiveModeModel('chat');
|
|
1082
|
+
}
|
|
377
1083
|
console.log(chalk_1.default.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
|
|
1084
|
+
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
378
1085
|
if (this.currentSession) {
|
|
379
1086
|
this.currentSession.agentMode = this.agentMode;
|
|
1087
|
+
this.currentSession.operatorMode = this.operatorMode;
|
|
1088
|
+
this.currentSession.model = this.currentModel;
|
|
1089
|
+
this.saveSession();
|
|
1090
|
+
}
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
if (trimmed === '/operator') {
|
|
1094
|
+
if (!this.operatorMode && !this.hasOperatorAccess()) {
|
|
1095
|
+
this.logger.error(this.operatorAccessMessage());
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
this.operatorMode = !this.operatorMode;
|
|
1099
|
+
if (this.operatorMode) {
|
|
1100
|
+
this.agentMode = false;
|
|
1101
|
+
this.syncInteractiveModeModel('operator');
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
this.syncInteractiveModeModel('chat');
|
|
1105
|
+
}
|
|
1106
|
+
console.log(chalk_1.default.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
|
|
1107
|
+
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
1108
|
+
if (this.currentSession) {
|
|
1109
|
+
this.currentSession.agentMode = this.agentMode;
|
|
1110
|
+
this.currentSession.operatorMode = this.operatorMode;
|
|
1111
|
+
this.currentSession.model = this.currentModel;
|
|
380
1112
|
this.saveSession();
|
|
381
1113
|
}
|
|
382
1114
|
continue;
|
|
@@ -393,6 +1125,7 @@ class ChatCommand {
|
|
|
393
1125
|
}
|
|
394
1126
|
if (trimmed.startsWith('/model ')) {
|
|
395
1127
|
this.currentModel = trimmed.slice(7).trim() || this.currentModel;
|
|
1128
|
+
this.modelExplicitlySelected = true;
|
|
396
1129
|
console.log(chalk_1.default.yellow(`Model changed to: ${this.currentModel}`));
|
|
397
1130
|
if (this.currentSession) {
|
|
398
1131
|
this.currentSession.model = this.currentModel;
|
|
@@ -400,7 +1133,13 @@ class ChatCommand {
|
|
|
400
1133
|
}
|
|
401
1134
|
continue;
|
|
402
1135
|
}
|
|
403
|
-
if (this.
|
|
1136
|
+
if (this.workflowTarget) {
|
|
1137
|
+
await this.runWorkflowTargetPrompt(trimmed);
|
|
1138
|
+
}
|
|
1139
|
+
else if (this.operatorMode) {
|
|
1140
|
+
await this.runOperatorTurn(trimmed);
|
|
1141
|
+
}
|
|
1142
|
+
else if (this.agentMode) {
|
|
404
1143
|
await this.runAgentTurn(trimmed);
|
|
405
1144
|
}
|
|
406
1145
|
else {
|
|
@@ -414,6 +1153,7 @@ class ChatCommand {
|
|
|
414
1153
|
console.log(' /help Show this help');
|
|
415
1154
|
console.log(' /exit Exit chat');
|
|
416
1155
|
console.log(' /agent Toggle agent mode');
|
|
1156
|
+
console.log(' /operator Toggle BMAD operator mode');
|
|
417
1157
|
console.log(' /context Show current session memory');
|
|
418
1158
|
console.log(' /compact Compact current session into memory summary');
|
|
419
1159
|
console.log(' /clear Clear conversation');
|
|
@@ -499,7 +1239,8 @@ class ChatCommand {
|
|
|
499
1239
|
'Finish the request and stop once it is complete.',
|
|
500
1240
|
].join('\n');
|
|
501
1241
|
}
|
|
502
|
-
|
|
1242
|
+
inferTargetFilesFromPrompt(prompt) {
|
|
1243
|
+
const candidates = [];
|
|
503
1244
|
const matches = Array.from(prompt.matchAll(/([A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g));
|
|
504
1245
|
for (const match of matches) {
|
|
505
1246
|
const candidate = match[1];
|
|
@@ -508,10 +1249,137 @@ class ChatCommand {
|
|
|
508
1249
|
}
|
|
509
1250
|
const resolved = path.resolve(this.currentProjectPath, candidate);
|
|
510
1251
|
if (resolved.startsWith(this.currentProjectPath)) {
|
|
511
|
-
|
|
1252
|
+
if (!candidates.includes(candidate)) {
|
|
1253
|
+
candidates.push(candidate);
|
|
1254
|
+
}
|
|
512
1255
|
}
|
|
513
1256
|
}
|
|
514
|
-
return
|
|
1257
|
+
return candidates;
|
|
1258
|
+
}
|
|
1259
|
+
inferTargetFileFromPrompt(prompt) {
|
|
1260
|
+
return this.inferTargetFilesFromPrompt(prompt)[0] || null;
|
|
1261
|
+
}
|
|
1262
|
+
workspaceContainsHtmlEntry(rootDir) {
|
|
1263
|
+
const stack = [rootDir];
|
|
1264
|
+
while (stack.length > 0) {
|
|
1265
|
+
const currentDir = stack.pop();
|
|
1266
|
+
if (!currentDir) {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
let entries = [];
|
|
1270
|
+
try {
|
|
1271
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1272
|
+
}
|
|
1273
|
+
catch {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
for (const entry of entries) {
|
|
1277
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1281
|
+
if (entry.isDirectory()) {
|
|
1282
|
+
stack.push(fullPath);
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
shouldBypassDirectSingleFileFlow(targetFile, prompt) {
|
|
1293
|
+
const referencedFiles = this.inferTargetFilesFromPrompt(prompt);
|
|
1294
|
+
const editableFiles = referencedFiles.filter((filePath) => !this.isProtectedFileReferenceSafe(prompt, filePath));
|
|
1295
|
+
if (editableFiles.length > 1) {
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
const extension = path.extname(targetFile).toLowerCase();
|
|
1299
|
+
return extension === '';
|
|
1300
|
+
}
|
|
1301
|
+
shouldPreferLocalAgentLoop(prompt) {
|
|
1302
|
+
if (this.shouldRequireV3AgentWorkflow(prompt)) {
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
if (!this.directPromptMode) {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
if (this.isServerBindableWorkspace(this.currentProjectPath)) {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
return /(analyse|analyze|audit|overview|inspect|explain|review|debug|diagnos|read|summari[sz]e|investigate)/i.test(prompt);
|
|
1312
|
+
}
|
|
1313
|
+
shouldRequireV3AgentWorkflow(prompt) {
|
|
1314
|
+
if (!this.directPromptMode) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
if (this.inferTargetFilesFromPrompt(prompt).length > 0) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
return /(create the required project files and write them to the workspace|build a polished|premium|landing experience|landing page|site|website|dashboard|saas|frontend|hero|responsive|animated reveals|journal preview|contact section|mobile navigation|ui details)/i.test(prompt);
|
|
1321
|
+
}
|
|
1322
|
+
isServerBindableWorkspace(projectPath) {
|
|
1323
|
+
if (!projectPath || !path.isAbsolute(projectPath) || !fs.existsSync(projectPath)) {
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
const configuredRoots = (process.env.VIGTHORIA_SERVER_WORKSPACE_ROOTS || '/var/www/vigthoria-user-workspaces,/var/lib/vigthoria-workspaces')
|
|
1327
|
+
.split(',')
|
|
1328
|
+
.map((entry) => entry.trim())
|
|
1329
|
+
.filter(Boolean);
|
|
1330
|
+
let resolvedCandidate = '';
|
|
1331
|
+
try {
|
|
1332
|
+
resolvedCandidate = fs.realpathSync(projectPath);
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
return configuredRoots.some((root) => {
|
|
1338
|
+
if (!root || !fs.existsSync(root)) {
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
try {
|
|
1342
|
+
const resolvedRoot = fs.realpathSync(root);
|
|
1343
|
+
const relativePath = path.relative(resolvedRoot, resolvedCandidate);
|
|
1344
|
+
return relativePath === '' || !relativePath.startsWith('..');
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
isProtectedFileReferenceSafe(prompt, filePath) {
|
|
1352
|
+
const normalizedPrompt = prompt.toLowerCase();
|
|
1353
|
+
const normalizedPath = filePath.toLowerCase();
|
|
1354
|
+
const protectedPhrases = [
|
|
1355
|
+
`do not modify ${normalizedPath}`,
|
|
1356
|
+
`do not modify \`${normalizedPath}\``,
|
|
1357
|
+
`do not modify '${normalizedPath}'`,
|
|
1358
|
+
`do not modify "${normalizedPath}"`,
|
|
1359
|
+
`don't modify ${normalizedPath}`,
|
|
1360
|
+
`don't modify \`${normalizedPath}\``,
|
|
1361
|
+
`don't modify '${normalizedPath}'`,
|
|
1362
|
+
`don't modify "${normalizedPath}"`,
|
|
1363
|
+
`leave ${normalizedPath} unchanged`,
|
|
1364
|
+
`leave \`${normalizedPath}\` unchanged`,
|
|
1365
|
+
`leave '${normalizedPath}' unchanged`,
|
|
1366
|
+
`leave "${normalizedPath}" unchanged`,
|
|
1367
|
+
`without modifying ${normalizedPath}`,
|
|
1368
|
+
`without modifying \`${normalizedPath}\``,
|
|
1369
|
+
`without modifying '${normalizedPath}'`,
|
|
1370
|
+
`without modifying "${normalizedPath}"`,
|
|
1371
|
+
];
|
|
1372
|
+
return protectedPhrases.some((phrase) => normalizedPrompt.includes(phrase));
|
|
1373
|
+
}
|
|
1374
|
+
isProtectedFileReference(prompt, filePath) {
|
|
1375
|
+
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1376
|
+
const protectedPatterns = [
|
|
1377
|
+
new RegExp(`do not modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1378
|
+
new RegExp(`don't modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1379
|
+
new RegExp(`leave\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?\\s+unchanged`, 'i'),
|
|
1380
|
+
new RegExp(`without modifying\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1381
|
+
];
|
|
1382
|
+
return protectedPatterns.some((pattern) => pattern.test(prompt));
|
|
515
1383
|
}
|
|
516
1384
|
buildContinuationPrompt() {
|
|
517
1385
|
const diagnosticMode = this.isDiagnosticPrompt(this.lastActionableUserInput);
|
|
@@ -583,15 +1451,110 @@ class ChatCommand {
|
|
|
583
1451
|
}
|
|
584
1452
|
return trimmed;
|
|
585
1453
|
}
|
|
1454
|
+
resolveDirectModeCompletion(prompt, visibleText) {
|
|
1455
|
+
const normalized = (visibleText || '').trim();
|
|
1456
|
+
if (normalized && !this.isDirectModeFollowUpQuestion(normalized)) {
|
|
1457
|
+
return normalized;
|
|
1458
|
+
}
|
|
1459
|
+
const fallback = this.buildLocalAnalysisFallback(prompt);
|
|
1460
|
+
if (fallback) {
|
|
1461
|
+
return fallback;
|
|
1462
|
+
}
|
|
1463
|
+
return normalized || 'Task complete.';
|
|
1464
|
+
}
|
|
1465
|
+
isDirectModeFollowUpQuestion(text) {
|
|
1466
|
+
return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on)/i.test(text.trim());
|
|
1467
|
+
}
|
|
1468
|
+
buildLocalAnalysisFallback(prompt) {
|
|
1469
|
+
if (!/(analyse|analyze|audit|overview|inspect|review|summari[sz]e|actual state)/i.test(prompt)) {
|
|
1470
|
+
return '';
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
const summary = [];
|
|
1474
|
+
const rootPath = this.currentProjectPath;
|
|
1475
|
+
const topLevelEntries = fs.readdirSync(rootPath, { withFileTypes: true })
|
|
1476
|
+
.filter((entry) => entry.name !== '.git' && entry.name !== 'node_modules')
|
|
1477
|
+
.slice(0, 20)
|
|
1478
|
+
.map((entry) => `${entry.name}${entry.isDirectory() ? '/' : ''}`);
|
|
1479
|
+
summary.push(`Project overview for ${path.basename(rootPath)}.`);
|
|
1480
|
+
summary.push(`Workspace: ${rootPath}`);
|
|
1481
|
+
const packageJsonPath = path.join(rootPath, 'package.json');
|
|
1482
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1483
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1484
|
+
const scripts = Object.keys(pkg.scripts || {});
|
|
1485
|
+
const dependencies = Object.keys(pkg.dependencies || {});
|
|
1486
|
+
const devDependencies = Object.keys(pkg.devDependencies || {});
|
|
1487
|
+
summary.push(`Package: ${pkg.name || path.basename(rootPath)}${pkg.version ? ` v${pkg.version}` : ''}`);
|
|
1488
|
+
if (scripts.length > 0) {
|
|
1489
|
+
summary.push(`Scripts: ${scripts.slice(0, 10).join(', ')}`);
|
|
1490
|
+
}
|
|
1491
|
+
summary.push(`Dependencies: ${dependencies.length} runtime, ${devDependencies.length} dev`);
|
|
1492
|
+
}
|
|
1493
|
+
if (topLevelEntries.length > 0) {
|
|
1494
|
+
summary.push(`Top-level entries: ${topLevelEntries.join(', ')}`);
|
|
1495
|
+
}
|
|
1496
|
+
const readmePath = path.join(rootPath, 'README.md');
|
|
1497
|
+
if (fs.existsSync(readmePath)) {
|
|
1498
|
+
const excerpt = fs.readFileSync(readmePath, 'utf8').replace(/\s+/g, ' ').trim().slice(0, 500);
|
|
1499
|
+
if (excerpt) {
|
|
1500
|
+
summary.push(`README excerpt: ${excerpt}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return summary.join('\n');
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
return '';
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
tryDeterministicSingleFileRewrite(prompt, targetFile, currentContent) {
|
|
1510
|
+
const extension = path.extname(targetFile).toLowerCase();
|
|
1511
|
+
if (extension !== '.html') {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
const exactText = this.extractExactTextRequirement(prompt);
|
|
1515
|
+
if (!exactText) {
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
if (!/hero\s+(?:paragraph|copy)/i.test(prompt)) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
const heroCopyPattern = /(<p[^>]*class=["'][^"']*hero-copy[^"']*["'][^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
1522
|
+
if (heroCopyPattern.test(currentContent)) {
|
|
1523
|
+
const nextContent = currentContent.replace(heroCopyPattern, `$1${exactText}$3`);
|
|
1524
|
+
return nextContent !== currentContent ? nextContent : null;
|
|
1525
|
+
}
|
|
1526
|
+
const heroParagraphPattern = /(<main[^>]*class=["'][^"']*hero[^"']*["'][^>]*>[\s\S]*?<p[^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
1527
|
+
if (heroParagraphPattern.test(currentContent)) {
|
|
1528
|
+
const nextContent = currentContent.replace(heroParagraphPattern, `$1${exactText}$3`);
|
|
1529
|
+
return nextContent !== currentContent ? nextContent : null;
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
extractExactTextRequirement(prompt) {
|
|
1534
|
+
const match = prompt.match(/reads exactly:\s*([\s\S]*?)(?:\s+Do not\b|\s+Do\b|$)/i);
|
|
1535
|
+
if (!match || !match[1]) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const normalized = match[1]
|
|
1539
|
+
.trim()
|
|
1540
|
+
.replace(/^[`"']+/, '')
|
|
1541
|
+
.replace(/[`"']+$/, '')
|
|
1542
|
+
.trim();
|
|
1543
|
+
return normalized || null;
|
|
1544
|
+
}
|
|
586
1545
|
async executeToolCalls(toolCalls) {
|
|
587
1546
|
if (!this.tools) {
|
|
588
1547
|
throw new Error('Agent tools are not initialized.');
|
|
589
1548
|
}
|
|
590
1549
|
for (const call of toolCalls) {
|
|
591
|
-
|
|
1550
|
+
if (!this.jsonOutput) {
|
|
1551
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
|
|
1552
|
+
}
|
|
592
1553
|
const result = await this.tools.execute(call);
|
|
593
1554
|
const summary = this.formatToolResult(call, result);
|
|
594
|
-
|
|
1555
|
+
if (!this.jsonOutput) {
|
|
1556
|
+
console.log(result.success ? chalk_1.default.gray(summary) : chalk_1.default.red(summary));
|
|
1557
|
+
}
|
|
595
1558
|
this.messages.push({ role: 'system', content: summary });
|
|
596
1559
|
}
|
|
597
1560
|
}
|
|
@@ -617,11 +1580,12 @@ class ChatCommand {
|
|
|
617
1580
|
}
|
|
618
1581
|
saveSession() {
|
|
619
1582
|
if (!this.currentSession) {
|
|
620
|
-
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode);
|
|
1583
|
+
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
|
|
621
1584
|
}
|
|
622
1585
|
this.currentSession.project = this.currentProjectPath;
|
|
623
1586
|
this.currentSession.model = this.currentModel;
|
|
624
1587
|
this.currentSession.agentMode = this.agentMode;
|
|
1588
|
+
this.currentSession.operatorMode = this.operatorMode;
|
|
625
1589
|
this.currentSession.messages = [...this.messages];
|
|
626
1590
|
this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
|
|
627
1591
|
this.messages = [...this.currentSession.messages];
|
|
@@ -658,4 +1622,3 @@ class ChatCommand {
|
|
|
658
1622
|
}
|
|
659
1623
|
}
|
|
660
1624
|
exports.ChatCommand = ChatCommand;
|
|
661
|
-
//# sourceMappingURL=chat.js.map
|