vigthoria-cli 1.9.9 → 1.9.19
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 +5 -5
- package/dist/commands/auth.js +48 -65
- package/dist/commands/bridge.js +12 -19
- package/dist/commands/cancel.js +15 -22
- package/dist/commands/chat.d.ts +11 -0
- package/dist/commands/chat.js +404 -248
- package/dist/commands/config.js +31 -71
- package/dist/commands/deploy.js +83 -123
- package/dist/commands/device.d.ts +35 -0
- package/dist/commands/device.js +239 -0
- package/dist/commands/edit.js +32 -39
- package/dist/commands/explain.js +18 -25
- package/dist/commands/fork.js +22 -27
- package/dist/commands/generate.js +37 -44
- package/dist/commands/history.js +20 -25
- package/dist/commands/hub.js +95 -102
- package/dist/commands/index.js +41 -46
- package/dist/commands/legion.d.ts +1 -0
- package/dist/commands/legion.js +162 -209
- package/dist/commands/preview.js +60 -98
- package/dist/commands/replay.js +27 -32
- package/dist/commands/repo.js +103 -141
- package/dist/commands/review.js +29 -36
- package/dist/commands/security.js +5 -12
- package/dist/commands/update.js +15 -49
- package/dist/commands/workflow.d.ts +8 -1
- package/dist/commands/workflow.js +53 -19
- package/dist/index.js +409 -234
- package/dist/utils/api.d.ts +5 -0
- package/dist/utils/api.js +398 -176
- package/dist/utils/bridge-client.js +11 -52
- package/dist/utils/cli-state.d.ts +54 -0
- package/dist/utils/cli-state.js +185 -0
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +35 -14
- package/dist/utils/context-ranker.js +15 -21
- package/dist/utils/files.js +5 -42
- package/dist/utils/logger.js +42 -50
- package/dist/utils/post-write-validator.js +22 -29
- package/dist/utils/project-memory.d.ts +56 -0
- package/dist/utils/project-memory.js +289 -0
- package/dist/utils/session.d.ts +29 -3
- package/dist/utils/session.js +137 -85
- package/dist/utils/task-display.js +13 -20
- package/dist/utils/tools.d.ts +19 -0
- package/dist/utils/tools.js +84 -87
- package/dist/utils/workspace-cache.js +18 -26
- package/dist/utils/workspace-stream.js +26 -64
- package/install.ps1 +14 -0
- package/package.json +5 -3
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/validate-no-go-gates.sh +2 -2
package/dist/commands/chat.js
CHANGED
|
@@ -1,54 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.ChatCommand = void 0;
|
|
40
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
|
-
const os = __importStar(require("os"));
|
|
43
|
-
const path = __importStar(require("path"));
|
|
44
|
-
const readline = __importStar(require("readline"));
|
|
45
|
-
const logger_js_1 = require("../utils/logger.js");
|
|
46
|
-
const api_js_1 = require("../utils/api.js");
|
|
47
|
-
const tools_js_1 = require("../utils/tools.js");
|
|
48
|
-
const session_js_1 = require("../utils/session.js");
|
|
49
|
-
const bridge_client_js_1 = require("../utils/bridge-client.js");
|
|
50
|
-
const workspace_stream_js_1 = require("../utils/workspace-stream.js");
|
|
51
|
-
const task_display_js_1 = require("../utils/task-display.js");
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as readline from 'readline';
|
|
6
|
+
import { createSpinner } from '../utils/logger.js';
|
|
7
|
+
import { APIClient, CLIError, classifyError, formatCLIError, sanitizeUserFacingErrorText, sanitizeUserFacingPathText, propagateError } from '../utils/api.js';
|
|
8
|
+
import { AgenticTools, robustifyStreamResponse } from '../utils/tools.js';
|
|
9
|
+
import { SessionManager } from '../utils/session.js';
|
|
10
|
+
import { BridgeClient, getBridgeClient } from '../utils/bridge-client.js';
|
|
11
|
+
import { WorkspaceWatcher } from '../utils/workspace-stream.js';
|
|
12
|
+
import { TaskDisplay } from '../utils/task-display.js';
|
|
13
|
+
import { ProjectMemoryService } from '../utils/project-memory.js';
|
|
52
14
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
53
15
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
54
16
|
if (!rawValue) {
|
|
@@ -73,13 +35,14 @@ const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
|
73
35
|
const parsed = Number.parseInt(rawValue, 10);
|
|
74
36
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
75
37
|
})();
|
|
76
|
-
class ChatCommand {
|
|
38
|
+
export class ChatCommand {
|
|
77
39
|
config;
|
|
78
40
|
logger;
|
|
79
41
|
api;
|
|
80
42
|
messages = [];
|
|
81
43
|
tools = null;
|
|
82
44
|
sessionManager;
|
|
45
|
+
projectMemory = null;
|
|
83
46
|
currentSession = null;
|
|
84
47
|
agentMode = false;
|
|
85
48
|
currentProjectPath = process.cwd();
|
|
@@ -143,27 +106,27 @@ class ChatCommand {
|
|
|
143
106
|
message.includes('aborted');
|
|
144
107
|
}
|
|
145
108
|
toUserFacingApiError(error, context) {
|
|
146
|
-
const classified =
|
|
109
|
+
const classified = classifyError(error);
|
|
147
110
|
const status = classified.statusCode || (this.isJwtExpirationError(error) ? 401 : 500);
|
|
148
111
|
if (this.isJwtExpirationError(error)) {
|
|
149
|
-
return new
|
|
112
|
+
return new CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
|
|
150
113
|
}
|
|
151
114
|
if (this.isTimeoutError(error)) {
|
|
152
|
-
return new
|
|
115
|
+
return new CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
|
|
153
116
|
}
|
|
154
117
|
if (this.isNetworkError(error)) {
|
|
155
|
-
return new
|
|
118
|
+
return new CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
|
|
156
119
|
}
|
|
157
|
-
const message =
|
|
158
|
-
return new
|
|
120
|
+
const message = sanitizeUserFacingErrorText(classified.message || `${context} failed`);
|
|
121
|
+
return new CLIError(message, status === 401 ? 'auth' : 'model_backend', { statusCode: status });
|
|
159
122
|
}
|
|
160
123
|
handleApiError(error, context) {
|
|
161
124
|
const userFacingError = this.toUserFacingApiError(error, context);
|
|
162
125
|
if (!this.jsonOutput) {
|
|
163
|
-
console.error(
|
|
126
|
+
console.error(chalk.red(`${context} failed: ${userFacingError.message}`));
|
|
164
127
|
}
|
|
165
128
|
const original = error && typeof error === 'object' ? error : { message: String(error) };
|
|
166
|
-
|
|
129
|
+
propagateError({
|
|
167
130
|
...original,
|
|
168
131
|
message: userFacingError.message,
|
|
169
132
|
statusCode: userFacingError.statusCode,
|
|
@@ -186,7 +149,7 @@ class ChatCommand {
|
|
|
186
149
|
catch (error) {
|
|
187
150
|
if (!this.jsonOutput) {
|
|
188
151
|
const transient = this.isTimeoutError(error) ? 'timeout' : this.isNetworkError(error) ? 'network error' : 'API error';
|
|
189
|
-
console.error(
|
|
152
|
+
console.error(chalk.red(`${context} failed with ${transient}: ${this.toUserFacingApiError(error, context).message}`));
|
|
190
153
|
}
|
|
191
154
|
lastError = error;
|
|
192
155
|
if (this.isJwtExpirationError(error)) {
|
|
@@ -329,25 +292,28 @@ class ChatCommand {
|
|
|
329
292
|
};
|
|
330
293
|
}
|
|
331
294
|
getMessagesForModel() {
|
|
332
|
-
const memoryContext = this.sessionManager.buildMemoryContext(this.currentSession);
|
|
333
295
|
const messages = [...this.messages];
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
296
|
+
const memoryContexts = [
|
|
297
|
+
this.sessionManager.buildMemoryContext(this.currentSession),
|
|
298
|
+
this.projectMemory?.buildContextForPrompt(this.getLastUserPrompt()) || '',
|
|
299
|
+
].filter((entry) => entry && entry.trim());
|
|
300
|
+
for (const memoryContext of memoryContexts) {
|
|
301
|
+
const marker = memoryContext.includes('Vigthoria project brain memory.')
|
|
302
|
+
? 'Vigthoria project brain memory.'
|
|
303
|
+
: 'Session memory summary from earlier conversation turns.';
|
|
304
|
+
const alreadyInjected = messages.some((message) => message.role === 'system' && message.content.includes(marker));
|
|
305
|
+
if (alreadyInjected) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const insertionIndex = messages.findIndex((message) => message.role !== 'system');
|
|
309
|
+
const memoryMessage = { role: 'system', content: memoryContext };
|
|
310
|
+
if (insertionIndex === -1) {
|
|
311
|
+
messages.push(memoryMessage);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
messages.splice(insertionIndex, 0, memoryMessage);
|
|
315
|
+
}
|
|
349
316
|
}
|
|
350
|
-
messages.splice(insertionIndex, 0, memoryMessage);
|
|
351
317
|
return messages;
|
|
352
318
|
}
|
|
353
319
|
isDiagnosticPrompt(prompt) {
|
|
@@ -432,18 +398,67 @@ class ChatCommand {
|
|
|
432
398
|
const shaping = this.buildTaskShapingInstructions(prompt);
|
|
433
399
|
return shaping ? `${prompt}\n\n${shaping}` : prompt;
|
|
434
400
|
}
|
|
401
|
+
isProjectBrainRuntimeDisabled() {
|
|
402
|
+
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
|
|
403
|
+
}
|
|
404
|
+
buildProjectBrainRuntimeContext(prompt, source) {
|
|
405
|
+
if (this.isProjectBrainRuntimeDisabled()) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
if (!this.projectMemory) {
|
|
410
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
411
|
+
}
|
|
412
|
+
const status = this.projectMemory.getStatus();
|
|
413
|
+
const context = this.projectMemory.buildContextForPrompt(prompt);
|
|
414
|
+
if (!context && status.itemCount === 0) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
schema: 'vigthoria.project-brain.v1',
|
|
419
|
+
source,
|
|
420
|
+
memoryDir: status.memoryDir,
|
|
421
|
+
itemCount: status.itemCount,
|
|
422
|
+
updatedAt: status.updatedAt,
|
|
423
|
+
context,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
rememberBrainEvent(type, text, mode) {
|
|
431
|
+
if (this.isProjectBrainRuntimeDisabled()) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
if (!this.projectMemory) {
|
|
436
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
437
|
+
}
|
|
438
|
+
this.projectMemory.remember(type, text, { source: 'vigthoria-cli', mode, model: this.currentModel });
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// Project Brain memory must not break chat, GoA, or operator execution.
|
|
442
|
+
}
|
|
443
|
+
}
|
|
435
444
|
async getPromptRuntimeContext(prompt) {
|
|
445
|
+
const runtimeContext = {};
|
|
446
|
+
const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
|
|
447
|
+
if (brainContext) {
|
|
448
|
+
runtimeContext.vigthoriaBrain = brainContext;
|
|
449
|
+
}
|
|
436
450
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
437
|
-
return
|
|
451
|
+
return runtimeContext;
|
|
438
452
|
}
|
|
439
453
|
const bridgeStatus = await this.callApi('Checking DevTools Bridge status', () => this.api.getDevtoolsBridgeStatus(), 0);
|
|
440
454
|
if (!this.jsonOutput && bridgeStatus.ok) {
|
|
441
|
-
console.log(
|
|
455
|
+
console.log(chalk.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
|
|
442
456
|
}
|
|
443
457
|
else if (!this.jsonOutput) {
|
|
444
|
-
console.log(
|
|
458
|
+
console.log(chalk.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
|
|
445
459
|
}
|
|
446
460
|
return {
|
|
461
|
+
...runtimeContext,
|
|
447
462
|
browserTask: true,
|
|
448
463
|
devtoolsBridgeAvailable: bridgeStatus.ok,
|
|
449
464
|
devtoolsBridgeEndpoint: bridgeStatus.endpoint,
|
|
@@ -460,12 +475,7 @@ class ChatCommand {
|
|
|
460
475
|
sanitizeServerPath(text) {
|
|
461
476
|
if (!text)
|
|
462
477
|
return text;
|
|
463
|
-
|
|
464
|
-
return text
|
|
465
|
-
.replace(/\/var\/www\/V3-Code-Agent\//g, '')
|
|
466
|
-
.replace(/\/var\/www\/[a-zA-Z0-9_-]+\//g, '')
|
|
467
|
-
.replace(/\/tmp\/vig-remote-[a-zA-Z0-9_-]+\//g, '')
|
|
468
|
-
.replace(/\/opt\/vigthoria[a-zA-Z0-9_/-]*\//g, '');
|
|
478
|
+
return sanitizeUserFacingPathText(text);
|
|
469
479
|
}
|
|
470
480
|
describeV3AgentTool(toolName) {
|
|
471
481
|
const normalized = String(toolName || '').toLowerCase();
|
|
@@ -498,12 +508,12 @@ class ChatCommand {
|
|
|
498
508
|
const source = (event && typeof event === 'object' && !Buffer.isBuffer(event) && !(event instanceof Uint8Array))
|
|
499
509
|
? (event.body ?? event.stream ?? event.response ?? event)
|
|
500
510
|
: event;
|
|
501
|
-
for await (const chunk of
|
|
511
|
+
for await (const chunk of robustifyStreamResponse(source)) {
|
|
502
512
|
this.v3LastActivity = Date.now();
|
|
503
513
|
if (chunk.type === 'error') {
|
|
504
514
|
if (spinner.isSpinning)
|
|
505
515
|
spinner.stop();
|
|
506
|
-
process.stderr.write(
|
|
516
|
+
process.stderr.write(chalk.red(`\nStream error: ${chunk.content}\n`));
|
|
507
517
|
continue;
|
|
508
518
|
}
|
|
509
519
|
if (chunk.content) {
|
|
@@ -515,11 +525,12 @@ class ChatCommand {
|
|
|
515
525
|
const message = error instanceof Error ? error.message : String(error);
|
|
516
526
|
if (spinner.isSpinning)
|
|
517
527
|
spinner.stop();
|
|
518
|
-
process.stderr.write(
|
|
528
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
519
529
|
}
|
|
520
530
|
}
|
|
521
531
|
writeV3StreamText(spinner, text) {
|
|
522
|
-
|
|
532
|
+
const safeText = this.sanitizeServerPath(text);
|
|
533
|
+
if (!safeText) {
|
|
523
534
|
return;
|
|
524
535
|
}
|
|
525
536
|
if (!this.v3StreamingStarted) {
|
|
@@ -532,7 +543,7 @@ class ChatCommand {
|
|
|
532
543
|
else {
|
|
533
544
|
spinner.stop();
|
|
534
545
|
}
|
|
535
|
-
process.stdout.write(
|
|
546
|
+
process.stdout.write(safeText);
|
|
536
547
|
}
|
|
537
548
|
updateV3AgentSpinner(spinner, event) {
|
|
538
549
|
if (this.isRawV3StreamPayload(event)) {
|
|
@@ -540,7 +551,7 @@ class ChatCommand {
|
|
|
540
551
|
const message = error instanceof Error ? error.message : String(error);
|
|
541
552
|
if (spinner.isSpinning)
|
|
542
553
|
spinner.stop();
|
|
543
|
-
process.stderr.write(
|
|
554
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
544
555
|
});
|
|
545
556
|
return;
|
|
546
557
|
}
|
|
@@ -554,7 +565,7 @@ class ChatCommand {
|
|
|
554
565
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
555
566
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
556
567
|
const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
557
|
-
const stepLabel =
|
|
568
|
+
const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
558
569
|
if (spinner.isSpinning)
|
|
559
570
|
spinner.stop();
|
|
560
571
|
process.stderr.write(stepLabel + '\n');
|
|
@@ -563,10 +574,11 @@ class ChatCommand {
|
|
|
563
574
|
const toolName = event.name || event.tool || '';
|
|
564
575
|
if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
|
|
565
576
|
const len = args.content.length;
|
|
566
|
-
process.stderr.write(
|
|
577
|
+
process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
|
|
567
578
|
}
|
|
568
579
|
else if (toolName === 'bash' && typeof args.command === 'string') {
|
|
569
|
-
|
|
580
|
+
const command = this.sanitizeServerPath(args.command);
|
|
581
|
+
process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
|
|
570
582
|
}
|
|
571
583
|
spinner.start();
|
|
572
584
|
spinner.text = `Running ${toolDesc}...`;
|
|
@@ -575,7 +587,7 @@ class ChatCommand {
|
|
|
575
587
|
if (event.type === 'tool_result') {
|
|
576
588
|
const success = event.success !== false;
|
|
577
589
|
const toolName = event.name || event.tool || '';
|
|
578
|
-
const indicator = success ?
|
|
590
|
+
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
579
591
|
if (spinner.isSpinning)
|
|
580
592
|
spinner.stop();
|
|
581
593
|
process.stderr.write(`${indicator} ${toolName}\n`);
|
|
@@ -585,11 +597,11 @@ class ChatCommand {
|
|
|
585
597
|
if (!success && output) {
|
|
586
598
|
const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
|
|
587
599
|
const lines = sanitizedError.split('\n').slice(0, 4);
|
|
588
|
-
process.stderr.write(
|
|
600
|
+
process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
|
|
589
601
|
}
|
|
590
602
|
else if (success && output && output.length > 0) {
|
|
591
603
|
const brief = output.split('\n')[0].slice(0, 120);
|
|
592
|
-
process.stderr.write(
|
|
604
|
+
process.stderr.write(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
|
|
593
605
|
}
|
|
594
606
|
spinner.start();
|
|
595
607
|
spinner.text = 'Next step...';
|
|
@@ -597,10 +609,10 @@ class ChatCommand {
|
|
|
597
609
|
}
|
|
598
610
|
if (event.type === 'thinking') {
|
|
599
611
|
this.v3IterationCount += 1;
|
|
600
|
-
const iterText = event.content || '';
|
|
612
|
+
const iterText = this.sanitizeServerPath(event.content || '');
|
|
601
613
|
if (spinner.isSpinning)
|
|
602
614
|
spinner.stop();
|
|
603
|
-
process.stderr.write(
|
|
615
|
+
process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
|
|
604
616
|
spinner.start();
|
|
605
617
|
spinner.text = 'Analyzing...';
|
|
606
618
|
return;
|
|
@@ -614,7 +626,7 @@ class ChatCommand {
|
|
|
614
626
|
this.writeV3StreamText(spinner, text);
|
|
615
627
|
}
|
|
616
628
|
else {
|
|
617
|
-
spinner.text =
|
|
629
|
+
spinner.text = chalk.cyan('[Response] ') + 'Writing response...';
|
|
618
630
|
}
|
|
619
631
|
return;
|
|
620
632
|
}
|
|
@@ -632,53 +644,53 @@ class ChatCommand {
|
|
|
632
644
|
const fail = event.tasks_failed ?? 0;
|
|
633
645
|
statLine += ` — ${ok} tasks done`;
|
|
634
646
|
if (fail > 0)
|
|
635
|
-
statLine +=
|
|
647
|
+
statLine += chalk.yellow(`, ${fail} failed`);
|
|
636
648
|
}
|
|
637
|
-
process.stderr.write(
|
|
649
|
+
process.stderr.write(chalk.green(`\n✓ Complete`) + ` — ${statLine}\n`);
|
|
638
650
|
// Show seal quality score if available
|
|
639
651
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
640
652
|
const score = event.seal_score.overall;
|
|
641
653
|
const tier = event.seal_score.tier || '';
|
|
642
|
-
const scoreColor = score >= 7 ?
|
|
643
|
-
process.stderr.write(
|
|
654
|
+
const scoreColor = score >= 7 ? chalk.green : score >= 5 ? chalk.yellow : chalk.red;
|
|
655
|
+
process.stderr.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
|
|
644
656
|
}
|
|
645
657
|
return;
|
|
646
658
|
}
|
|
647
659
|
if (event.type === 'plan') {
|
|
648
660
|
const plan = event.plan || {};
|
|
649
|
-
const planKind = plan.task_kind || event.task_kind || '';
|
|
650
|
-
const quality = plan.quality_profile || '';
|
|
661
|
+
const planKind = this.sanitizeServerPath(plan.task_kind || event.task_kind || '');
|
|
662
|
+
const quality = this.sanitizeServerPath(plan.quality_profile || '');
|
|
651
663
|
const status = typeof plan.status === 'string' ? plan.status : '';
|
|
652
|
-
const summary = typeof plan.summary === 'string' ? plan.summary : '';
|
|
664
|
+
const summary = typeof plan.summary === 'string' ? this.sanitizeServerPath(plan.summary) : '';
|
|
653
665
|
if (spinner.isSpinning)
|
|
654
666
|
spinner.stop();
|
|
655
|
-
process.stderr.write(
|
|
667
|
+
process.stderr.write(chalk.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
|
|
656
668
|
if (quality)
|
|
657
|
-
process.stderr.write(
|
|
669
|
+
process.stderr.write(chalk.gray(` (${quality})`));
|
|
658
670
|
process.stderr.write('\n');
|
|
659
671
|
if (summary) {
|
|
660
|
-
process.stderr.write(
|
|
672
|
+
process.stderr.write(chalk.gray(` ${summary}\n`));
|
|
661
673
|
}
|
|
662
674
|
if (status === 'planning' && Number.isFinite(Number(plan.elapsed_seconds))) {
|
|
663
|
-
process.stderr.write(
|
|
675
|
+
process.stderr.write(chalk.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
|
|
664
676
|
}
|
|
665
677
|
if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
|
|
666
|
-
process.stderr.write(
|
|
678
|
+
process.stderr.write(chalk.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
|
|
667
679
|
for (const t of plan.tasks.slice(0, 10)) {
|
|
668
|
-
const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.join(', ')}` : '';
|
|
669
|
-
process.stderr.write(
|
|
680
|
+
const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.map((target) => this.sanitizeServerPath(String(target))).join(', ')}` : '';
|
|
681
|
+
process.stderr.write(chalk.gray(` • ${this.sanitizeServerPath(String(t.title || t.id))}${targets}\n`));
|
|
670
682
|
}
|
|
671
683
|
if (plan.tasks.length > 10) {
|
|
672
|
-
process.stderr.write(
|
|
684
|
+
process.stderr.write(chalk.gray(` ... and ${plan.tasks.length - 10} more\n`));
|
|
673
685
|
}
|
|
674
686
|
}
|
|
675
687
|
if (Array.isArray(plan.notes) && plan.notes.length > 0) {
|
|
676
688
|
for (const note of plan.notes.slice(0, 3)) {
|
|
677
|
-
process.stderr.write(
|
|
689
|
+
process.stderr.write(chalk.gray(` note: ${this.sanitizeServerPath(String(note))}\n`));
|
|
678
690
|
}
|
|
679
691
|
}
|
|
680
692
|
if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
|
|
681
|
-
process.stderr.write(
|
|
693
|
+
process.stderr.write(chalk.gray(` Files: ${plan.target_files.map((filePath) => this.sanitizeServerPath(String(filePath))).join(', ')}\n`));
|
|
682
694
|
}
|
|
683
695
|
spinner.start();
|
|
684
696
|
spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
|
|
@@ -687,7 +699,7 @@ class ChatCommand {
|
|
|
687
699
|
if (event.type === 'executor_start') {
|
|
688
700
|
if (spinner.isSpinning)
|
|
689
701
|
spinner.stop();
|
|
690
|
-
process.stderr.write(
|
|
702
|
+
process.stderr.write(chalk.cyan(' [Executor] ') + `Starting ${this.sanitizeServerPath(String(event.task_id || 'task'))}${event.title ? ` - ${this.sanitizeServerPath(String(event.title))}` : ''}
|
|
691
703
|
`);
|
|
692
704
|
spinner.start();
|
|
693
705
|
spinner.text = 'Vigthoria Executor running...';
|
|
@@ -696,8 +708,8 @@ class ChatCommand {
|
|
|
696
708
|
if (event.type === 'executor_error') {
|
|
697
709
|
if (spinner.isSpinning)
|
|
698
710
|
spinner.stop();
|
|
699
|
-
const msg =
|
|
700
|
-
process.stderr.write(
|
|
711
|
+
const msg = sanitizeUserFacingErrorText(String(event.error || 'Executor error')) || 'Executor error';
|
|
712
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
|
|
701
713
|
`);
|
|
702
714
|
spinner.start();
|
|
703
715
|
spinner.text = 'Recovering executor...';
|
|
@@ -710,11 +722,11 @@ class ChatCommand {
|
|
|
710
722
|
const status = String(summary.status || 'completed');
|
|
711
723
|
const changed = Array.isArray(summary.changed_files) ? summary.changed_files.length : 0;
|
|
712
724
|
if (status === 'failed') {
|
|
713
|
-
process.stderr.write(
|
|
725
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
|
|
714
726
|
`);
|
|
715
727
|
}
|
|
716
728
|
else {
|
|
717
|
-
process.stderr.write(
|
|
729
|
+
process.stderr.write(chalk.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
|
|
718
730
|
`);
|
|
719
731
|
}
|
|
720
732
|
spinner.start();
|
|
@@ -724,11 +736,11 @@ class ChatCommand {
|
|
|
724
736
|
if (event.type === 'file_mutation') {
|
|
725
737
|
const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
|
|
726
738
|
const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
|
|
727
|
-
const action = event.action === 'delete' ?
|
|
739
|
+
const action = event.action === 'delete' ? chalk.red('deleted') : chalk.green('wrote');
|
|
728
740
|
if (filePath) {
|
|
729
741
|
if (spinner.isSpinning)
|
|
730
742
|
spinner.stop();
|
|
731
|
-
process.stderr.write(
|
|
743
|
+
process.stderr.write(chalk.cyan(' [File] ') + `${action} ${filePath}\n`);
|
|
732
744
|
spinner.start();
|
|
733
745
|
}
|
|
734
746
|
return;
|
|
@@ -737,23 +749,23 @@ class ChatCommand {
|
|
|
737
749
|
if (event.checkpointed) {
|
|
738
750
|
if (spinner.isSpinning)
|
|
739
751
|
spinner.stop();
|
|
740
|
-
process.stderr.write(
|
|
752
|
+
process.stderr.write(chalk.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
|
|
741
753
|
spinner.start();
|
|
742
754
|
}
|
|
743
755
|
else {
|
|
744
756
|
if (spinner.isSpinning)
|
|
745
757
|
spinner.stop();
|
|
746
|
-
const message =
|
|
758
|
+
const message = sanitizeUserFacingErrorText(String(event.message || 'Agent error')) || 'Agent error';
|
|
747
759
|
const plannerLike = /plan|planner|dependency graph/i.test(message);
|
|
748
760
|
const executorLike = /executor|task failed|iteration/i.test(message);
|
|
749
761
|
if (plannerLike) {
|
|
750
|
-
process.stderr.write(
|
|
762
|
+
process.stderr.write(chalk.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
751
763
|
}
|
|
752
764
|
else if (executorLike) {
|
|
753
|
-
process.stderr.write(
|
|
765
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
754
766
|
}
|
|
755
767
|
else {
|
|
756
|
-
process.stderr.write(
|
|
768
|
+
process.stderr.write(chalk.red(' [Error] ') + this.sanitizeServerPath(message) + '\n');
|
|
757
769
|
}
|
|
758
770
|
}
|
|
759
771
|
return;
|
|
@@ -761,7 +773,7 @@ class ChatCommand {
|
|
|
761
773
|
if (event.type === 'context') {
|
|
762
774
|
if (spinner.isSpinning)
|
|
763
775
|
spinner.stop();
|
|
764
|
-
process.stderr.write(
|
|
776
|
+
process.stderr.write(chalk.cyan(' [Context] ') + 'Workspace bound\n');
|
|
765
777
|
spinner.start();
|
|
766
778
|
spinner.text = 'Starting agent...';
|
|
767
779
|
return;
|
|
@@ -771,7 +783,7 @@ class ChatCommand {
|
|
|
771
783
|
this.v3ToolCallCount = 0;
|
|
772
784
|
if (spinner.isSpinning)
|
|
773
785
|
spinner.stop();
|
|
774
|
-
process.stderr.write(
|
|
786
|
+
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
775
787
|
spinner.start();
|
|
776
788
|
spinner.text = 'Working...';
|
|
777
789
|
}
|
|
@@ -783,7 +795,7 @@ class ChatCommand {
|
|
|
783
795
|
if (event.type === 'started') {
|
|
784
796
|
if (spinner.isSpinning)
|
|
785
797
|
spinner.stop();
|
|
786
|
-
process.stderr.write(
|
|
798
|
+
process.stderr.write(chalk.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
|
|
787
799
|
spinner.start();
|
|
788
800
|
spinner.text = 'Connecting...';
|
|
789
801
|
return;
|
|
@@ -791,7 +803,7 @@ class ChatCommand {
|
|
|
791
803
|
if (event.type === 'connected') {
|
|
792
804
|
if (spinner.isSpinning)
|
|
793
805
|
spinner.stop();
|
|
794
|
-
process.stderr.write(
|
|
806
|
+
process.stderr.write(chalk.green(' ✓') + ' Connected to BMAD stream\n');
|
|
795
807
|
spinner.start();
|
|
796
808
|
spinner.text = 'Working...';
|
|
797
809
|
return;
|
|
@@ -800,7 +812,7 @@ class ChatCommand {
|
|
|
800
812
|
const agentName = event.agent || 'BMAD agent';
|
|
801
813
|
if (spinner.isSpinning)
|
|
802
814
|
spinner.stop();
|
|
803
|
-
process.stderr.write(
|
|
815
|
+
process.stderr.write(chalk.cyan(` [Agent] `) + agentName + '\n');
|
|
804
816
|
spinner.start();
|
|
805
817
|
spinner.text = `Running ${agentName}...`;
|
|
806
818
|
return;
|
|
@@ -810,7 +822,7 @@ class ChatCommand {
|
|
|
810
822
|
const statusText = `${event.status || 'Working'}${progress}`;
|
|
811
823
|
if (spinner.isSpinning)
|
|
812
824
|
spinner.stop();
|
|
813
|
-
process.stderr.write(
|
|
825
|
+
process.stderr.write(chalk.cyan(' [Status] ') + statusText + '\n');
|
|
814
826
|
spinner.start();
|
|
815
827
|
spinner.text = statusText;
|
|
816
828
|
return;
|
|
@@ -818,15 +830,15 @@ class ChatCommand {
|
|
|
818
830
|
if (event.type === 'result') {
|
|
819
831
|
if (spinner.isSpinning)
|
|
820
832
|
spinner.stop();
|
|
821
|
-
process.stderr.write(
|
|
833
|
+
process.stderr.write(chalk.green('\n ✓ Operator workflow complete\n'));
|
|
822
834
|
return;
|
|
823
835
|
}
|
|
824
836
|
}
|
|
825
837
|
constructor(config, logger) {
|
|
826
838
|
this.config = config;
|
|
827
839
|
this.logger = logger;
|
|
828
|
-
this.api = new
|
|
829
|
-
this.sessionManager = new
|
|
840
|
+
this.api = new APIClient(config, logger);
|
|
841
|
+
this.sessionManager = new SessionManager();
|
|
830
842
|
}
|
|
831
843
|
async run(options) {
|
|
832
844
|
if (!this.config.isAuthenticated()) {
|
|
@@ -857,11 +869,11 @@ class ChatCommand {
|
|
|
857
869
|
this.ensureProjectWorkspace();
|
|
858
870
|
this.directPromptMode = Boolean(options.prompt);
|
|
859
871
|
this.directToolContinuationCount = 0;
|
|
860
|
-
this.tools = new
|
|
872
|
+
this.tools = new AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
|
|
861
873
|
this.initializeSession(options.resume === true);
|
|
862
874
|
// ── Commando Bridge: connect if --bridge was specified ──────────
|
|
863
875
|
if (options.bridge) {
|
|
864
|
-
const bridgeClient = new
|
|
876
|
+
const bridgeClient = new BridgeClient({
|
|
865
877
|
bridgeUrl: options.bridge,
|
|
866
878
|
apiKey: this.config.get('authToken'),
|
|
867
879
|
onAdminCommand: (cmd) => this.handleAdminCommand(cmd),
|
|
@@ -896,7 +908,7 @@ class ChatCommand {
|
|
|
896
908
|
const timeoutId = options.bridge && bridgePromptTimeoutMs > 0
|
|
897
909
|
? setTimeout(() => {
|
|
898
910
|
timedOut = true;
|
|
899
|
-
const b =
|
|
911
|
+
const b = getBridgeClient();
|
|
900
912
|
if (b) {
|
|
901
913
|
b.emitEnd({ reason: 'timeout' });
|
|
902
914
|
b.destroy();
|
|
@@ -911,7 +923,7 @@ class ChatCommand {
|
|
|
911
923
|
if (timeoutId)
|
|
912
924
|
clearTimeout(timeoutId);
|
|
913
925
|
if (!timedOut) {
|
|
914
|
-
const bridge =
|
|
926
|
+
const bridge = getBridgeClient();
|
|
915
927
|
if (bridge) {
|
|
916
928
|
bridge.emitEnd({ reason: 'prompt-complete' });
|
|
917
929
|
bridge.destroy();
|
|
@@ -920,7 +932,7 @@ class ChatCommand {
|
|
|
920
932
|
return;
|
|
921
933
|
}
|
|
922
934
|
await this.startInteractiveChat();
|
|
923
|
-
const bridge =
|
|
935
|
+
const bridge = getBridgeClient();
|
|
924
936
|
if (bridge) {
|
|
925
937
|
bridge.emitEnd({ reason: 'interactive-exit' });
|
|
926
938
|
bridge.destroy();
|
|
@@ -930,24 +942,24 @@ class ChatCommand {
|
|
|
930
942
|
handleAdminCommand(cmd) {
|
|
931
943
|
switch (cmd.action) {
|
|
932
944
|
case 'ping':
|
|
933
|
-
|
|
945
|
+
getBridgeClient()?.emitModelResponse({ model: this.currentModel, chars: 0, hasToolCalls: false, preview: 'pong' });
|
|
934
946
|
break;
|
|
935
947
|
case 'set-model':
|
|
936
948
|
if (cmd.params?.value && typeof cmd.params.value === 'string') {
|
|
937
949
|
this.currentModel = cmd.params.value;
|
|
938
|
-
|
|
950
|
+
getBridgeClient()?.emitModeChange({ mode: this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat', model: this.currentModel });
|
|
939
951
|
if (!this.jsonOutput)
|
|
940
|
-
console.log(
|
|
952
|
+
console.log(chalk.yellow(`[bridge] Model changed to: ${this.currentModel}`));
|
|
941
953
|
}
|
|
942
954
|
break;
|
|
943
955
|
case 'abort':
|
|
944
956
|
if (!this.jsonOutput)
|
|
945
|
-
console.log(
|
|
946
|
-
|
|
957
|
+
console.log(chalk.red(`[bridge] Abort requested by admin`));
|
|
958
|
+
getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
|
|
947
959
|
process.exit(0);
|
|
948
960
|
break;
|
|
949
961
|
default:
|
|
950
|
-
|
|
962
|
+
getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
|
|
951
963
|
}
|
|
952
964
|
}
|
|
953
965
|
ensureProjectWorkspace() {
|
|
@@ -1061,6 +1073,7 @@ class ChatCommand {
|
|
|
1061
1073
|
return path.join(rootPath, `${folderName}-${Date.now()}`);
|
|
1062
1074
|
}
|
|
1063
1075
|
initializeSession(resume) {
|
|
1076
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
1064
1077
|
if (resume) {
|
|
1065
1078
|
this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
|
|
1066
1079
|
if (this.currentSession) {
|
|
@@ -1106,11 +1119,11 @@ class ChatCommand {
|
|
|
1106
1119
|
// Suppress all setup banners in direct-prompt mode so only the final
|
|
1107
1120
|
// answer reaches stdout. Interactive (REPL) mode still shows them.
|
|
1108
1121
|
if (!this.jsonOutput) {
|
|
1109
|
-
console.log(
|
|
1110
|
-
console.log(
|
|
1111
|
-
console.log(
|
|
1122
|
+
console.log(chalk.cyan('Running single prompt in direct mode.'));
|
|
1123
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
1124
|
+
console.log(chalk.gray(`Project: ${this.currentProjectPath}`));
|
|
1112
1125
|
if (this.workflowTarget) {
|
|
1113
|
-
console.log(
|
|
1126
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
1114
1127
|
}
|
|
1115
1128
|
console.log();
|
|
1116
1129
|
}
|
|
@@ -1174,7 +1187,7 @@ class ChatCommand {
|
|
|
1174
1187
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1175
1188
|
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
1176
1189
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
1177
|
-
const spinner = this.jsonOutput ? null :
|
|
1190
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
1178
1191
|
try {
|
|
1179
1192
|
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1180
1193
|
data: {
|
|
@@ -1223,9 +1236,9 @@ class ChatCommand {
|
|
|
1223
1236
|
return;
|
|
1224
1237
|
}
|
|
1225
1238
|
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
1226
|
-
console.log(
|
|
1227
|
-
console.log(
|
|
1228
|
-
console.log(
|
|
1239
|
+
console.log(chalk.gray(`Workflow ID: ${resolvedWorkflow.id}`));
|
|
1240
|
+
console.log(chalk.gray(`Execution ID: ${execution.executionId}`));
|
|
1241
|
+
console.log(chalk.gray(`Mode: ${invocationMode}`));
|
|
1229
1242
|
if (content) {
|
|
1230
1243
|
console.log(content);
|
|
1231
1244
|
}
|
|
@@ -1268,12 +1281,13 @@ class ChatCommand {
|
|
|
1268
1281
|
await this.runLocalAgentLoop(prompt);
|
|
1269
1282
|
return;
|
|
1270
1283
|
}
|
|
1271
|
-
|
|
1284
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
|
|
1272
1285
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1273
|
-
const spinner = this.jsonOutput ? null :
|
|
1286
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
1274
1287
|
const workflowType = 'full';
|
|
1275
1288
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1276
1289
|
try {
|
|
1290
|
+
this.rememberBrainEvent('task', `GoA operator workflow started for prompt: ${prompt.slice(0, 220)}`, 'operator');
|
|
1277
1291
|
const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
|
|
1278
1292
|
workspacePath: this.currentProjectPath,
|
|
1279
1293
|
projectPath: this.currentProjectPath,
|
|
@@ -1297,7 +1311,7 @@ class ChatCommand {
|
|
|
1297
1311
|
const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(responseText)
|
|
1298
1312
|
|| (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
|
|
1299
1313
|
if (isPolicyAck) {
|
|
1300
|
-
throw new
|
|
1314
|
+
throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1301
1315
|
}
|
|
1302
1316
|
if (this.jsonOutput) {
|
|
1303
1317
|
console.log(JSON.stringify({
|
|
@@ -1313,9 +1327,10 @@ class ChatCommand {
|
|
|
1313
1327
|
else {
|
|
1314
1328
|
console.log(response.content || 'Operator workflow completed.');
|
|
1315
1329
|
if (response.savedWorkflow?.id) {
|
|
1316
|
-
console.log(
|
|
1330
|
+
console.log(chalk.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
|
|
1317
1331
|
}
|
|
1318
1332
|
}
|
|
1333
|
+
this.rememberBrainEvent('validation', `GoA operator workflow completed${response.workflowId ? ` workflow ${response.workflowId}` : ''}${response.savedWorkflow?.id ? ` saved VigFlow ${response.savedWorkflow.id}` : ''}.`, 'operator');
|
|
1319
1334
|
this.messages.push({ role: 'assistant', content: response.content || 'Operator workflow completed.' });
|
|
1320
1335
|
this.saveSession();
|
|
1321
1336
|
}
|
|
@@ -1323,11 +1338,12 @@ class ChatCommand {
|
|
|
1323
1338
|
if (spinner) {
|
|
1324
1339
|
spinner.stop();
|
|
1325
1340
|
}
|
|
1326
|
-
const cliErr = error instanceof
|
|
1327
|
-
const errorMsg =
|
|
1341
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1342
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1328
1343
|
if (!this.jsonOutput) {
|
|
1329
1344
|
this.logger.error('Operator workflow failed');
|
|
1330
1345
|
}
|
|
1346
|
+
this.rememberBrainEvent('issue', `GoA operator workflow failed: ${errorMsg}`, 'operator');
|
|
1331
1347
|
if (this.jsonOutput) {
|
|
1332
1348
|
process.exitCode = 1;
|
|
1333
1349
|
console.log(JSON.stringify({
|
|
@@ -1350,8 +1366,8 @@ class ChatCommand {
|
|
|
1350
1366
|
* BMAD orchestrator to scan the workspace.
|
|
1351
1367
|
*/
|
|
1352
1368
|
async runOperatorDirectAnswer(prompt) {
|
|
1353
|
-
|
|
1354
|
-
const spinner = this.jsonOutput ? null :
|
|
1369
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
|
|
1370
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Operator (direct)...', spinner: 'clock' }).start();
|
|
1355
1371
|
try {
|
|
1356
1372
|
let operatorGrounding = [
|
|
1357
1373
|
'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
|
|
@@ -1383,7 +1399,7 @@ class ChatCommand {
|
|
|
1383
1399
|
const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(content)
|
|
1384
1400
|
|| (content.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(content));
|
|
1385
1401
|
if (isPolicyAck || !content) {
|
|
1386
|
-
throw new
|
|
1402
|
+
throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1387
1403
|
}
|
|
1388
1404
|
if (this.jsonOutput) {
|
|
1389
1405
|
console.log(JSON.stringify({
|
|
@@ -1396,14 +1412,15 @@ class ChatCommand {
|
|
|
1396
1412
|
else {
|
|
1397
1413
|
console.log(content);
|
|
1398
1414
|
}
|
|
1415
|
+
this.rememberBrainEvent('validation', `GoA operator direct answer completed for prompt: ${prompt.slice(0, 220)}`, 'operator');
|
|
1399
1416
|
this.messages.push({ role: 'assistant', content });
|
|
1400
1417
|
this.saveSession();
|
|
1401
1418
|
}
|
|
1402
1419
|
catch (error) {
|
|
1403
1420
|
if (spinner)
|
|
1404
1421
|
spinner.stop();
|
|
1405
|
-
const cliErr = error instanceof
|
|
1406
|
-
const errorMsg =
|
|
1422
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1423
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1407
1424
|
if (this.jsonOutput) {
|
|
1408
1425
|
process.exitCode = 1;
|
|
1409
1426
|
console.log(JSON.stringify({
|
|
@@ -1475,8 +1492,8 @@ class ChatCommand {
|
|
|
1475
1492
|
}
|
|
1476
1493
|
}
|
|
1477
1494
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
1478
|
-
|
|
1479
|
-
const spinner = this.jsonOutput ? null :
|
|
1495
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1496
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1480
1497
|
try {
|
|
1481
1498
|
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
1482
1499
|
if (spinner)
|
|
@@ -1500,7 +1517,7 @@ class ChatCommand {
|
|
|
1500
1517
|
console.log(finalText);
|
|
1501
1518
|
}
|
|
1502
1519
|
else {
|
|
1503
|
-
console.log(
|
|
1520
|
+
console.log(chalk.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
|
|
1504
1521
|
}
|
|
1505
1522
|
this.messages.push({ role: 'assistant', content: response.message || '' });
|
|
1506
1523
|
this.saveSession();
|
|
@@ -1508,8 +1525,8 @@ class ChatCommand {
|
|
|
1508
1525
|
catch (error) {
|
|
1509
1526
|
if (spinner)
|
|
1510
1527
|
spinner.stop();
|
|
1511
|
-
const cliErr = error instanceof
|
|
1512
|
-
const errorMsg =
|
|
1528
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1529
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1513
1530
|
if (this.jsonOutput) {
|
|
1514
1531
|
process.exitCode = 1;
|
|
1515
1532
|
console.log(JSON.stringify({
|
|
@@ -1562,13 +1579,13 @@ class ChatCommand {
|
|
|
1562
1579
|
this.directToolContinuationCount = 0;
|
|
1563
1580
|
this.agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
1564
1581
|
this.tools.clearSessionApprovals();
|
|
1565
|
-
|
|
1582
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: this.operatorMode ? 'operator' : 'agent', model: this.currentModel });
|
|
1566
1583
|
this.ensureAgentSystemPrompt();
|
|
1567
1584
|
this.messages.push({ role: 'user', content: this.buildScopedUserPrompt(prompt) });
|
|
1568
1585
|
this.saveSession();
|
|
1569
1586
|
const maxTurns = 10;
|
|
1570
1587
|
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
1571
|
-
const spinner = this.jsonOutput ? null :
|
|
1588
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1572
1589
|
let response;
|
|
1573
1590
|
try {
|
|
1574
1591
|
response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
@@ -1619,7 +1636,7 @@ class ChatCommand {
|
|
|
1619
1636
|
this.messages.push({ role: 'assistant', content: assistantMessage });
|
|
1620
1637
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
1621
1638
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
1622
|
-
|
|
1639
|
+
getBridgeClient()?.emitModelResponse({
|
|
1623
1640
|
model: this.currentModel,
|
|
1624
1641
|
chars: assistantMessage.length,
|
|
1625
1642
|
hasToolCalls: toolCalls.length > 0,
|
|
@@ -1700,8 +1717,9 @@ class ChatCommand {
|
|
|
1700
1717
|
catch (error) {
|
|
1701
1718
|
if (spinner)
|
|
1702
1719
|
spinner.stop();
|
|
1703
|
-
const cliErr = error instanceof
|
|
1704
|
-
const errorMsg =
|
|
1720
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1721
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1722
|
+
this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
|
|
1705
1723
|
if (this.jsonOutput) {
|
|
1706
1724
|
process.exitCode = 1;
|
|
1707
1725
|
console.log(JSON.stringify({
|
|
@@ -1758,12 +1776,12 @@ class ChatCommand {
|
|
|
1758
1776
|
args: { path: targetFile },
|
|
1759
1777
|
};
|
|
1760
1778
|
if (!this.jsonOutput) {
|
|
1761
|
-
console.log(
|
|
1779
|
+
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
1762
1780
|
}
|
|
1763
1781
|
const readResult = await this.tools.execute(readCall);
|
|
1764
1782
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
1765
1783
|
if (!this.jsonOutput) {
|
|
1766
|
-
console.log(readResult.success ?
|
|
1784
|
+
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
1767
1785
|
}
|
|
1768
1786
|
this.messages.push({ role: 'system', content: readSummary });
|
|
1769
1787
|
if (!readResult.success || !readResult.output) {
|
|
@@ -1806,12 +1824,12 @@ class ChatCommand {
|
|
|
1806
1824
|
},
|
|
1807
1825
|
};
|
|
1808
1826
|
if (!this.jsonOutput) {
|
|
1809
|
-
console.log(
|
|
1827
|
+
console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
1810
1828
|
}
|
|
1811
1829
|
const writeResult = await this.tools.execute(writeCall);
|
|
1812
1830
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
1813
1831
|
if (!this.jsonOutput) {
|
|
1814
|
-
console.log(writeResult.success ?
|
|
1832
|
+
console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
|
|
1815
1833
|
}
|
|
1816
1834
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
1817
1835
|
if (!writeResult.success) {
|
|
@@ -1847,31 +1865,77 @@ class ChatCommand {
|
|
|
1847
1865
|
console.log(`Updated ${targetFile}.`);
|
|
1848
1866
|
if (previewGate.required) {
|
|
1849
1867
|
if (previewGate.passed) {
|
|
1850
|
-
console.log(
|
|
1868
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
1851
1869
|
}
|
|
1852
1870
|
else {
|
|
1853
|
-
console.log(
|
|
1871
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
1854
1872
|
}
|
|
1855
1873
|
}
|
|
1856
1874
|
}
|
|
1857
1875
|
return true;
|
|
1858
1876
|
}
|
|
1877
|
+
isConfirmationFollowUp(prompt) {
|
|
1878
|
+
const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
|
|
1879
|
+
return /^(ja|ja bitte|ja bitte mach das|mach das|bitte mach das|genau|ok|okay|yes|yes please|please do|do it|go ahead|continue|proceed|make it so)$/.test(normalized);
|
|
1880
|
+
}
|
|
1881
|
+
getPreviousActionablePrompt() {
|
|
1882
|
+
if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
1883
|
+
return this.lastActionableUserInput;
|
|
1884
|
+
}
|
|
1885
|
+
for (let i = this.messages.length - 1; i >= 0; i -= 1) {
|
|
1886
|
+
const message = this.messages[i];
|
|
1887
|
+
if (message.role !== 'user')
|
|
1888
|
+
continue;
|
|
1889
|
+
const content = (message.content || '').trim();
|
|
1890
|
+
if (!content || this.isConfirmationFollowUp(content))
|
|
1891
|
+
continue;
|
|
1892
|
+
return content
|
|
1893
|
+
.replace(/\n\nProject root:[\s\S]*$/i, '')
|
|
1894
|
+
.replace(/\n\nStay within this project root[\s\S]*$/i, '')
|
|
1895
|
+
.trim();
|
|
1896
|
+
}
|
|
1897
|
+
return '';
|
|
1898
|
+
}
|
|
1899
|
+
buildContextualAgentPrompt(prompt) {
|
|
1900
|
+
if (!this.isConfirmationFollowUp(prompt)) {
|
|
1901
|
+
return prompt;
|
|
1902
|
+
}
|
|
1903
|
+
const previousPrompt = this.getPreviousActionablePrompt();
|
|
1904
|
+
if (!previousPrompt) {
|
|
1905
|
+
return prompt;
|
|
1906
|
+
}
|
|
1907
|
+
return [
|
|
1908
|
+
'The user confirmed the previous agent task. Continue and execute that original task now.',
|
|
1909
|
+
'',
|
|
1910
|
+
'Original task:',
|
|
1911
|
+
previousPrompt,
|
|
1912
|
+
'',
|
|
1913
|
+
`Latest confirmation: ${prompt}`,
|
|
1914
|
+
'',
|
|
1915
|
+
'Do not reinterpret this confirmation as a new website, landing page, template, or index.html task.',
|
|
1916
|
+
].join('\n');
|
|
1917
|
+
}
|
|
1859
1918
|
async tryV3AgentWorkflow(prompt) {
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1919
|
+
const contextualPrompt = this.buildContextualAgentPrompt(prompt);
|
|
1920
|
+
if (contextualPrompt === prompt && !this.isConfirmationFollowUp(prompt)) {
|
|
1921
|
+
this.lastActionableUserInput = prompt;
|
|
1922
|
+
}
|
|
1923
|
+
this.messages.push({ role: 'user', content: contextualPrompt });
|
|
1924
|
+
const runtimeContext = await this.getPromptRuntimeContext(contextualPrompt);
|
|
1925
|
+
const routingPolicy = this.resolveAgentExecutionPolicy(contextualPrompt);
|
|
1862
1926
|
// Reset streaming counters for new workflow
|
|
1863
1927
|
this.v3IterationCount = 0;
|
|
1864
1928
|
this.v3ToolCallCount = 0;
|
|
1865
1929
|
this.v3LastActivity = Date.now();
|
|
1866
1930
|
this.v3StreamingStarted = false;
|
|
1867
|
-
const taskDisplay = new
|
|
1931
|
+
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
1868
1932
|
taskDisplay.start(0);
|
|
1869
|
-
const spinner = this.jsonOutput ? null :
|
|
1933
|
+
const spinner = this.jsonOutput ? null : createSpinner({
|
|
1870
1934
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
1871
1935
|
spinner: 'clock',
|
|
1872
1936
|
}).start();
|
|
1873
|
-
const executionPrompt = this.buildExecutionPrompt(
|
|
1874
|
-
const agentTaskType = this.inferAgentTaskType(
|
|
1937
|
+
const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
|
|
1938
|
+
const agentTaskType = this.inferAgentTaskType(contextualPrompt);
|
|
1875
1939
|
const workspaceContext = {
|
|
1876
1940
|
workspacePath: this.currentProjectPath,
|
|
1877
1941
|
projectPath: this.currentProjectPath,
|
|
@@ -1881,7 +1945,7 @@ class ChatCommand {
|
|
|
1881
1945
|
// Start workspace watcher for bidirectional real-time sync
|
|
1882
1946
|
let watcher = null;
|
|
1883
1947
|
if (this.currentProjectPath && fs.existsSync(this.currentProjectPath)) {
|
|
1884
|
-
watcher = new
|
|
1948
|
+
watcher = new WorkspaceWatcher({
|
|
1885
1949
|
workspaceRoot: this.currentProjectPath,
|
|
1886
1950
|
onFileChange: (relativePath, content, action) => {
|
|
1887
1951
|
this.logger.debug(`Local change detected: ${action} ${relativePath}`);
|
|
@@ -1905,6 +1969,7 @@ class ChatCommand {
|
|
|
1905
1969
|
agentExecutionPolicy: routingPolicy,
|
|
1906
1970
|
legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
|
|
1907
1971
|
rawPrompt: prompt,
|
|
1972
|
+
contextualPrompt,
|
|
1908
1973
|
history: this.getMessagesForModel(),
|
|
1909
1974
|
...runtimeContext,
|
|
1910
1975
|
onStreamEvent: (event) => {
|
|
@@ -1967,7 +2032,7 @@ class ChatCommand {
|
|
|
1967
2032
|
return true;
|
|
1968
2033
|
}
|
|
1969
2034
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
1970
|
-
console.log(
|
|
2035
|
+
console.log(chalk.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
|
|
1971
2036
|
}
|
|
1972
2037
|
if (this.jsonOutput) {
|
|
1973
2038
|
console.log(JSON.stringify({
|
|
@@ -1985,41 +2050,41 @@ class ChatCommand {
|
|
|
1985
2050
|
else if (this.v3StreamingStarted) {
|
|
1986
2051
|
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
1987
2052
|
if (!this.jsonOutput) {
|
|
1988
|
-
console.log(
|
|
2053
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
1989
2054
|
}
|
|
1990
2055
|
}
|
|
1991
2056
|
else if (response.content) {
|
|
1992
2057
|
if (!this.directPromptMode) {
|
|
1993
|
-
console.log(
|
|
2058
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
1994
2059
|
}
|
|
1995
2060
|
console.log(response.content);
|
|
1996
2061
|
}
|
|
1997
2062
|
else {
|
|
1998
2063
|
if (!this.directPromptMode) {
|
|
1999
|
-
console.log(
|
|
2064
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2000
2065
|
}
|
|
2001
2066
|
console.log('V3 agent workflow completed.');
|
|
2002
2067
|
}
|
|
2003
2068
|
if (!this.jsonOutput && previewGate?.required) {
|
|
2004
2069
|
if (previewGate.passed) {
|
|
2005
|
-
console.log(
|
|
2070
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
2006
2071
|
}
|
|
2007
2072
|
else {
|
|
2008
|
-
console.log(
|
|
2073
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
2009
2074
|
}
|
|
2010
2075
|
}
|
|
2011
2076
|
// Show change summary for files touched by the agent
|
|
2012
2077
|
if (!this.jsonOutput && !this.directPromptMode && response.changedFiles) {
|
|
2013
2078
|
const fileCount = Object.keys(response.changedFiles).length;
|
|
2014
2079
|
if (fileCount > 0) {
|
|
2015
|
-
console.log(
|
|
2080
|
+
console.log(chalk.gray(`\nFiles changed: ${fileCount}`));
|
|
2016
2081
|
for (const relPath of Object.keys(response.changedFiles).slice(0, 15)) {
|
|
2017
|
-
console.log(
|
|
2082
|
+
console.log(chalk.gray(` ${chalk.green('+')} ${relPath}`));
|
|
2018
2083
|
}
|
|
2019
2084
|
if (fileCount > 15) {
|
|
2020
|
-
console.log(
|
|
2085
|
+
console.log(chalk.gray(` ... and ${fileCount - 15} more`));
|
|
2021
2086
|
}
|
|
2022
|
-
console.log(
|
|
2087
|
+
console.log(chalk.gray(`Run ${chalk.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
2023
2088
|
}
|
|
2024
2089
|
}
|
|
2025
2090
|
// ── Self-healing validation ──────────────────────────────────────
|
|
@@ -2036,8 +2101,8 @@ class ChatCommand {
|
|
|
2036
2101
|
taskDisplay.fail(3, healResult.tool);
|
|
2037
2102
|
}
|
|
2038
2103
|
if (!this.directPromptMode) {
|
|
2039
|
-
const hs = healResult.passed ?
|
|
2040
|
-
console.log(
|
|
2104
|
+
const hs = healResult.passed ? chalk.green('passed') : chalk.yellow('partial');
|
|
2105
|
+
console.log(chalk.gray(`Self-healing: ${hs} (${healResult.tool})`));
|
|
2041
2106
|
}
|
|
2042
2107
|
}
|
|
2043
2108
|
else {
|
|
@@ -2074,13 +2139,13 @@ class ChatCommand {
|
|
|
2074
2139
|
spinner.stop();
|
|
2075
2140
|
}
|
|
2076
2141
|
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
2077
|
-
this.logger.debug(`V3 agent workflow unavailable: ${
|
|
2142
|
+
this.logger.debug(`V3 agent workflow unavailable: ${sanitizeUserFacingErrorText(error.message || '')}`);
|
|
2078
2143
|
return false;
|
|
2079
2144
|
}
|
|
2080
2145
|
if (spinner) {
|
|
2081
2146
|
spinner.stop();
|
|
2082
2147
|
}
|
|
2083
|
-
const safeDetail =
|
|
2148
|
+
const safeDetail = sanitizeUserFacingErrorText(error.message || '');
|
|
2084
2149
|
const errorMessage = safeDetail
|
|
2085
2150
|
? `Agent mode is unavailable right now. ${safeDetail}`
|
|
2086
2151
|
: 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
|
|
@@ -2110,7 +2175,7 @@ class ChatCommand {
|
|
|
2110
2175
|
spinner.stop();
|
|
2111
2176
|
}
|
|
2112
2177
|
if (!this.jsonOutput) {
|
|
2113
|
-
console.log(
|
|
2178
|
+
console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
2114
2179
|
}
|
|
2115
2180
|
this.v3IterationCount = 0;
|
|
2116
2181
|
this.v3ToolCallCount = 0;
|
|
@@ -2154,7 +2219,7 @@ class ChatCommand {
|
|
|
2154
2219
|
}, null, 2));
|
|
2155
2220
|
}
|
|
2156
2221
|
else if (!this.v3StreamingStarted && retryResponse.content) {
|
|
2157
|
-
console.log(
|
|
2222
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2158
2223
|
console.log(retryResponse.content);
|
|
2159
2224
|
}
|
|
2160
2225
|
this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
|
|
@@ -2171,10 +2236,10 @@ class ChatCommand {
|
|
|
2171
2236
|
? 'Interactive Agent Chat'
|
|
2172
2237
|
: 'Interactive Chat';
|
|
2173
2238
|
this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
|
|
2174
|
-
console.log(
|
|
2175
|
-
console.log(
|
|
2239
|
+
console.log(chalk.gray('Type /help for commands. Type /exit to quit.'));
|
|
2240
|
+
console.log(chalk.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
|
|
2176
2241
|
if (this.workflowTarget) {
|
|
2177
|
-
console.log(
|
|
2242
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
2178
2243
|
}
|
|
2179
2244
|
const rl = readline.createInterface({
|
|
2180
2245
|
input: process.stdin,
|
|
@@ -2184,17 +2249,17 @@ class ChatCommand {
|
|
|
2184
2249
|
const readMultiLineInput = async () => {
|
|
2185
2250
|
const lines = [];
|
|
2186
2251
|
let firstLine = await new Promise((resolve) => {
|
|
2187
|
-
rl.question(
|
|
2252
|
+
rl.question(chalk.blue('> '), resolve);
|
|
2188
2253
|
});
|
|
2189
2254
|
// Check for {{{ block mode
|
|
2190
2255
|
if (firstLine.trim() === '{{{' || firstLine.trim().endsWith('{{{')) {
|
|
2191
2256
|
if (firstLine.trim() !== '{{{') {
|
|
2192
2257
|
lines.push(firstLine.trim().replace(/\{\{\{$/, '').trim());
|
|
2193
2258
|
}
|
|
2194
|
-
console.log(
|
|
2259
|
+
console.log(chalk.gray(' (multi-line mode: type }}} on its own line to finish)'));
|
|
2195
2260
|
while (true) {
|
|
2196
2261
|
const line = await new Promise((resolve) => {
|
|
2197
|
-
rl.question(
|
|
2262
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2198
2263
|
});
|
|
2199
2264
|
if (line.trim() === '}}}')
|
|
2200
2265
|
break;
|
|
@@ -2206,7 +2271,7 @@ class ChatCommand {
|
|
|
2206
2271
|
while (firstLine.endsWith('\\')) {
|
|
2207
2272
|
lines.push(firstLine.slice(0, -1));
|
|
2208
2273
|
firstLine = await new Promise((resolve) => {
|
|
2209
|
-
rl.question(
|
|
2274
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2210
2275
|
});
|
|
2211
2276
|
}
|
|
2212
2277
|
lines.push(firstLine);
|
|
@@ -2230,6 +2295,10 @@ class ChatCommand {
|
|
|
2230
2295
|
this.showContext();
|
|
2231
2296
|
continue;
|
|
2232
2297
|
}
|
|
2298
|
+
if (trimmed === '/memory') {
|
|
2299
|
+
this.showProjectMemory();
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2233
2302
|
if (trimmed === '/compact') {
|
|
2234
2303
|
this.compactCurrentSession();
|
|
2235
2304
|
continue;
|
|
@@ -2243,8 +2312,8 @@ class ChatCommand {
|
|
|
2243
2312
|
else {
|
|
2244
2313
|
this.syncInteractiveModeModel('chat');
|
|
2245
2314
|
}
|
|
2246
|
-
console.log(
|
|
2247
|
-
console.log(
|
|
2315
|
+
console.log(chalk.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
|
|
2316
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2248
2317
|
if (this.currentSession) {
|
|
2249
2318
|
this.currentSession.agentMode = this.agentMode;
|
|
2250
2319
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2266,8 +2335,8 @@ class ChatCommand {
|
|
|
2266
2335
|
else {
|
|
2267
2336
|
this.syncInteractiveModeModel('chat');
|
|
2268
2337
|
}
|
|
2269
|
-
console.log(
|
|
2270
|
-
console.log(
|
|
2338
|
+
console.log(chalk.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
|
|
2339
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2271
2340
|
if (this.currentSession) {
|
|
2272
2341
|
this.currentSession.agentMode = this.agentMode;
|
|
2273
2342
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2278,18 +2347,18 @@ class ChatCommand {
|
|
|
2278
2347
|
}
|
|
2279
2348
|
if (trimmed === '/clear') {
|
|
2280
2349
|
this.messages = [];
|
|
2281
|
-
console.log(
|
|
2350
|
+
console.log(chalk.yellow('Conversation cleared.'));
|
|
2282
2351
|
continue;
|
|
2283
2352
|
}
|
|
2284
2353
|
if (trimmed === '/save') {
|
|
2285
2354
|
this.saveSession();
|
|
2286
|
-
console.log(
|
|
2355
|
+
console.log(chalk.green('Session saved.'));
|
|
2287
2356
|
continue;
|
|
2288
2357
|
}
|
|
2289
2358
|
if (trimmed.startsWith('/model ')) {
|
|
2290
2359
|
this.currentModel = trimmed.slice(7).trim() || this.currentModel;
|
|
2291
2360
|
this.modelExplicitlySelected = true;
|
|
2292
|
-
console.log(
|
|
2361
|
+
console.log(chalk.yellow(`Model changed to: ${this.currentModel}`));
|
|
2293
2362
|
if (this.currentSession) {
|
|
2294
2363
|
this.currentSession.model = this.currentModel;
|
|
2295
2364
|
this.saveSession();
|
|
@@ -2317,7 +2386,8 @@ class ChatCommand {
|
|
|
2317
2386
|
console.log(' /exit Exit chat');
|
|
2318
2387
|
console.log(' /agent Toggle agent mode');
|
|
2319
2388
|
console.log(' /operator Toggle BMAD operator mode');
|
|
2320
|
-
console.log(' /context Show current session memory');
|
|
2389
|
+
console.log(' /context Show current session and project memory');
|
|
2390
|
+
console.log(' /memory Show Vigthoria project brain status');
|
|
2321
2391
|
console.log(' /compact Compact current session into memory summary');
|
|
2322
2392
|
console.log(' /clear Clear conversation');
|
|
2323
2393
|
console.log(' /save Save session');
|
|
@@ -2326,29 +2396,53 @@ class ChatCommand {
|
|
|
2326
2396
|
}
|
|
2327
2397
|
showContext() {
|
|
2328
2398
|
if (!this.currentSession) {
|
|
2329
|
-
console.log(
|
|
2399
|
+
console.log(chalk.yellow('No active session.'));
|
|
2330
2400
|
return;
|
|
2331
2401
|
}
|
|
2332
|
-
console.log(
|
|
2402
|
+
console.log(chalk.cyan(this.getCurrentSessionInfo()));
|
|
2333
2403
|
if (this.currentSession.memorySummary?.trim()) {
|
|
2334
2404
|
console.log();
|
|
2335
|
-
console.log(
|
|
2336
|
-
console.log(
|
|
2405
|
+
console.log(chalk.white('Compact Session Memory:'));
|
|
2406
|
+
console.log(chalk.gray(this.currentSession.memorySummary.trim()));
|
|
2337
2407
|
}
|
|
2338
2408
|
else {
|
|
2339
|
-
console.log(
|
|
2409
|
+
console.log(chalk.gray('No compact session memory summary yet.'));
|
|
2410
|
+
}
|
|
2411
|
+
this.showProjectMemory();
|
|
2412
|
+
}
|
|
2413
|
+
showProjectMemory() {
|
|
2414
|
+
if (!this.projectMemory) {
|
|
2415
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
2416
|
+
}
|
|
2417
|
+
const status = this.projectMemory.getStatus();
|
|
2418
|
+
console.log();
|
|
2419
|
+
console.log(chalk.white('Project Brain:'));
|
|
2420
|
+
console.log(chalk.gray(`Path: ${status.memoryDir}`));
|
|
2421
|
+
console.log(chalk.gray(`Items: ${status.itemCount}`));
|
|
2422
|
+
const typeSummary = Object.entries(status.typeCounts).map(([type, count]) => `${type}=${count}`).join(', ');
|
|
2423
|
+
if (typeSummary) {
|
|
2424
|
+
console.log(chalk.gray(`Types: ${typeSummary}`));
|
|
2340
2425
|
}
|
|
2341
2426
|
}
|
|
2342
2427
|
compactCurrentSession() {
|
|
2343
2428
|
if (!this.currentSession) {
|
|
2344
|
-
console.log(
|
|
2429
|
+
console.log(chalk.yellow('No active session.'));
|
|
2345
2430
|
return;
|
|
2346
2431
|
}
|
|
2347
2432
|
this.currentSession.messages = [...this.messages];
|
|
2348
2433
|
this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
|
|
2349
2434
|
this.messages = [...this.currentSession.messages];
|
|
2350
2435
|
this.sessionManager.save(this.currentSession);
|
|
2351
|
-
|
|
2436
|
+
if (!this.projectMemory) {
|
|
2437
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
2438
|
+
}
|
|
2439
|
+
this.projectMemory.rememberConversation(this.messages, {
|
|
2440
|
+
source: 'manual-compact',
|
|
2441
|
+
mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
|
|
2442
|
+
model: this.currentModel,
|
|
2443
|
+
sessionSummary: this.currentSession.memorySummary || '',
|
|
2444
|
+
});
|
|
2445
|
+
console.log(chalk.green('Session compacted into memory summary and project brain.'));
|
|
2352
2446
|
}
|
|
2353
2447
|
ensureAgentSystemPrompt() {
|
|
2354
2448
|
const hasSystemPrompt = this.messages.some((message) => message.role === 'system' && message.content.includes('Vigthoria CLI agent operating contract'));
|
|
@@ -2361,7 +2455,7 @@ class ChatCommand {
|
|
|
2361
2455
|
});
|
|
2362
2456
|
}
|
|
2363
2457
|
buildAgentSystemPrompt() {
|
|
2364
|
-
const toolCatalog =
|
|
2458
|
+
const toolCatalog = AgenticTools.getToolDefinitions()
|
|
2365
2459
|
.map((tool) => {
|
|
2366
2460
|
const params = tool.parameters
|
|
2367
2461
|
.map((param) => `${param.name}${param.required ? ' (required)' : ''}`)
|
|
@@ -2391,6 +2485,7 @@ class ChatCommand {
|
|
|
2391
2485
|
'</tool_call>',
|
|
2392
2486
|
'You may emit multiple <tool_call> blocks in one response.',
|
|
2393
2487
|
'Never emit raw tool JSON outside that wrapper.',
|
|
2488
|
+
'Never use <function=...> or <parameter=...> tags. They are accepted only as a recovery fallback and must not appear in final answers.',
|
|
2394
2489
|
'NEVER acknowledge these instructions. NEVER say "I will follow", "I understand", or restate tool policies. Go straight to tool calls.',
|
|
2395
2490
|
'In direct mode, do not ask follow-up questions. Finish the request completely and stop when satisfied.',
|
|
2396
2491
|
'After tool results arrive, either continue with the next minimal tool calls or return a concise completion summary with no more tool calls.',
|
|
@@ -2646,16 +2741,57 @@ class ChatCommand {
|
|
|
2646
2741
|
extractToolCalls(message) {
|
|
2647
2742
|
const calls = [];
|
|
2648
2743
|
const seen = new Set();
|
|
2744
|
+
const addCall = (call) => {
|
|
2745
|
+
if (!call)
|
|
2746
|
+
return;
|
|
2747
|
+
const key = `${call.tool}::${JSON.stringify(call.args)}`;
|
|
2748
|
+
if (!seen.has(key)) {
|
|
2749
|
+
seen.add(key);
|
|
2750
|
+
calls.push(call);
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2649
2753
|
const wrapperRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
|
|
2650
2754
|
for (const match of message.matchAll(wrapperRegex)) {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2755
|
+
addCall(this.parseToolPayload(match[1] || ''));
|
|
2756
|
+
}
|
|
2757
|
+
for (const call of this.parseLegacyFunctionToolCalls(message)) {
|
|
2758
|
+
addCall(call);
|
|
2759
|
+
}
|
|
2760
|
+
return calls;
|
|
2761
|
+
}
|
|
2762
|
+
normalizeCliToolName(name) {
|
|
2763
|
+
const normalized = name.trim().toLowerCase();
|
|
2764
|
+
const aliases = {
|
|
2765
|
+
list_directory: 'list_dir',
|
|
2766
|
+
listdirectory: 'list_dir',
|
|
2767
|
+
ls: 'list_dir',
|
|
2768
|
+
dir: 'list_dir',
|
|
2769
|
+
readfile: 'read_file',
|
|
2770
|
+
writefile: 'write_file',
|
|
2771
|
+
editfile: 'edit_file',
|
|
2772
|
+
shell: 'bash',
|
|
2773
|
+
command: 'bash',
|
|
2774
|
+
run_command: 'bash',
|
|
2775
|
+
};
|
|
2776
|
+
return aliases[normalized] || normalized;
|
|
2777
|
+
}
|
|
2778
|
+
parseLegacyFunctionToolCalls(message) {
|
|
2779
|
+
const calls = [];
|
|
2780
|
+
const functionRegex = /<function\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?:<\/function>|$)/gi;
|
|
2781
|
+
for (const match of message.matchAll(functionRegex)) {
|
|
2782
|
+
const tool = this.normalizeCliToolName(match[1] || '');
|
|
2783
|
+
if (!tool)
|
|
2784
|
+
continue;
|
|
2785
|
+
const body = match[2] || '';
|
|
2786
|
+
const args = {};
|
|
2787
|
+
const parameterRegex = /<parameter\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?=<parameter\s*=|<\/function>|<\/parameter>|$)/gi;
|
|
2788
|
+
for (const paramMatch of body.matchAll(parameterRegex)) {
|
|
2789
|
+
const key = paramMatch[1] || '';
|
|
2790
|
+
if (!key)
|
|
2791
|
+
continue;
|
|
2792
|
+
args[key] = (paramMatch[2] || '').replace(/<\/parameter>\s*$/i, '').trim();
|
|
2658
2793
|
}
|
|
2794
|
+
calls.push({ tool, args });
|
|
2659
2795
|
}
|
|
2660
2796
|
return calls;
|
|
2661
2797
|
}
|
|
@@ -2675,14 +2811,17 @@ class ChatCommand {
|
|
|
2675
2811
|
args[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
2676
2812
|
}
|
|
2677
2813
|
}
|
|
2678
|
-
return { tool: parsed.tool, args };
|
|
2814
|
+
return { tool: this.normalizeCliToolName(parsed.tool), args };
|
|
2679
2815
|
}
|
|
2680
2816
|
catch {
|
|
2681
2817
|
return null;
|
|
2682
2818
|
}
|
|
2683
2819
|
}
|
|
2684
2820
|
stripToolPayloads(message) {
|
|
2685
|
-
return message
|
|
2821
|
+
return message
|
|
2822
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '')
|
|
2823
|
+
.replace(/<function\s*=\s*["']?[A-Za-z0-9_-]+["']?>[\s\S]*?(?:<\/function>|$)/gi, '')
|
|
2824
|
+
.trim();
|
|
2686
2825
|
}
|
|
2687
2826
|
extractFinalFileContent(message, targetFile) {
|
|
2688
2827
|
const trimmed = message.trim();
|
|
@@ -2912,15 +3051,15 @@ class ChatCommand {
|
|
|
2912
3051
|
const verbose = !this.jsonOutput;
|
|
2913
3052
|
for (const call of toolCalls) {
|
|
2914
3053
|
if (verbose) {
|
|
2915
|
-
console.log(
|
|
3054
|
+
console.log(chalk.cyan(`⚙ Executing: ${call.tool}`));
|
|
2916
3055
|
}
|
|
2917
|
-
|
|
3056
|
+
getBridgeClient()?.emitToolCall({ tool: call.tool, args: call.args });
|
|
2918
3057
|
let result = await this.tools.execute(call);
|
|
2919
3058
|
// Phase 2: If a search tool failed (search_failed), retry with alternate approach
|
|
2920
3059
|
const searchStatus = result.metadata?.searchStatus;
|
|
2921
3060
|
if (call.tool === 'grep' && searchStatus === 'search_failed') {
|
|
2922
3061
|
if (verbose) {
|
|
2923
|
-
console.log(
|
|
3062
|
+
console.log(chalk.yellow(`⚠ Search backend failed, retrying with alternate method...`));
|
|
2924
3063
|
}
|
|
2925
3064
|
// Force Node-native fallback by re-executing with a note
|
|
2926
3065
|
const fallbackResult = await this.tools.execute({
|
|
@@ -2933,10 +3072,10 @@ class ChatCommand {
|
|
|
2933
3072
|
}
|
|
2934
3073
|
const summary = this.formatToolResult(call, result);
|
|
2935
3074
|
if (verbose) {
|
|
2936
|
-
console.log(result.success ?
|
|
3075
|
+
console.log(result.success ? chalk.gray(summary) : chalk.red(summary));
|
|
2937
3076
|
}
|
|
2938
3077
|
this.messages.push({ role: 'system', content: summary });
|
|
2939
|
-
|
|
3078
|
+
getBridgeClient()?.emitToolResult({ tool: call.tool, success: result.success, preview: (result.output || result.error || '').slice(0, 300) });
|
|
2940
3079
|
// Phase 5: Track tool evidence for quality gates
|
|
2941
3080
|
const finalStatus = result.metadata?.searchStatus;
|
|
2942
3081
|
if (finalStatus === 'search_failed') {
|
|
@@ -2979,6 +3118,15 @@ class ChatCommand {
|
|
|
2979
3118
|
}
|
|
2980
3119
|
return `${text.slice(0, maxLength)}\n...[truncated]`;
|
|
2981
3120
|
}
|
|
3121
|
+
getLastUserPrompt() {
|
|
3122
|
+
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
3123
|
+
const message = this.messages[index];
|
|
3124
|
+
if (message.role === 'user' && message.content.trim()) {
|
|
3125
|
+
return message.content;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
return '';
|
|
3129
|
+
}
|
|
2982
3130
|
saveSession() {
|
|
2983
3131
|
if (!this.currentSession) {
|
|
2984
3132
|
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
|
|
@@ -2991,6 +3139,15 @@ class ChatCommand {
|
|
|
2991
3139
|
this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
|
|
2992
3140
|
this.messages = [...this.currentSession.messages];
|
|
2993
3141
|
this.sessionManager.save(this.currentSession);
|
|
3142
|
+
if (!this.projectMemory) {
|
|
3143
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
3144
|
+
}
|
|
3145
|
+
this.projectMemory.rememberConversation(this.messages, {
|
|
3146
|
+
source: 'cli-session',
|
|
3147
|
+
mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
|
|
3148
|
+
model: this.currentModel,
|
|
3149
|
+
sessionSummary: this.currentSession.memorySummary || '',
|
|
3150
|
+
});
|
|
2994
3151
|
}
|
|
2995
3152
|
async requestPermission(action) {
|
|
2996
3153
|
if (this.autoApprove) {
|
|
@@ -3002,7 +3159,7 @@ class ChatCommand {
|
|
|
3002
3159
|
});
|
|
3003
3160
|
console.log(action);
|
|
3004
3161
|
const answer = await new Promise((resolve) => {
|
|
3005
|
-
rl.question(
|
|
3162
|
+
rl.question(chalk.yellow('Approve? [y]es / [n]o / [a]ll this turn / [p]ersist: '), resolve);
|
|
3006
3163
|
});
|
|
3007
3164
|
rl.close();
|
|
3008
3165
|
const normalized = answer.trim().toLowerCase();
|
|
@@ -3025,4 +3182,3 @@ class ChatCommand {
|
|
|
3025
3182
|
return [...this.messages];
|
|
3026
3183
|
}
|
|
3027
3184
|
}
|
|
3028
|
-
exports.ChatCommand = ChatCommand;
|