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.
Files changed (74) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/auth.d.ts +0 -1
  3. package/dist/commands/auth.js +34 -2
  4. package/dist/commands/bridge.d.ts +7 -0
  5. package/dist/commands/bridge.js +31 -0
  6. package/dist/commands/chat.d.ts +50 -3
  7. package/dist/commands/chat.js +1033 -70
  8. package/dist/commands/config.d.ts +0 -1
  9. package/dist/commands/config.js +0 -1
  10. package/dist/commands/deploy.d.ts +0 -1
  11. package/dist/commands/deploy.js +0 -1
  12. package/dist/commands/edit.d.ts +0 -1
  13. package/dist/commands/edit.js +0 -1
  14. package/dist/commands/explain.d.ts +0 -1
  15. package/dist/commands/explain.js +0 -1
  16. package/dist/commands/generate.d.ts +0 -1
  17. package/dist/commands/generate.js +0 -1
  18. package/dist/commands/hub.d.ts +0 -1
  19. package/dist/commands/hub.js +0 -1
  20. package/dist/commands/repo.d.ts +0 -1
  21. package/dist/commands/repo.js +0 -1
  22. package/dist/commands/review.d.ts +0 -1
  23. package/dist/commands/review.js +0 -1
  24. package/dist/commands/workflow.d.ts +31 -0
  25. package/dist/commands/workflow.js +140 -0
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.js +137 -17
  28. package/dist/utils/api.d.ts +172 -2
  29. package/dist/utils/api.js +2046 -69
  30. package/dist/utils/config.d.ts +14 -7
  31. package/dist/utils/config.js +27 -12
  32. package/dist/utils/files.d.ts +0 -1
  33. package/dist/utils/files.js +0 -1
  34. package/dist/utils/logger.d.ts +0 -1
  35. package/dist/utils/logger.js +0 -1
  36. package/dist/utils/session.d.ts +2 -2
  37. package/dist/utils/session.js +2 -2
  38. package/dist/utils/tools.d.ts +0 -1
  39. package/dist/utils/tools.js +33 -7
  40. package/package.json +21 -3
  41. package/dist/commands/auth.d.ts.map +0 -1
  42. package/dist/commands/auth.js.map +0 -1
  43. package/dist/commands/chat.d.ts.map +0 -1
  44. package/dist/commands/chat.js.map +0 -1
  45. package/dist/commands/config.d.ts.map +0 -1
  46. package/dist/commands/config.js.map +0 -1
  47. package/dist/commands/deploy.d.ts.map +0 -1
  48. package/dist/commands/deploy.js.map +0 -1
  49. package/dist/commands/edit.d.ts.map +0 -1
  50. package/dist/commands/edit.js.map +0 -1
  51. package/dist/commands/explain.d.ts.map +0 -1
  52. package/dist/commands/explain.js.map +0 -1
  53. package/dist/commands/generate.d.ts.map +0 -1
  54. package/dist/commands/generate.js.map +0 -1
  55. package/dist/commands/hub.d.ts.map +0 -1
  56. package/dist/commands/hub.js.map +0 -1
  57. package/dist/commands/repo.d.ts.map +0 -1
  58. package/dist/commands/repo.js.map +0 -1
  59. package/dist/commands/review.d.ts.map +0 -1
  60. package/dist/commands/review.js.map +0 -1
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/index.js.map +0 -1
  63. package/dist/utils/api.d.ts.map +0 -1
  64. package/dist/utils/api.js.map +0 -1
  65. package/dist/utils/config.d.ts.map +0 -1
  66. package/dist/utils/config.js.map +0 -1
  67. package/dist/utils/files.d.ts.map +0 -1
  68. package/dist/utils/files.js.map +0 -1
  69. package/dist/utils/logger.d.ts.map +0 -1
  70. package/dist/utils/logger.js.map +0 -1
  71. package/dist/utils/session.d.ts.map +0 -1
  72. package/dist/utils/session.js.map +0 -1
  73. package/dist/utils/tools.d.ts.map +0 -1
  74. package/dist/utils/tools.js.map +0 -1
@@ -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
- if (!this.isDiagnosticPrompt(prompt)) {
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.autoApprove = options.autoApprove === true;
129
- this.currentModel = options.model || 'code';
130
- this.currentProjectPath = path.resolve(options.project || process.cwd());
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
- console.log(chalk_1.default.cyan('Running single prompt in direct mode.'));
166
- console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
167
- console.log(chalk_1.default.gray(`Project: ${this.currentProjectPath}`));
168
- console.log();
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
- const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(prompt);
204
- if (handledByDirectFileFlow) {
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
- console.log(visibleText || 'Task complete.');
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
- console.log('Task complete.');
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
- console.log(chalk_1.default.cyan(`⚙ Executing: ${readCall.tool}`));
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
- console.log(readResult.success ? chalk_1.default.gray(readSummary) : chalk_1.default.red(readSummary));
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
- const rewriteMessages = [
267
- {
268
- role: 'system',
269
- content: [
270
- 'You are repairing a single file for a CLI agent task.',
271
- `Return only the final contents for ${targetFile}.`,
272
- 'Do not use Markdown fences.',
273
- 'Do not add explanations before or after the file contents.',
274
- 'Produce complete, runnable code or markup.',
275
- ].join('\n'),
276
- },
277
- {
278
- role: 'user',
279
- content: [
280
- `Task: ${prompt}`,
281
- `Target file: ${targetFile}`,
282
- 'Current file contents:',
283
- readResult.output,
284
- ].join('\n\n'),
285
- },
286
- ];
287
- const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
288
- const rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
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
- console.log(chalk_1.default.cyan(`⚙ Executing: ${writeCall.tool}`));
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
- console.log(writeResult.success ? chalk_1.default.gray(writeSummary) : chalk_1.default.red(writeSummary));
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
- console.log(`Updated ${targetFile}.`);
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 spinner = (0, ora_1.default)({ text: 'Running V3 agent workflow...', spinner: 'clock' }).start();
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 response = await this.api.runV3AgentWorkflow(executionPrompt, {
872
+ const workflowPromise = this.api.runV3AgentWorkflow(executionPrompt, {
316
873
  workspace: { path: this.currentProjectPath },
317
- workspacePath: this.currentProjectPath,
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
- spinner.stop();
329
- if (response.content) {
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
- spinner.warn('Falling back to legacy CLI agent loop');
340
- this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
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
- this.logger.section(this.agentMode ? 'Interactive Agent Chat' : 'Interactive Chat');
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.agentMode) {
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
- inferTargetFileFromPrompt(prompt) {
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
- return candidate;
1252
+ if (!candidates.includes(candidate)) {
1253
+ candidates.push(candidate);
1254
+ }
512
1255
  }
513
1256
  }
514
- return null;
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
- console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
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
- console.log(result.success ? chalk_1.default.gray(summary) : chalk_1.default.red(summary));
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