vigthoria-cli 1.9.10 → 1.9.20
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 +4 -4
- 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 +35 -0
- package/dist/commands/chat.js +747 -256
- 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 +373 -166
- 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 +15 -1
- package/install.sh +1 -1
- 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();
|
|
@@ -96,6 +59,8 @@ class ChatCommand {
|
|
|
96
59
|
savePlanToVigFlow = false;
|
|
97
60
|
jsonOutput = false;
|
|
98
61
|
modelGovernanceFallback = null;
|
|
62
|
+
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
63
|
+
lastAgentRunOutcome = null;
|
|
99
64
|
isJwtExpirationError(error) {
|
|
100
65
|
const candidate = error;
|
|
101
66
|
const message = `${candidate?.message || ''} ${typeof candidate?.details === 'string' ? candidate.details : ''}`.toLowerCase();
|
|
@@ -143,27 +108,27 @@ class ChatCommand {
|
|
|
143
108
|
message.includes('aborted');
|
|
144
109
|
}
|
|
145
110
|
toUserFacingApiError(error, context) {
|
|
146
|
-
const classified =
|
|
111
|
+
const classified = classifyError(error);
|
|
147
112
|
const status = classified.statusCode || (this.isJwtExpirationError(error) ? 401 : 500);
|
|
148
113
|
if (this.isJwtExpirationError(error)) {
|
|
149
|
-
return new
|
|
114
|
+
return new CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
|
|
150
115
|
}
|
|
151
116
|
if (this.isTimeoutError(error)) {
|
|
152
|
-
return new
|
|
117
|
+
return new CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
|
|
153
118
|
}
|
|
154
119
|
if (this.isNetworkError(error)) {
|
|
155
|
-
return new
|
|
120
|
+
return new CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
|
|
156
121
|
}
|
|
157
|
-
const message =
|
|
158
|
-
return new
|
|
122
|
+
const message = sanitizeUserFacingErrorText(classified.message || `${context} failed`);
|
|
123
|
+
return new CLIError(message, status === 401 ? 'auth' : 'model_backend', { statusCode: status });
|
|
159
124
|
}
|
|
160
125
|
handleApiError(error, context) {
|
|
161
126
|
const userFacingError = this.toUserFacingApiError(error, context);
|
|
162
127
|
if (!this.jsonOutput) {
|
|
163
|
-
console.error(
|
|
128
|
+
console.error(chalk.red(`${context} failed: ${userFacingError.message}`));
|
|
164
129
|
}
|
|
165
130
|
const original = error && typeof error === 'object' ? error : { message: String(error) };
|
|
166
|
-
|
|
131
|
+
propagateError({
|
|
167
132
|
...original,
|
|
168
133
|
message: userFacingError.message,
|
|
169
134
|
statusCode: userFacingError.statusCode,
|
|
@@ -186,7 +151,7 @@ class ChatCommand {
|
|
|
186
151
|
catch (error) {
|
|
187
152
|
if (!this.jsonOutput) {
|
|
188
153
|
const transient = this.isTimeoutError(error) ? 'timeout' : this.isNetworkError(error) ? 'network error' : 'API error';
|
|
189
|
-
console.error(
|
|
154
|
+
console.error(chalk.red(`${context} failed with ${transient}: ${this.toUserFacingApiError(error, context).message}`));
|
|
190
155
|
}
|
|
191
156
|
lastError = error;
|
|
192
157
|
if (this.isJwtExpirationError(error)) {
|
|
@@ -329,25 +294,28 @@ class ChatCommand {
|
|
|
329
294
|
};
|
|
330
295
|
}
|
|
331
296
|
getMessagesForModel() {
|
|
332
|
-
const memoryContext = this.sessionManager.buildMemoryContext(this.currentSession);
|
|
333
297
|
const messages = [...this.messages];
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
298
|
+
const memoryContexts = [
|
|
299
|
+
this.sessionManager.buildMemoryContext(this.currentSession),
|
|
300
|
+
this.projectMemory?.buildContextForPrompt(this.getLastUserPrompt()) || '',
|
|
301
|
+
].filter((entry) => entry && entry.trim());
|
|
302
|
+
for (const memoryContext of memoryContexts) {
|
|
303
|
+
const marker = memoryContext.includes('Vigthoria project brain memory.')
|
|
304
|
+
? 'Vigthoria project brain memory.'
|
|
305
|
+
: 'Session memory summary from earlier conversation turns.';
|
|
306
|
+
const alreadyInjected = messages.some((message) => message.role === 'system' && message.content.includes(marker));
|
|
307
|
+
if (alreadyInjected) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const insertionIndex = messages.findIndex((message) => message.role !== 'system');
|
|
311
|
+
const memoryMessage = { role: 'system', content: memoryContext };
|
|
312
|
+
if (insertionIndex === -1) {
|
|
313
|
+
messages.push(memoryMessage);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
messages.splice(insertionIndex, 0, memoryMessage);
|
|
317
|
+
}
|
|
349
318
|
}
|
|
350
|
-
messages.splice(insertionIndex, 0, memoryMessage);
|
|
351
319
|
return messages;
|
|
352
320
|
}
|
|
353
321
|
isDiagnosticPrompt(prompt) {
|
|
@@ -432,18 +400,67 @@ class ChatCommand {
|
|
|
432
400
|
const shaping = this.buildTaskShapingInstructions(prompt);
|
|
433
401
|
return shaping ? `${prompt}\n\n${shaping}` : prompt;
|
|
434
402
|
}
|
|
403
|
+
isProjectBrainRuntimeDisabled() {
|
|
404
|
+
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_NO_BRAIN || process.env.VIGTHORIA_BRAIN_DISABLED || ''));
|
|
405
|
+
}
|
|
406
|
+
buildProjectBrainRuntimeContext(prompt, source) {
|
|
407
|
+
if (this.isProjectBrainRuntimeDisabled()) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
if (!this.projectMemory) {
|
|
412
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
413
|
+
}
|
|
414
|
+
const status = this.projectMemory.getStatus();
|
|
415
|
+
const context = this.projectMemory.buildContextForPrompt(prompt);
|
|
416
|
+
if (!context && status.itemCount === 0) {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
schema: 'vigthoria.project-brain.v1',
|
|
421
|
+
source,
|
|
422
|
+
memoryDir: status.memoryDir,
|
|
423
|
+
itemCount: status.itemCount,
|
|
424
|
+
updatedAt: status.updatedAt,
|
|
425
|
+
context,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
rememberBrainEvent(type, text, mode) {
|
|
433
|
+
if (this.isProjectBrainRuntimeDisabled()) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
if (!this.projectMemory) {
|
|
438
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
439
|
+
}
|
|
440
|
+
this.projectMemory.remember(type, text, { source: 'vigthoria-cli', mode, model: this.currentModel });
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
// Project Brain memory must not break chat, GoA, or operator execution.
|
|
444
|
+
}
|
|
445
|
+
}
|
|
435
446
|
async getPromptRuntimeContext(prompt) {
|
|
447
|
+
const runtimeContext = {};
|
|
448
|
+
const brainContext = this.buildProjectBrainRuntimeContext(prompt, this.operatorMode ? 'vigthoria-cli.operator' : this.agentMode ? 'vigthoria-cli.agent' : 'vigthoria-cli.chat');
|
|
449
|
+
if (brainContext) {
|
|
450
|
+
runtimeContext.vigthoriaBrain = brainContext;
|
|
451
|
+
}
|
|
436
452
|
if (!this.isBrowserTaskPrompt(prompt)) {
|
|
437
|
-
return
|
|
453
|
+
return runtimeContext;
|
|
438
454
|
}
|
|
439
455
|
const bridgeStatus = await this.callApi('Checking DevTools Bridge status', () => this.api.getDevtoolsBridgeStatus(), 0);
|
|
440
456
|
if (!this.jsonOutput && bridgeStatus.ok) {
|
|
441
|
-
console.log(
|
|
457
|
+
console.log(chalk.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
|
|
442
458
|
}
|
|
443
459
|
else if (!this.jsonOutput) {
|
|
444
|
-
console.log(
|
|
460
|
+
console.log(chalk.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
|
|
445
461
|
}
|
|
446
462
|
return {
|
|
463
|
+
...runtimeContext,
|
|
447
464
|
browserTask: true,
|
|
448
465
|
devtoolsBridgeAvailable: bridgeStatus.ok,
|
|
449
466
|
devtoolsBridgeEndpoint: bridgeStatus.endpoint,
|
|
@@ -460,12 +477,7 @@ class ChatCommand {
|
|
|
460
477
|
sanitizeServerPath(text) {
|
|
461
478
|
if (!text)
|
|
462
479
|
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, '');
|
|
480
|
+
return sanitizeUserFacingPathText(text);
|
|
469
481
|
}
|
|
470
482
|
describeV3AgentTool(toolName) {
|
|
471
483
|
const normalized = String(toolName || '').toLowerCase();
|
|
@@ -498,12 +510,12 @@ class ChatCommand {
|
|
|
498
510
|
const source = (event && typeof event === 'object' && !Buffer.isBuffer(event) && !(event instanceof Uint8Array))
|
|
499
511
|
? (event.body ?? event.stream ?? event.response ?? event)
|
|
500
512
|
: event;
|
|
501
|
-
for await (const chunk of
|
|
513
|
+
for await (const chunk of robustifyStreamResponse(source)) {
|
|
502
514
|
this.v3LastActivity = Date.now();
|
|
503
515
|
if (chunk.type === 'error') {
|
|
504
516
|
if (spinner.isSpinning)
|
|
505
517
|
spinner.stop();
|
|
506
|
-
process.stderr.write(
|
|
518
|
+
process.stderr.write(chalk.red(`\nStream error: ${chunk.content}\n`));
|
|
507
519
|
continue;
|
|
508
520
|
}
|
|
509
521
|
if (chunk.content) {
|
|
@@ -515,11 +527,12 @@ class ChatCommand {
|
|
|
515
527
|
const message = error instanceof Error ? error.message : String(error);
|
|
516
528
|
if (spinner.isSpinning)
|
|
517
529
|
spinner.stop();
|
|
518
|
-
process.stderr.write(
|
|
530
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
519
531
|
}
|
|
520
532
|
}
|
|
521
533
|
writeV3StreamText(spinner, text) {
|
|
522
|
-
|
|
534
|
+
const safeText = this.sanitizeServerPath(text);
|
|
535
|
+
if (!safeText) {
|
|
523
536
|
return;
|
|
524
537
|
}
|
|
525
538
|
if (!this.v3StreamingStarted) {
|
|
@@ -532,7 +545,7 @@ class ChatCommand {
|
|
|
532
545
|
else {
|
|
533
546
|
spinner.stop();
|
|
534
547
|
}
|
|
535
|
-
process.stdout.write(
|
|
548
|
+
process.stdout.write(safeText);
|
|
536
549
|
}
|
|
537
550
|
updateV3AgentSpinner(spinner, event) {
|
|
538
551
|
if (this.isRawV3StreamPayload(event)) {
|
|
@@ -540,7 +553,7 @@ class ChatCommand {
|
|
|
540
553
|
const message = error instanceof Error ? error.message : String(error);
|
|
541
554
|
if (spinner.isSpinning)
|
|
542
555
|
spinner.stop();
|
|
543
|
-
process.stderr.write(
|
|
556
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
544
557
|
});
|
|
545
558
|
return;
|
|
546
559
|
}
|
|
@@ -554,7 +567,7 @@ class ChatCommand {
|
|
|
554
567
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
555
568
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
556
569
|
const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
557
|
-
const stepLabel =
|
|
570
|
+
const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
558
571
|
if (spinner.isSpinning)
|
|
559
572
|
spinner.stop();
|
|
560
573
|
process.stderr.write(stepLabel + '\n');
|
|
@@ -563,10 +576,11 @@ class ChatCommand {
|
|
|
563
576
|
const toolName = event.name || event.tool || '';
|
|
564
577
|
if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
|
|
565
578
|
const len = args.content.length;
|
|
566
|
-
process.stderr.write(
|
|
579
|
+
process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
|
|
567
580
|
}
|
|
568
581
|
else if (toolName === 'bash' && typeof args.command === 'string') {
|
|
569
|
-
|
|
582
|
+
const command = this.sanitizeServerPath(args.command);
|
|
583
|
+
process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
|
|
570
584
|
}
|
|
571
585
|
spinner.start();
|
|
572
586
|
spinner.text = `Running ${toolDesc}...`;
|
|
@@ -575,7 +589,7 @@ class ChatCommand {
|
|
|
575
589
|
if (event.type === 'tool_result') {
|
|
576
590
|
const success = event.success !== false;
|
|
577
591
|
const toolName = event.name || event.tool || '';
|
|
578
|
-
const indicator = success ?
|
|
592
|
+
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
579
593
|
if (spinner.isSpinning)
|
|
580
594
|
spinner.stop();
|
|
581
595
|
process.stderr.write(`${indicator} ${toolName}\n`);
|
|
@@ -585,11 +599,11 @@ class ChatCommand {
|
|
|
585
599
|
if (!success && output) {
|
|
586
600
|
const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
|
|
587
601
|
const lines = sanitizedError.split('\n').slice(0, 4);
|
|
588
|
-
process.stderr.write(
|
|
602
|
+
process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
|
|
589
603
|
}
|
|
590
604
|
else if (success && output && output.length > 0) {
|
|
591
605
|
const brief = output.split('\n')[0].slice(0, 120);
|
|
592
|
-
process.stderr.write(
|
|
606
|
+
process.stderr.write(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
|
|
593
607
|
}
|
|
594
608
|
spinner.start();
|
|
595
609
|
spinner.text = 'Next step...';
|
|
@@ -597,10 +611,10 @@ class ChatCommand {
|
|
|
597
611
|
}
|
|
598
612
|
if (event.type === 'thinking') {
|
|
599
613
|
this.v3IterationCount += 1;
|
|
600
|
-
const iterText = event.content || '';
|
|
614
|
+
const iterText = this.sanitizeServerPath(event.content || '');
|
|
601
615
|
if (spinner.isSpinning)
|
|
602
616
|
spinner.stop();
|
|
603
|
-
process.stderr.write(
|
|
617
|
+
process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
|
|
604
618
|
spinner.start();
|
|
605
619
|
spinner.text = 'Analyzing...';
|
|
606
620
|
return;
|
|
@@ -614,7 +628,7 @@ class ChatCommand {
|
|
|
614
628
|
this.writeV3StreamText(spinner, text);
|
|
615
629
|
}
|
|
616
630
|
else {
|
|
617
|
-
spinner.text =
|
|
631
|
+
spinner.text = chalk.cyan('[Response] ') + 'Writing response...';
|
|
618
632
|
}
|
|
619
633
|
return;
|
|
620
634
|
}
|
|
@@ -632,53 +646,53 @@ class ChatCommand {
|
|
|
632
646
|
const fail = event.tasks_failed ?? 0;
|
|
633
647
|
statLine += ` — ${ok} tasks done`;
|
|
634
648
|
if (fail > 0)
|
|
635
|
-
statLine +=
|
|
649
|
+
statLine += chalk.yellow(`, ${fail} failed`);
|
|
636
650
|
}
|
|
637
|
-
process.stderr.write(
|
|
651
|
+
process.stderr.write(chalk.green(`\n✓ Complete`) + ` — ${statLine}\n`);
|
|
638
652
|
// Show seal quality score if available
|
|
639
653
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
640
654
|
const score = event.seal_score.overall;
|
|
641
655
|
const tier = event.seal_score.tier || '';
|
|
642
|
-
const scoreColor = score >= 7 ?
|
|
643
|
-
process.stderr.write(
|
|
656
|
+
const scoreColor = score >= 7 ? chalk.green : score >= 5 ? chalk.yellow : chalk.red;
|
|
657
|
+
process.stderr.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
|
|
644
658
|
}
|
|
645
659
|
return;
|
|
646
660
|
}
|
|
647
661
|
if (event.type === 'plan') {
|
|
648
662
|
const plan = event.plan || {};
|
|
649
|
-
const planKind = plan.task_kind || event.task_kind || '';
|
|
650
|
-
const quality = plan.quality_profile || '';
|
|
663
|
+
const planKind = this.sanitizeServerPath(plan.task_kind || event.task_kind || '');
|
|
664
|
+
const quality = this.sanitizeServerPath(plan.quality_profile || '');
|
|
651
665
|
const status = typeof plan.status === 'string' ? plan.status : '';
|
|
652
|
-
const summary = typeof plan.summary === 'string' ? plan.summary : '';
|
|
666
|
+
const summary = typeof plan.summary === 'string' ? this.sanitizeServerPath(plan.summary) : '';
|
|
653
667
|
if (spinner.isSpinning)
|
|
654
668
|
spinner.stop();
|
|
655
|
-
process.stderr.write(
|
|
669
|
+
process.stderr.write(chalk.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
|
|
656
670
|
if (quality)
|
|
657
|
-
process.stderr.write(
|
|
671
|
+
process.stderr.write(chalk.gray(` (${quality})`));
|
|
658
672
|
process.stderr.write('\n');
|
|
659
673
|
if (summary) {
|
|
660
|
-
process.stderr.write(
|
|
674
|
+
process.stderr.write(chalk.gray(` ${summary}\n`));
|
|
661
675
|
}
|
|
662
676
|
if (status === 'planning' && Number.isFinite(Number(plan.elapsed_seconds))) {
|
|
663
|
-
process.stderr.write(
|
|
677
|
+
process.stderr.write(chalk.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
|
|
664
678
|
}
|
|
665
679
|
if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
|
|
666
|
-
process.stderr.write(
|
|
680
|
+
process.stderr.write(chalk.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
|
|
667
681
|
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(
|
|
682
|
+
const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.map((target) => this.sanitizeServerPath(String(target))).join(', ')}` : '';
|
|
683
|
+
process.stderr.write(chalk.gray(` • ${this.sanitizeServerPath(String(t.title || t.id))}${targets}\n`));
|
|
670
684
|
}
|
|
671
685
|
if (plan.tasks.length > 10) {
|
|
672
|
-
process.stderr.write(
|
|
686
|
+
process.stderr.write(chalk.gray(` ... and ${plan.tasks.length - 10} more\n`));
|
|
673
687
|
}
|
|
674
688
|
}
|
|
675
689
|
if (Array.isArray(plan.notes) && plan.notes.length > 0) {
|
|
676
690
|
for (const note of plan.notes.slice(0, 3)) {
|
|
677
|
-
process.stderr.write(
|
|
691
|
+
process.stderr.write(chalk.gray(` note: ${this.sanitizeServerPath(String(note))}\n`));
|
|
678
692
|
}
|
|
679
693
|
}
|
|
680
694
|
if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
|
|
681
|
-
process.stderr.write(
|
|
695
|
+
process.stderr.write(chalk.gray(` Files: ${plan.target_files.map((filePath) => this.sanitizeServerPath(String(filePath))).join(', ')}\n`));
|
|
682
696
|
}
|
|
683
697
|
spinner.start();
|
|
684
698
|
spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
|
|
@@ -687,7 +701,7 @@ class ChatCommand {
|
|
|
687
701
|
if (event.type === 'executor_start') {
|
|
688
702
|
if (spinner.isSpinning)
|
|
689
703
|
spinner.stop();
|
|
690
|
-
process.stderr.write(
|
|
704
|
+
process.stderr.write(chalk.cyan(' [Executor] ') + `Starting ${this.sanitizeServerPath(String(event.task_id || 'task'))}${event.title ? ` - ${this.sanitizeServerPath(String(event.title))}` : ''}
|
|
691
705
|
`);
|
|
692
706
|
spinner.start();
|
|
693
707
|
spinner.text = 'Vigthoria Executor running...';
|
|
@@ -696,8 +710,8 @@ class ChatCommand {
|
|
|
696
710
|
if (event.type === 'executor_error') {
|
|
697
711
|
if (spinner.isSpinning)
|
|
698
712
|
spinner.stop();
|
|
699
|
-
const msg =
|
|
700
|
-
process.stderr.write(
|
|
713
|
+
const msg = sanitizeUserFacingErrorText(String(event.error || 'Executor error')) || 'Executor error';
|
|
714
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
|
|
701
715
|
`);
|
|
702
716
|
spinner.start();
|
|
703
717
|
spinner.text = 'Recovering executor...';
|
|
@@ -710,11 +724,11 @@ class ChatCommand {
|
|
|
710
724
|
const status = String(summary.status || 'completed');
|
|
711
725
|
const changed = Array.isArray(summary.changed_files) ? summary.changed_files.length : 0;
|
|
712
726
|
if (status === 'failed') {
|
|
713
|
-
process.stderr.write(
|
|
727
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
|
|
714
728
|
`);
|
|
715
729
|
}
|
|
716
730
|
else {
|
|
717
|
-
process.stderr.write(
|
|
731
|
+
process.stderr.write(chalk.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
|
|
718
732
|
`);
|
|
719
733
|
}
|
|
720
734
|
spinner.start();
|
|
@@ -724,11 +738,11 @@ class ChatCommand {
|
|
|
724
738
|
if (event.type === 'file_mutation') {
|
|
725
739
|
const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
|
|
726
740
|
const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
|
|
727
|
-
const action = event.action === 'delete' ?
|
|
741
|
+
const action = event.action === 'delete' ? chalk.red('deleted') : chalk.green('wrote');
|
|
728
742
|
if (filePath) {
|
|
729
743
|
if (spinner.isSpinning)
|
|
730
744
|
spinner.stop();
|
|
731
|
-
process.stderr.write(
|
|
745
|
+
process.stderr.write(chalk.cyan(' [File] ') + `${action} ${filePath}\n`);
|
|
732
746
|
spinner.start();
|
|
733
747
|
}
|
|
734
748
|
return;
|
|
@@ -737,23 +751,23 @@ class ChatCommand {
|
|
|
737
751
|
if (event.checkpointed) {
|
|
738
752
|
if (spinner.isSpinning)
|
|
739
753
|
spinner.stop();
|
|
740
|
-
process.stderr.write(
|
|
754
|
+
process.stderr.write(chalk.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
|
|
741
755
|
spinner.start();
|
|
742
756
|
}
|
|
743
757
|
else {
|
|
744
758
|
if (spinner.isSpinning)
|
|
745
759
|
spinner.stop();
|
|
746
|
-
const message =
|
|
760
|
+
const message = sanitizeUserFacingErrorText(String(event.message || 'Agent error')) || 'Agent error';
|
|
747
761
|
const plannerLike = /plan|planner|dependency graph/i.test(message);
|
|
748
762
|
const executorLike = /executor|task failed|iteration/i.test(message);
|
|
749
763
|
if (plannerLike) {
|
|
750
|
-
process.stderr.write(
|
|
764
|
+
process.stderr.write(chalk.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
751
765
|
}
|
|
752
766
|
else if (executorLike) {
|
|
753
|
-
process.stderr.write(
|
|
767
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
754
768
|
}
|
|
755
769
|
else {
|
|
756
|
-
process.stderr.write(
|
|
770
|
+
process.stderr.write(chalk.red(' [Error] ') + this.sanitizeServerPath(message) + '\n');
|
|
757
771
|
}
|
|
758
772
|
}
|
|
759
773
|
return;
|
|
@@ -761,7 +775,7 @@ class ChatCommand {
|
|
|
761
775
|
if (event.type === 'context') {
|
|
762
776
|
if (spinner.isSpinning)
|
|
763
777
|
spinner.stop();
|
|
764
|
-
process.stderr.write(
|
|
778
|
+
process.stderr.write(chalk.cyan(' [Context] ') + 'Workspace bound\n');
|
|
765
779
|
spinner.start();
|
|
766
780
|
spinner.text = 'Starting agent...';
|
|
767
781
|
return;
|
|
@@ -771,7 +785,7 @@ class ChatCommand {
|
|
|
771
785
|
this.v3ToolCallCount = 0;
|
|
772
786
|
if (spinner.isSpinning)
|
|
773
787
|
spinner.stop();
|
|
774
|
-
process.stderr.write(
|
|
788
|
+
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
775
789
|
spinner.start();
|
|
776
790
|
spinner.text = 'Working...';
|
|
777
791
|
}
|
|
@@ -783,7 +797,7 @@ class ChatCommand {
|
|
|
783
797
|
if (event.type === 'started') {
|
|
784
798
|
if (spinner.isSpinning)
|
|
785
799
|
spinner.stop();
|
|
786
|
-
process.stderr.write(
|
|
800
|
+
process.stderr.write(chalk.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
|
|
787
801
|
spinner.start();
|
|
788
802
|
spinner.text = 'Connecting...';
|
|
789
803
|
return;
|
|
@@ -791,7 +805,7 @@ class ChatCommand {
|
|
|
791
805
|
if (event.type === 'connected') {
|
|
792
806
|
if (spinner.isSpinning)
|
|
793
807
|
spinner.stop();
|
|
794
|
-
process.stderr.write(
|
|
808
|
+
process.stderr.write(chalk.green(' ✓') + ' Connected to BMAD stream\n');
|
|
795
809
|
spinner.start();
|
|
796
810
|
spinner.text = 'Working...';
|
|
797
811
|
return;
|
|
@@ -800,7 +814,7 @@ class ChatCommand {
|
|
|
800
814
|
const agentName = event.agent || 'BMAD agent';
|
|
801
815
|
if (spinner.isSpinning)
|
|
802
816
|
spinner.stop();
|
|
803
|
-
process.stderr.write(
|
|
817
|
+
process.stderr.write(chalk.cyan(` [Agent] `) + agentName + '\n');
|
|
804
818
|
spinner.start();
|
|
805
819
|
spinner.text = `Running ${agentName}...`;
|
|
806
820
|
return;
|
|
@@ -810,7 +824,7 @@ class ChatCommand {
|
|
|
810
824
|
const statusText = `${event.status || 'Working'}${progress}`;
|
|
811
825
|
if (spinner.isSpinning)
|
|
812
826
|
spinner.stop();
|
|
813
|
-
process.stderr.write(
|
|
827
|
+
process.stderr.write(chalk.cyan(' [Status] ') + statusText + '\n');
|
|
814
828
|
spinner.start();
|
|
815
829
|
spinner.text = statusText;
|
|
816
830
|
return;
|
|
@@ -818,15 +832,15 @@ class ChatCommand {
|
|
|
818
832
|
if (event.type === 'result') {
|
|
819
833
|
if (spinner.isSpinning)
|
|
820
834
|
spinner.stop();
|
|
821
|
-
process.stderr.write(
|
|
835
|
+
process.stderr.write(chalk.green('\n ✓ Operator workflow complete\n'));
|
|
822
836
|
return;
|
|
823
837
|
}
|
|
824
838
|
}
|
|
825
839
|
constructor(config, logger) {
|
|
826
840
|
this.config = config;
|
|
827
841
|
this.logger = logger;
|
|
828
|
-
this.api = new
|
|
829
|
-
this.sessionManager = new
|
|
842
|
+
this.api = new APIClient(config, logger);
|
|
843
|
+
this.sessionManager = new SessionManager();
|
|
830
844
|
}
|
|
831
845
|
async run(options) {
|
|
832
846
|
if (!this.config.isAuthenticated()) {
|
|
@@ -857,11 +871,11 @@ class ChatCommand {
|
|
|
857
871
|
this.ensureProjectWorkspace();
|
|
858
872
|
this.directPromptMode = Boolean(options.prompt);
|
|
859
873
|
this.directToolContinuationCount = 0;
|
|
860
|
-
this.tools = new
|
|
874
|
+
this.tools = new AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
|
|
861
875
|
this.initializeSession(options.resume === true);
|
|
862
876
|
// ── Commando Bridge: connect if --bridge was specified ──────────
|
|
863
877
|
if (options.bridge) {
|
|
864
|
-
const bridgeClient = new
|
|
878
|
+
const bridgeClient = new BridgeClient({
|
|
865
879
|
bridgeUrl: options.bridge,
|
|
866
880
|
apiKey: this.config.get('authToken'),
|
|
867
881
|
onAdminCommand: (cmd) => this.handleAdminCommand(cmd),
|
|
@@ -896,7 +910,7 @@ class ChatCommand {
|
|
|
896
910
|
const timeoutId = options.bridge && bridgePromptTimeoutMs > 0
|
|
897
911
|
? setTimeout(() => {
|
|
898
912
|
timedOut = true;
|
|
899
|
-
const b =
|
|
913
|
+
const b = getBridgeClient();
|
|
900
914
|
if (b) {
|
|
901
915
|
b.emitEnd({ reason: 'timeout' });
|
|
902
916
|
b.destroy();
|
|
@@ -911,7 +925,7 @@ class ChatCommand {
|
|
|
911
925
|
if (timeoutId)
|
|
912
926
|
clearTimeout(timeoutId);
|
|
913
927
|
if (!timedOut) {
|
|
914
|
-
const bridge =
|
|
928
|
+
const bridge = getBridgeClient();
|
|
915
929
|
if (bridge) {
|
|
916
930
|
bridge.emitEnd({ reason: 'prompt-complete' });
|
|
917
931
|
bridge.destroy();
|
|
@@ -920,7 +934,7 @@ class ChatCommand {
|
|
|
920
934
|
return;
|
|
921
935
|
}
|
|
922
936
|
await this.startInteractiveChat();
|
|
923
|
-
const bridge =
|
|
937
|
+
const bridge = getBridgeClient();
|
|
924
938
|
if (bridge) {
|
|
925
939
|
bridge.emitEnd({ reason: 'interactive-exit' });
|
|
926
940
|
bridge.destroy();
|
|
@@ -930,24 +944,24 @@ class ChatCommand {
|
|
|
930
944
|
handleAdminCommand(cmd) {
|
|
931
945
|
switch (cmd.action) {
|
|
932
946
|
case 'ping':
|
|
933
|
-
|
|
947
|
+
getBridgeClient()?.emitModelResponse({ model: this.currentModel, chars: 0, hasToolCalls: false, preview: 'pong' });
|
|
934
948
|
break;
|
|
935
949
|
case 'set-model':
|
|
936
950
|
if (cmd.params?.value && typeof cmd.params.value === 'string') {
|
|
937
951
|
this.currentModel = cmd.params.value;
|
|
938
|
-
|
|
952
|
+
getBridgeClient()?.emitModeChange({ mode: this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat', model: this.currentModel });
|
|
939
953
|
if (!this.jsonOutput)
|
|
940
|
-
console.log(
|
|
954
|
+
console.log(chalk.yellow(`[bridge] Model changed to: ${this.currentModel}`));
|
|
941
955
|
}
|
|
942
956
|
break;
|
|
943
957
|
case 'abort':
|
|
944
958
|
if (!this.jsonOutput)
|
|
945
|
-
console.log(
|
|
946
|
-
|
|
959
|
+
console.log(chalk.red(`[bridge] Abort requested by admin`));
|
|
960
|
+
getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
|
|
947
961
|
process.exit(0);
|
|
948
962
|
break;
|
|
949
963
|
default:
|
|
950
|
-
|
|
964
|
+
getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
|
|
951
965
|
}
|
|
952
966
|
}
|
|
953
967
|
ensureProjectWorkspace() {
|
|
@@ -1061,6 +1075,7 @@ class ChatCommand {
|
|
|
1061
1075
|
return path.join(rootPath, `${folderName}-${Date.now()}`);
|
|
1062
1076
|
}
|
|
1063
1077
|
initializeSession(resume) {
|
|
1078
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
1064
1079
|
if (resume) {
|
|
1065
1080
|
this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
|
|
1066
1081
|
if (this.currentSession) {
|
|
@@ -1106,11 +1121,11 @@ class ChatCommand {
|
|
|
1106
1121
|
// Suppress all setup banners in direct-prompt mode so only the final
|
|
1107
1122
|
// answer reaches stdout. Interactive (REPL) mode still shows them.
|
|
1108
1123
|
if (!this.jsonOutput) {
|
|
1109
|
-
console.log(
|
|
1110
|
-
console.log(
|
|
1111
|
-
console.log(
|
|
1124
|
+
console.log(chalk.cyan('Running single prompt in direct mode.'));
|
|
1125
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
1126
|
+
console.log(chalk.gray(`Project: ${this.currentProjectPath}`));
|
|
1112
1127
|
if (this.workflowTarget) {
|
|
1113
|
-
console.log(
|
|
1128
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
1114
1129
|
}
|
|
1115
1130
|
console.log();
|
|
1116
1131
|
}
|
|
@@ -1174,7 +1189,7 @@ class ChatCommand {
|
|
|
1174
1189
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1175
1190
|
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
1176
1191
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
1177
|
-
const spinner = this.jsonOutput ? null :
|
|
1192
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
1178
1193
|
try {
|
|
1179
1194
|
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1180
1195
|
data: {
|
|
@@ -1223,9 +1238,9 @@ class ChatCommand {
|
|
|
1223
1238
|
return;
|
|
1224
1239
|
}
|
|
1225
1240
|
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
1226
|
-
console.log(
|
|
1227
|
-
console.log(
|
|
1228
|
-
console.log(
|
|
1241
|
+
console.log(chalk.gray(`Workflow ID: ${resolvedWorkflow.id}`));
|
|
1242
|
+
console.log(chalk.gray(`Execution ID: ${execution.executionId}`));
|
|
1243
|
+
console.log(chalk.gray(`Mode: ${invocationMode}`));
|
|
1229
1244
|
if (content) {
|
|
1230
1245
|
console.log(content);
|
|
1231
1246
|
}
|
|
@@ -1268,12 +1283,13 @@ class ChatCommand {
|
|
|
1268
1283
|
await this.runLocalAgentLoop(prompt);
|
|
1269
1284
|
return;
|
|
1270
1285
|
}
|
|
1271
|
-
|
|
1286
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
|
|
1272
1287
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1273
|
-
const spinner = this.jsonOutput ? null :
|
|
1288
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
1274
1289
|
const workflowType = 'full';
|
|
1275
1290
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1276
1291
|
try {
|
|
1292
|
+
this.rememberBrainEvent('task', `GoA operator workflow started for prompt: ${prompt.slice(0, 220)}`, 'operator');
|
|
1277
1293
|
const response = await this.callApi('Run operator workflow', () => this.api.runOperatorWorkflow(executionPrompt, {
|
|
1278
1294
|
workspacePath: this.currentProjectPath,
|
|
1279
1295
|
projectPath: this.currentProjectPath,
|
|
@@ -1297,7 +1313,7 @@ class ChatCommand {
|
|
|
1297
1313
|
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
1314
|
|| (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
|
|
1299
1315
|
if (isPolicyAck) {
|
|
1300
|
-
throw new
|
|
1316
|
+
throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1301
1317
|
}
|
|
1302
1318
|
if (this.jsonOutput) {
|
|
1303
1319
|
console.log(JSON.stringify({
|
|
@@ -1313,9 +1329,10 @@ class ChatCommand {
|
|
|
1313
1329
|
else {
|
|
1314
1330
|
console.log(response.content || 'Operator workflow completed.');
|
|
1315
1331
|
if (response.savedWorkflow?.id) {
|
|
1316
|
-
console.log(
|
|
1332
|
+
console.log(chalk.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
|
|
1317
1333
|
}
|
|
1318
1334
|
}
|
|
1335
|
+
this.rememberBrainEvent('validation', `GoA operator workflow completed${response.workflowId ? ` workflow ${response.workflowId}` : ''}${response.savedWorkflow?.id ? ` saved VigFlow ${response.savedWorkflow.id}` : ''}.`, 'operator');
|
|
1319
1336
|
this.messages.push({ role: 'assistant', content: response.content || 'Operator workflow completed.' });
|
|
1320
1337
|
this.saveSession();
|
|
1321
1338
|
}
|
|
@@ -1323,11 +1340,12 @@ class ChatCommand {
|
|
|
1323
1340
|
if (spinner) {
|
|
1324
1341
|
spinner.stop();
|
|
1325
1342
|
}
|
|
1326
|
-
const cliErr = error instanceof
|
|
1327
|
-
const errorMsg =
|
|
1343
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1344
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1328
1345
|
if (!this.jsonOutput) {
|
|
1329
1346
|
this.logger.error('Operator workflow failed');
|
|
1330
1347
|
}
|
|
1348
|
+
this.rememberBrainEvent('issue', `GoA operator workflow failed: ${errorMsg}`, 'operator');
|
|
1331
1349
|
if (this.jsonOutput) {
|
|
1332
1350
|
process.exitCode = 1;
|
|
1333
1351
|
console.log(JSON.stringify({
|
|
@@ -1350,8 +1368,8 @@ class ChatCommand {
|
|
|
1350
1368
|
* BMAD orchestrator to scan the workspace.
|
|
1351
1369
|
*/
|
|
1352
1370
|
async runOperatorDirectAnswer(prompt) {
|
|
1353
|
-
|
|
1354
|
-
const spinner = this.jsonOutput ? null :
|
|
1371
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
|
|
1372
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Operator (direct)...', spinner: 'clock' }).start();
|
|
1355
1373
|
try {
|
|
1356
1374
|
let operatorGrounding = [
|
|
1357
1375
|
'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
|
|
@@ -1383,7 +1401,7 @@ class ChatCommand {
|
|
|
1383
1401
|
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
1402
|
|| (content.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(content));
|
|
1385
1403
|
if (isPolicyAck || !content) {
|
|
1386
|
-
throw new
|
|
1404
|
+
throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1387
1405
|
}
|
|
1388
1406
|
if (this.jsonOutput) {
|
|
1389
1407
|
console.log(JSON.stringify({
|
|
@@ -1396,14 +1414,15 @@ class ChatCommand {
|
|
|
1396
1414
|
else {
|
|
1397
1415
|
console.log(content);
|
|
1398
1416
|
}
|
|
1417
|
+
this.rememberBrainEvent('validation', `GoA operator direct answer completed for prompt: ${prompt.slice(0, 220)}`, 'operator');
|
|
1399
1418
|
this.messages.push({ role: 'assistant', content });
|
|
1400
1419
|
this.saveSession();
|
|
1401
1420
|
}
|
|
1402
1421
|
catch (error) {
|
|
1403
1422
|
if (spinner)
|
|
1404
1423
|
spinner.stop();
|
|
1405
|
-
const cliErr = error instanceof
|
|
1406
|
-
const errorMsg =
|
|
1424
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1425
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1407
1426
|
if (this.jsonOutput) {
|
|
1408
1427
|
process.exitCode = 1;
|
|
1409
1428
|
console.log(JSON.stringify({
|
|
@@ -1475,8 +1494,8 @@ class ChatCommand {
|
|
|
1475
1494
|
}
|
|
1476
1495
|
}
|
|
1477
1496
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
1478
|
-
|
|
1479
|
-
const spinner = this.jsonOutput ? null :
|
|
1497
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1498
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1480
1499
|
try {
|
|
1481
1500
|
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
1482
1501
|
if (spinner)
|
|
@@ -1500,7 +1519,7 @@ class ChatCommand {
|
|
|
1500
1519
|
console.log(finalText);
|
|
1501
1520
|
}
|
|
1502
1521
|
else {
|
|
1503
|
-
console.log(
|
|
1522
|
+
console.log(chalk.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
|
|
1504
1523
|
}
|
|
1505
1524
|
this.messages.push({ role: 'assistant', content: response.message || '' });
|
|
1506
1525
|
this.saveSession();
|
|
@@ -1508,8 +1527,8 @@ class ChatCommand {
|
|
|
1508
1527
|
catch (error) {
|
|
1509
1528
|
if (spinner)
|
|
1510
1529
|
spinner.stop();
|
|
1511
|
-
const cliErr = error instanceof
|
|
1512
|
-
const errorMsg =
|
|
1530
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1531
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1513
1532
|
if (this.jsonOutput) {
|
|
1514
1533
|
process.exitCode = 1;
|
|
1515
1534
|
console.log(JSON.stringify({
|
|
@@ -1562,13 +1581,13 @@ class ChatCommand {
|
|
|
1562
1581
|
this.directToolContinuationCount = 0;
|
|
1563
1582
|
this.agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
1564
1583
|
this.tools.clearSessionApprovals();
|
|
1565
|
-
|
|
1584
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: this.operatorMode ? 'operator' : 'agent', model: this.currentModel });
|
|
1566
1585
|
this.ensureAgentSystemPrompt();
|
|
1567
1586
|
this.messages.push({ role: 'user', content: this.buildScopedUserPrompt(prompt) });
|
|
1568
1587
|
this.saveSession();
|
|
1569
1588
|
const maxTurns = 10;
|
|
1570
1589
|
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
1571
|
-
const spinner = this.jsonOutput ? null :
|
|
1590
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1572
1591
|
let response;
|
|
1573
1592
|
try {
|
|
1574
1593
|
response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
@@ -1619,7 +1638,7 @@ class ChatCommand {
|
|
|
1619
1638
|
this.messages.push({ role: 'assistant', content: assistantMessage });
|
|
1620
1639
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
1621
1640
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
1622
|
-
|
|
1641
|
+
getBridgeClient()?.emitModelResponse({
|
|
1623
1642
|
model: this.currentModel,
|
|
1624
1643
|
chars: assistantMessage.length,
|
|
1625
1644
|
hasToolCalls: toolCalls.length > 0,
|
|
@@ -1700,8 +1719,9 @@ class ChatCommand {
|
|
|
1700
1719
|
catch (error) {
|
|
1701
1720
|
if (spinner)
|
|
1702
1721
|
spinner.stop();
|
|
1703
|
-
const cliErr = error instanceof
|
|
1704
|
-
const errorMsg =
|
|
1722
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1723
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1724
|
+
this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
|
|
1705
1725
|
if (this.jsonOutput) {
|
|
1706
1726
|
process.exitCode = 1;
|
|
1707
1727
|
console.log(JSON.stringify({
|
|
@@ -1758,12 +1778,12 @@ class ChatCommand {
|
|
|
1758
1778
|
args: { path: targetFile },
|
|
1759
1779
|
};
|
|
1760
1780
|
if (!this.jsonOutput) {
|
|
1761
|
-
console.log(
|
|
1781
|
+
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
1762
1782
|
}
|
|
1763
1783
|
const readResult = await this.tools.execute(readCall);
|
|
1764
1784
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
1765
1785
|
if (!this.jsonOutput) {
|
|
1766
|
-
console.log(readResult.success ?
|
|
1786
|
+
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
1767
1787
|
}
|
|
1768
1788
|
this.messages.push({ role: 'system', content: readSummary });
|
|
1769
1789
|
if (!readResult.success || !readResult.output) {
|
|
@@ -1806,12 +1826,12 @@ class ChatCommand {
|
|
|
1806
1826
|
},
|
|
1807
1827
|
};
|
|
1808
1828
|
if (!this.jsonOutput) {
|
|
1809
|
-
console.log(
|
|
1829
|
+
console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
1810
1830
|
}
|
|
1811
1831
|
const writeResult = await this.tools.execute(writeCall);
|
|
1812
1832
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
1813
1833
|
if (!this.jsonOutput) {
|
|
1814
|
-
console.log(writeResult.success ?
|
|
1834
|
+
console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
|
|
1815
1835
|
}
|
|
1816
1836
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
1817
1837
|
if (!writeResult.success) {
|
|
@@ -1847,31 +1867,131 @@ class ChatCommand {
|
|
|
1847
1867
|
console.log(`Updated ${targetFile}.`);
|
|
1848
1868
|
if (previewGate.required) {
|
|
1849
1869
|
if (previewGate.passed) {
|
|
1850
|
-
console.log(
|
|
1870
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
1851
1871
|
}
|
|
1852
1872
|
else {
|
|
1853
|
-
console.log(
|
|
1873
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
1854
1874
|
}
|
|
1855
1875
|
}
|
|
1856
1876
|
}
|
|
1857
1877
|
return true;
|
|
1858
1878
|
}
|
|
1879
|
+
isConfirmationFollowUp(prompt) {
|
|
1880
|
+
const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
|
|
1881
|
+
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);
|
|
1882
|
+
}
|
|
1883
|
+
getPreviousActionablePrompt() {
|
|
1884
|
+
if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
1885
|
+
return this.lastActionableUserInput;
|
|
1886
|
+
}
|
|
1887
|
+
for (let i = this.messages.length - 1; i >= 0; i -= 1) {
|
|
1888
|
+
const message = this.messages[i];
|
|
1889
|
+
if (message.role !== 'user')
|
|
1890
|
+
continue;
|
|
1891
|
+
const content = (message.content || '').trim();
|
|
1892
|
+
if (!content || this.isConfirmationFollowUp(content))
|
|
1893
|
+
continue;
|
|
1894
|
+
return content
|
|
1895
|
+
.replace(/\n\nProject root:[\s\S]*$/i, '')
|
|
1896
|
+
.replace(/\n\nStay within this project root[\s\S]*$/i, '')
|
|
1897
|
+
.trim();
|
|
1898
|
+
}
|
|
1899
|
+
return '';
|
|
1900
|
+
}
|
|
1901
|
+
buildContextualAgentPrompt(prompt) {
|
|
1902
|
+
if (!this.isConfirmationFollowUp(prompt)) {
|
|
1903
|
+
return prompt;
|
|
1904
|
+
}
|
|
1905
|
+
const previousPrompt = this.getPreviousActionablePrompt();
|
|
1906
|
+
if (!previousPrompt) {
|
|
1907
|
+
return prompt;
|
|
1908
|
+
}
|
|
1909
|
+
return [
|
|
1910
|
+
'The user confirmed the previous agent task. Continue and execute that original task now.',
|
|
1911
|
+
'',
|
|
1912
|
+
'Original task:',
|
|
1913
|
+
previousPrompt,
|
|
1914
|
+
'',
|
|
1915
|
+
`Latest confirmation: ${prompt}`,
|
|
1916
|
+
'',
|
|
1917
|
+
'Do not reinterpret this confirmation as a new website, landing page, template, or index.html task.',
|
|
1918
|
+
].join('\n');
|
|
1919
|
+
}
|
|
1859
1920
|
async tryV3AgentWorkflow(prompt) {
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1921
|
+
const contextualPrompt = this.buildContextualAgentPrompt(prompt);
|
|
1922
|
+
if (contextualPrompt === prompt && !this.isConfirmationFollowUp(prompt)) {
|
|
1923
|
+
this.lastActionableUserInput = prompt;
|
|
1924
|
+
}
|
|
1925
|
+
this.messages.push({ role: 'user', content: contextualPrompt });
|
|
1926
|
+
const runtimeContext = await this.getPromptRuntimeContext(contextualPrompt);
|
|
1927
|
+
const routingPolicy = this.resolveAgentExecutionPolicy(contextualPrompt);
|
|
1862
1928
|
// Reset streaming counters for new workflow
|
|
1863
1929
|
this.v3IterationCount = 0;
|
|
1864
1930
|
this.v3ToolCallCount = 0;
|
|
1865
1931
|
this.v3LastActivity = Date.now();
|
|
1866
1932
|
this.v3StreamingStarted = false;
|
|
1867
|
-
const taskDisplay = new
|
|
1933
|
+
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
1868
1934
|
taskDisplay.start(0);
|
|
1869
|
-
const spinner = this.jsonOutput ? null :
|
|
1935
|
+
const spinner = this.jsonOutput ? null : createSpinner({
|
|
1870
1936
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
1871
1937
|
spinner: 'clock',
|
|
1872
1938
|
}).start();
|
|
1873
|
-
|
|
1874
|
-
const
|
|
1939
|
+
// Live run telemetry, used both for the final summary block and for /retry, /continue.
|
|
1940
|
+
const liveOutcome = {
|
|
1941
|
+
tasksSucceeded: 0,
|
|
1942
|
+
tasksTotal: 0,
|
|
1943
|
+
failedTaskIds: new Set(),
|
|
1944
|
+
unfinishedTaskIds: new Set(),
|
|
1945
|
+
qualityScore: null,
|
|
1946
|
+
qualityMissing: [],
|
|
1947
|
+
qualityBlockers: [],
|
|
1948
|
+
plannerError: null,
|
|
1949
|
+
executorError: null,
|
|
1950
|
+
executorFailed: false,
|
|
1951
|
+
};
|
|
1952
|
+
const parsePlannerSummary = (raw) => {
|
|
1953
|
+
if (!raw || typeof raw !== 'string')
|
|
1954
|
+
return;
|
|
1955
|
+
const succ = raw.match(/(\d+)\s*\/\s*(\d+)\s+tasks?\s+succeeded/i);
|
|
1956
|
+
if (succ) {
|
|
1957
|
+
liveOutcome.tasksSucceeded = Number(succ[1]) || 0;
|
|
1958
|
+
liveOutcome.tasksTotal = Number(succ[2]) || 0;
|
|
1959
|
+
}
|
|
1960
|
+
const failed = raw.match(/Failed\s+tasks?:\s*([^.\n]+)/i);
|
|
1961
|
+
if (failed) {
|
|
1962
|
+
failed[1].split(/[,\s]+/).map(s => s.trim()).filter(Boolean)
|
|
1963
|
+
.forEach(t => liveOutcome.failedTaskIds.add(t));
|
|
1964
|
+
}
|
|
1965
|
+
const unfinished = raw.match(/Unfinished\s+tasks?:\s*([^.\n]+)/i);
|
|
1966
|
+
if (unfinished) {
|
|
1967
|
+
unfinished[1].split(/[,\s]+/).map(s => s.trim()).filter(Boolean)
|
|
1968
|
+
.forEach(t => liveOutcome.unfinishedTaskIds.add(t));
|
|
1969
|
+
}
|
|
1970
|
+
const qScore = raw.match(/Quality\s+audit:\s*([0-9.]+)\s*\/\s*100/i);
|
|
1971
|
+
if (qScore) {
|
|
1972
|
+
const n = Number(qScore[1]);
|
|
1973
|
+
if (Number.isFinite(n))
|
|
1974
|
+
liveOutcome.qualityScore = n;
|
|
1975
|
+
}
|
|
1976
|
+
const blockers = raw.match(/Quality\s+blockers?:\s*([^\n]+?)(?:\s+Quality\s+audit|$)/i);
|
|
1977
|
+
if (blockers) {
|
|
1978
|
+
blockers[1].split(/;/).map(s => s.trim()).filter(Boolean)
|
|
1979
|
+
.forEach(b => {
|
|
1980
|
+
if (!liveOutcome.qualityBlockers.includes(b))
|
|
1981
|
+
liveOutcome.qualityBlockers.push(b);
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
const missingFeatures = raw.match(/Missing\s+features?:\s*([^\n]+)/i);
|
|
1985
|
+
if (missingFeatures) {
|
|
1986
|
+
missingFeatures[1].split(/[,;]/).map(s => s.trim()).filter(Boolean)
|
|
1987
|
+
.forEach(m => {
|
|
1988
|
+
if (!liveOutcome.qualityMissing.includes(m))
|
|
1989
|
+
liveOutcome.qualityMissing.push(m);
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
const executionPrompt = this.buildExecutionPrompt(contextualPrompt);
|
|
1994
|
+
const agentTaskType = this.inferAgentTaskType(contextualPrompt);
|
|
1875
1995
|
const workspaceContext = {
|
|
1876
1996
|
workspacePath: this.currentProjectPath,
|
|
1877
1997
|
projectPath: this.currentProjectPath,
|
|
@@ -1881,7 +2001,7 @@ class ChatCommand {
|
|
|
1881
2001
|
// Start workspace watcher for bidirectional real-time sync
|
|
1882
2002
|
let watcher = null;
|
|
1883
2003
|
if (this.currentProjectPath && fs.existsSync(this.currentProjectPath)) {
|
|
1884
|
-
watcher = new
|
|
2004
|
+
watcher = new WorkspaceWatcher({
|
|
1885
2005
|
workspaceRoot: this.currentProjectPath,
|
|
1886
2006
|
onFileChange: (relativePath, content, action) => {
|
|
1887
2007
|
this.logger.debug(`Local change detected: ${action} ${relativePath}`);
|
|
@@ -1905,16 +2025,52 @@ class ChatCommand {
|
|
|
1905
2025
|
agentExecutionPolicy: routingPolicy,
|
|
1906
2026
|
legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
|
|
1907
2027
|
rawPrompt: prompt,
|
|
2028
|
+
contextualPrompt,
|
|
1908
2029
|
history: this.getMessagesForModel(),
|
|
1909
2030
|
...runtimeContext,
|
|
1910
2031
|
onStreamEvent: (event) => {
|
|
1911
2032
|
if (event.type === 'plan') {
|
|
1912
2033
|
taskDisplay.complete(0);
|
|
1913
2034
|
taskDisplay.start(1);
|
|
2035
|
+
const tasks = event?.plan?.tasks;
|
|
2036
|
+
if (Array.isArray(tasks))
|
|
2037
|
+
liveOutcome.tasksTotal = tasks.length;
|
|
1914
2038
|
}
|
|
1915
2039
|
else if (event.type === 'executor_start') {
|
|
1916
2040
|
taskDisplay.start(1);
|
|
1917
2041
|
}
|
|
2042
|
+
else if (event.type === 'executor_complete') {
|
|
2043
|
+
const summary = event.summary || {};
|
|
2044
|
+
const tid = summary.task_id || summary.id;
|
|
2045
|
+
if (summary.status === 'failed') {
|
|
2046
|
+
if (tid)
|
|
2047
|
+
liveOutcome.failedTaskIds.add(String(tid));
|
|
2048
|
+
liveOutcome.executorFailed = true;
|
|
2049
|
+
}
|
|
2050
|
+
else if (summary.status === 'completed' || summary.status === 'success') {
|
|
2051
|
+
if (tid)
|
|
2052
|
+
liveOutcome.failedTaskIds.delete(String(tid));
|
|
2053
|
+
liveOutcome.tasksSucceeded += 1;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
else if (event.type === 'executor_error') {
|
|
2057
|
+
const msg = typeof event.error === 'string' ? event.error : '';
|
|
2058
|
+
if (msg)
|
|
2059
|
+
liveOutcome.executorError = msg;
|
|
2060
|
+
}
|
|
2061
|
+
else if (event.type === 'error') {
|
|
2062
|
+
const msg = typeof event.message === 'string' ? event.message : '';
|
|
2063
|
+
if (/plan|planner|dependency graph/i.test(msg)) {
|
|
2064
|
+
if (!liveOutcome.plannerError)
|
|
2065
|
+
liveOutcome.plannerError = msg;
|
|
2066
|
+
parsePlannerSummary(msg);
|
|
2067
|
+
}
|
|
2068
|
+
else if (/executor|task failed|iteration/i.test(msg)) {
|
|
2069
|
+
if (!liveOutcome.executorError)
|
|
2070
|
+
liveOutcome.executorError = msg;
|
|
2071
|
+
parsePlannerSummary(msg);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
1918
2074
|
else if (event.type === 'complete') {
|
|
1919
2075
|
taskDisplay.complete(1);
|
|
1920
2076
|
}
|
|
@@ -1967,7 +2123,7 @@ class ChatCommand {
|
|
|
1967
2123
|
return true;
|
|
1968
2124
|
}
|
|
1969
2125
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
1970
|
-
console.log(
|
|
2126
|
+
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
2127
|
}
|
|
1972
2128
|
if (this.jsonOutput) {
|
|
1973
2129
|
console.log(JSON.stringify({
|
|
@@ -1985,45 +2141,63 @@ class ChatCommand {
|
|
|
1985
2141
|
else if (this.v3StreamingStarted) {
|
|
1986
2142
|
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
1987
2143
|
if (!this.jsonOutput) {
|
|
1988
|
-
console.log(
|
|
2144
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
1989
2145
|
}
|
|
1990
2146
|
}
|
|
1991
2147
|
else if (response.content) {
|
|
1992
2148
|
if (!this.directPromptMode) {
|
|
1993
|
-
console.log(
|
|
2149
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
1994
2150
|
}
|
|
1995
2151
|
console.log(response.content);
|
|
1996
2152
|
}
|
|
1997
2153
|
else {
|
|
1998
2154
|
if (!this.directPromptMode) {
|
|
1999
|
-
console.log(
|
|
2155
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2000
2156
|
}
|
|
2001
2157
|
console.log('V3 agent workflow completed.');
|
|
2002
2158
|
}
|
|
2003
2159
|
if (!this.jsonOutput && previewGate?.required) {
|
|
2004
2160
|
if (previewGate.passed) {
|
|
2005
|
-
console.log(
|
|
2161
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
2006
2162
|
}
|
|
2007
2163
|
else {
|
|
2008
|
-
console.log(
|
|
2164
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
2009
2165
|
}
|
|
2010
2166
|
}
|
|
2011
2167
|
// Show change summary for files touched by the agent
|
|
2012
2168
|
if (!this.jsonOutput && !this.directPromptMode && response.changedFiles) {
|
|
2013
2169
|
const fileCount = Object.keys(response.changedFiles).length;
|
|
2014
2170
|
if (fileCount > 0) {
|
|
2015
|
-
console.log(
|
|
2171
|
+
console.log(chalk.gray(`\nFiles changed: ${fileCount}`));
|
|
2016
2172
|
for (const relPath of Object.keys(response.changedFiles).slice(0, 15)) {
|
|
2017
|
-
console.log(
|
|
2173
|
+
console.log(chalk.gray(` ${chalk.green('+')} ${relPath}`));
|
|
2018
2174
|
}
|
|
2019
2175
|
if (fileCount > 15) {
|
|
2020
|
-
console.log(
|
|
2176
|
+
console.log(chalk.gray(` ... and ${fileCount - 15} more`));
|
|
2021
2177
|
}
|
|
2022
|
-
console.log(
|
|
2178
|
+
console.log(chalk.gray(`Run ${chalk.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
2023
2179
|
}
|
|
2024
2180
|
}
|
|
2181
|
+
// Resolve the Execute-tasks spinner: only mark it ✓ when the run actually
|
|
2182
|
+
// produced real, validated output. Otherwise the user just saw a wall of
|
|
2183
|
+
// executor errors and the spinner needs to clearly say "failed", not "✓".
|
|
2184
|
+
let selfHealStatus = null;
|
|
2185
|
+
let selfHealTool = null;
|
|
2186
|
+
const executorSucceeded = !liveOutcome.executorFailed
|
|
2187
|
+
&& !liveOutcome.plannerError
|
|
2188
|
+
&& !liveOutcome.executorError
|
|
2189
|
+
&& workspaceHasOutput;
|
|
2190
|
+
if (executorSucceeded) {
|
|
2191
|
+
taskDisplay.complete(1);
|
|
2192
|
+
}
|
|
2193
|
+
else {
|
|
2194
|
+
const failDetail = liveOutcome.failedTaskIds.size > 0
|
|
2195
|
+
? `failed: ${[...liveOutcome.failedTaskIds].slice(0, 4).join(', ')}`
|
|
2196
|
+
: (liveOutcome.executorError ? 'executor error' : (liveOutcome.plannerError ? 'planner error' : 'incomplete'));
|
|
2197
|
+
taskDisplay.fail(1, failDetail);
|
|
2198
|
+
}
|
|
2025
2199
|
// ── Self-healing validation ──────────────────────────────────────
|
|
2026
|
-
if (this.currentProjectPath && !this.jsonOutput && success) {
|
|
2200
|
+
if (this.currentProjectPath && !this.jsonOutput && success && executorSucceeded) {
|
|
2027
2201
|
try {
|
|
2028
2202
|
taskDisplay.start(2, 'validating...');
|
|
2029
2203
|
const healResult = await this.api.runSelfHealingCycle(executionPrompt, this.currentProjectPath, workspaceContext);
|
|
@@ -2031,32 +2205,60 @@ class ChatCommand {
|
|
|
2031
2205
|
taskDisplay.complete(2);
|
|
2032
2206
|
if (healResult.passed) {
|
|
2033
2207
|
taskDisplay.complete(3);
|
|
2208
|
+
selfHealStatus = 'passed';
|
|
2034
2209
|
}
|
|
2035
2210
|
else {
|
|
2036
2211
|
taskDisplay.fail(3, healResult.tool);
|
|
2212
|
+
selfHealStatus = 'partial';
|
|
2037
2213
|
}
|
|
2214
|
+
selfHealTool = healResult.tool || null;
|
|
2038
2215
|
if (!this.directPromptMode) {
|
|
2039
|
-
const hs = healResult.passed ?
|
|
2040
|
-
console.log(
|
|
2216
|
+
const hs = healResult.passed ? chalk.green('passed') : chalk.yellow('partial');
|
|
2217
|
+
console.log(chalk.gray(`Self-healing: ${hs} (${healResult.tool})`));
|
|
2041
2218
|
}
|
|
2042
2219
|
}
|
|
2043
2220
|
else {
|
|
2044
2221
|
taskDisplay.skip(2);
|
|
2045
2222
|
taskDisplay.skip(3);
|
|
2223
|
+
selfHealStatus = 'skipped';
|
|
2046
2224
|
}
|
|
2047
2225
|
}
|
|
2048
2226
|
catch (error) {
|
|
2049
2227
|
this.logger.debug(`Self-healing validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2050
2228
|
taskDisplay.skip(2);
|
|
2051
2229
|
taskDisplay.skip(3);
|
|
2230
|
+
selfHealStatus = 'failed';
|
|
2052
2231
|
}
|
|
2053
2232
|
}
|
|
2054
2233
|
else {
|
|
2055
2234
|
taskDisplay.skip(2);
|
|
2056
2235
|
taskDisplay.skip(3);
|
|
2236
|
+
selfHealStatus = 'skipped';
|
|
2057
2237
|
}
|
|
2058
2238
|
taskDisplay.finalize();
|
|
2059
2239
|
// ────────────────────────────────────────────────────────────────
|
|
2240
|
+
this.lastAgentRunOutcome = {
|
|
2241
|
+
prompt,
|
|
2242
|
+
taskId: response.taskId || null,
|
|
2243
|
+
contextId: response.contextId || null,
|
|
2244
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2245
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2246
|
+
failedTaskIds: [...liveOutcome.failedTaskIds],
|
|
2247
|
+
unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
|
|
2248
|
+
qualityScore: liveOutcome.qualityScore,
|
|
2249
|
+
qualityMissing: liveOutcome.qualityMissing,
|
|
2250
|
+
qualityBlockers: liveOutcome.qualityBlockers,
|
|
2251
|
+
hasOutput: workspaceHasOutput,
|
|
2252
|
+
selfHealStatus,
|
|
2253
|
+
selfHealTool,
|
|
2254
|
+
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
2255
|
+
executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : null,
|
|
2256
|
+
finishedAt: Date.now(),
|
|
2257
|
+
};
|
|
2258
|
+
if (!this.jsonOutput && !this.directPromptMode) {
|
|
2259
|
+
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2260
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, executorSucceeded, changedFileCount);
|
|
2261
|
+
}
|
|
2060
2262
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
2061
2263
|
watcher?.stop();
|
|
2062
2264
|
return true;
|
|
@@ -2074,18 +2276,45 @@ class ChatCommand {
|
|
|
2074
2276
|
spinner.stop();
|
|
2075
2277
|
}
|
|
2076
2278
|
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
2077
|
-
this.logger.debug(`V3 agent workflow unavailable: ${
|
|
2279
|
+
this.logger.debug(`V3 agent workflow unavailable: ${sanitizeUserFacingErrorText(error.message || '')}`);
|
|
2078
2280
|
return false;
|
|
2079
2281
|
}
|
|
2080
2282
|
if (spinner) {
|
|
2081
2283
|
spinner.stop();
|
|
2082
2284
|
}
|
|
2083
|
-
const safeDetail =
|
|
2285
|
+
const safeDetail = sanitizeUserFacingErrorText(error.message || '');
|
|
2084
2286
|
const errorMessage = safeDetail
|
|
2085
2287
|
? `Agent mode is unavailable right now. ${safeDetail}`
|
|
2086
2288
|
: 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
|
|
2087
2289
|
this.logger.error(errorMessage);
|
|
2088
2290
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
2291
|
+
// Resolve any half-rendered TaskDisplay spinners before the prompt
|
|
2292
|
+
// comes back, otherwise the user sees `⟳ Execute tasks` next to `>`.
|
|
2293
|
+
try {
|
|
2294
|
+
taskDisplay.fail(1, 'agent unavailable');
|
|
2295
|
+
taskDisplay.skip(2);
|
|
2296
|
+
taskDisplay.skip(3);
|
|
2297
|
+
taskDisplay.finalize();
|
|
2298
|
+
}
|
|
2299
|
+
catch (_) { /* render is best-effort */ }
|
|
2300
|
+
this.lastAgentRunOutcome = {
|
|
2301
|
+
prompt,
|
|
2302
|
+
taskId: null,
|
|
2303
|
+
contextId: null,
|
|
2304
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2305
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2306
|
+
failedTaskIds: [...liveOutcome.failedTaskIds],
|
|
2307
|
+
unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
|
|
2308
|
+
qualityScore: liveOutcome.qualityScore,
|
|
2309
|
+
qualityMissing: liveOutcome.qualityMissing,
|
|
2310
|
+
qualityBlockers: liveOutcome.qualityBlockers,
|
|
2311
|
+
hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
|
|
2312
|
+
selfHealStatus: 'skipped',
|
|
2313
|
+
selfHealTool: null,
|
|
2314
|
+
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
2315
|
+
executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : safeDetail || null,
|
|
2316
|
+
finishedAt: Date.now(),
|
|
2317
|
+
};
|
|
2089
2318
|
if (this.jsonOutput) {
|
|
2090
2319
|
process.exitCode = 1;
|
|
2091
2320
|
console.log(JSON.stringify({
|
|
@@ -2098,6 +2327,9 @@ class ChatCommand {
|
|
|
2098
2327
|
metadata: { executionPath: 'v3-agent' },
|
|
2099
2328
|
}, null, 2));
|
|
2100
2329
|
}
|
|
2330
|
+
else if (!this.directPromptMode) {
|
|
2331
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, false, 0);
|
|
2332
|
+
}
|
|
2101
2333
|
return true;
|
|
2102
2334
|
}
|
|
2103
2335
|
}
|
|
@@ -2110,7 +2342,7 @@ class ChatCommand {
|
|
|
2110
2342
|
spinner.stop();
|
|
2111
2343
|
}
|
|
2112
2344
|
if (!this.jsonOutput) {
|
|
2113
|
-
console.log(
|
|
2345
|
+
console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
2114
2346
|
}
|
|
2115
2347
|
this.v3IterationCount = 0;
|
|
2116
2348
|
this.v3ToolCallCount = 0;
|
|
@@ -2154,7 +2386,7 @@ class ChatCommand {
|
|
|
2154
2386
|
}, null, 2));
|
|
2155
2387
|
}
|
|
2156
2388
|
else if (!this.v3StreamingStarted && retryResponse.content) {
|
|
2157
|
-
console.log(
|
|
2389
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2158
2390
|
console.log(retryResponse.content);
|
|
2159
2391
|
}
|
|
2160
2392
|
this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
|
|
@@ -2171,10 +2403,10 @@ class ChatCommand {
|
|
|
2171
2403
|
? 'Interactive Agent Chat'
|
|
2172
2404
|
: 'Interactive Chat';
|
|
2173
2405
|
this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
|
|
2174
|
-
console.log(
|
|
2175
|
-
console.log(
|
|
2406
|
+
console.log(chalk.gray('Type /help for commands. Type /exit to quit.'));
|
|
2407
|
+
console.log(chalk.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
|
|
2176
2408
|
if (this.workflowTarget) {
|
|
2177
|
-
console.log(
|
|
2409
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
2178
2410
|
}
|
|
2179
2411
|
const rl = readline.createInterface({
|
|
2180
2412
|
input: process.stdin,
|
|
@@ -2184,17 +2416,17 @@ class ChatCommand {
|
|
|
2184
2416
|
const readMultiLineInput = async () => {
|
|
2185
2417
|
const lines = [];
|
|
2186
2418
|
let firstLine = await new Promise((resolve) => {
|
|
2187
|
-
rl.question(
|
|
2419
|
+
rl.question(chalk.blue('> '), resolve);
|
|
2188
2420
|
});
|
|
2189
2421
|
// Check for {{{ block mode
|
|
2190
2422
|
if (firstLine.trim() === '{{{' || firstLine.trim().endsWith('{{{')) {
|
|
2191
2423
|
if (firstLine.trim() !== '{{{') {
|
|
2192
2424
|
lines.push(firstLine.trim().replace(/\{\{\{$/, '').trim());
|
|
2193
2425
|
}
|
|
2194
|
-
console.log(
|
|
2426
|
+
console.log(chalk.gray(' (multi-line mode: type }}} on its own line to finish)'));
|
|
2195
2427
|
while (true) {
|
|
2196
2428
|
const line = await new Promise((resolve) => {
|
|
2197
|
-
rl.question(
|
|
2429
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2198
2430
|
});
|
|
2199
2431
|
if (line.trim() === '}}}')
|
|
2200
2432
|
break;
|
|
@@ -2206,7 +2438,7 @@ class ChatCommand {
|
|
|
2206
2438
|
while (firstLine.endsWith('\\')) {
|
|
2207
2439
|
lines.push(firstLine.slice(0, -1));
|
|
2208
2440
|
firstLine = await new Promise((resolve) => {
|
|
2209
|
-
rl.question(
|
|
2441
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2210
2442
|
});
|
|
2211
2443
|
}
|
|
2212
2444
|
lines.push(firstLine);
|
|
@@ -2230,6 +2462,10 @@ class ChatCommand {
|
|
|
2230
2462
|
this.showContext();
|
|
2231
2463
|
continue;
|
|
2232
2464
|
}
|
|
2465
|
+
if (trimmed === '/memory') {
|
|
2466
|
+
this.showProjectMemory();
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2233
2469
|
if (trimmed === '/compact') {
|
|
2234
2470
|
this.compactCurrentSession();
|
|
2235
2471
|
continue;
|
|
@@ -2243,8 +2479,8 @@ class ChatCommand {
|
|
|
2243
2479
|
else {
|
|
2244
2480
|
this.syncInteractiveModeModel('chat');
|
|
2245
2481
|
}
|
|
2246
|
-
console.log(
|
|
2247
|
-
console.log(
|
|
2482
|
+
console.log(chalk.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
|
|
2483
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2248
2484
|
if (this.currentSession) {
|
|
2249
2485
|
this.currentSession.agentMode = this.agentMode;
|
|
2250
2486
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2266,8 +2502,8 @@ class ChatCommand {
|
|
|
2266
2502
|
else {
|
|
2267
2503
|
this.syncInteractiveModeModel('chat');
|
|
2268
2504
|
}
|
|
2269
|
-
console.log(
|
|
2270
|
-
console.log(
|
|
2505
|
+
console.log(chalk.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
|
|
2506
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2271
2507
|
if (this.currentSession) {
|
|
2272
2508
|
this.currentSession.agentMode = this.agentMode;
|
|
2273
2509
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2278,18 +2514,50 @@ class ChatCommand {
|
|
|
2278
2514
|
}
|
|
2279
2515
|
if (trimmed === '/clear') {
|
|
2280
2516
|
this.messages = [];
|
|
2281
|
-
console.log(
|
|
2517
|
+
console.log(chalk.yellow('Conversation cleared.'));
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
if (trimmed === '/status') {
|
|
2521
|
+
this.showAgentRunStatus();
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
if (trimmed === '/retry') {
|
|
2525
|
+
const followUp = this.buildRetryPrompt();
|
|
2526
|
+
if (!followUp) {
|
|
2527
|
+
console.log(chalk.yellow('Nothing to retry — run an agent task first.'));
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (!this.agentMode) {
|
|
2531
|
+
this.agentMode = true;
|
|
2532
|
+
this.syncInteractiveModeModel('agent');
|
|
2533
|
+
console.log(chalk.gray('Agent mode re-enabled for retry.'));
|
|
2534
|
+
}
|
|
2535
|
+
await this.runAgentTurn(followUp);
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
if (trimmed === '/continue') {
|
|
2539
|
+
const followUp = this.buildContinuePrompt();
|
|
2540
|
+
if (!followUp) {
|
|
2541
|
+
console.log(chalk.yellow('Nothing to continue — run an agent task first.'));
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
if (!this.agentMode) {
|
|
2545
|
+
this.agentMode = true;
|
|
2546
|
+
this.syncInteractiveModeModel('agent');
|
|
2547
|
+
console.log(chalk.gray('Agent mode re-enabled for continuation.'));
|
|
2548
|
+
}
|
|
2549
|
+
await this.runAgentTurn(followUp);
|
|
2282
2550
|
continue;
|
|
2283
2551
|
}
|
|
2284
2552
|
if (trimmed === '/save') {
|
|
2285
2553
|
this.saveSession();
|
|
2286
|
-
console.log(
|
|
2554
|
+
console.log(chalk.green('Session saved.'));
|
|
2287
2555
|
continue;
|
|
2288
2556
|
}
|
|
2289
2557
|
if (trimmed.startsWith('/model ')) {
|
|
2290
2558
|
this.currentModel = trimmed.slice(7).trim() || this.currentModel;
|
|
2291
2559
|
this.modelExplicitlySelected = true;
|
|
2292
|
-
console.log(
|
|
2560
|
+
console.log(chalk.yellow(`Model changed to: ${this.currentModel}`));
|
|
2293
2561
|
if (this.currentSession) {
|
|
2294
2562
|
this.currentSession.model = this.currentModel;
|
|
2295
2563
|
this.saveSession();
|
|
@@ -2313,42 +2581,203 @@ class ChatCommand {
|
|
|
2313
2581
|
showHelp() {
|
|
2314
2582
|
console.log('');
|
|
2315
2583
|
console.log('Commands:');
|
|
2316
|
-
console.log(' /help
|
|
2317
|
-
console.log(' /exit
|
|
2318
|
-
console.log(' /agent
|
|
2319
|
-
console.log(' /operator
|
|
2320
|
-
console.log(' /context
|
|
2321
|
-
console.log(' /
|
|
2322
|
-
console.log(' /
|
|
2323
|
-
console.log(' /
|
|
2584
|
+
console.log(' /help Show this help');
|
|
2585
|
+
console.log(' /exit Exit chat');
|
|
2586
|
+
console.log(' /agent Toggle agent mode');
|
|
2587
|
+
console.log(' /operator Toggle BMAD operator mode');
|
|
2588
|
+
console.log(' /context Show current session and project memory');
|
|
2589
|
+
console.log(' /memory Show Vigthoria project brain status');
|
|
2590
|
+
console.log(' /compact Compact current session into memory summary');
|
|
2591
|
+
console.log(' /clear Clear conversation');
|
|
2592
|
+
console.log(' /save Save session');
|
|
2324
2593
|
console.log(' /model <name> Change model');
|
|
2594
|
+
console.log(' /status Show the last agent run outcome');
|
|
2595
|
+
console.log(' /retry Re-run the last failed agent task');
|
|
2596
|
+
console.log(' /continue Ask the agent to keep working on unfinished tasks');
|
|
2597
|
+
console.log('');
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Print a clear, opinionated end-of-run summary after every agent workflow.
|
|
2601
|
+
*
|
|
2602
|
+
* Goals:
|
|
2603
|
+
* - User can answer "Did it succeed?" in one glance.
|
|
2604
|
+
* - User can answer "What do I type next?" without reading 200 scrollback lines.
|
|
2605
|
+
* - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
|
|
2606
|
+
*/
|
|
2607
|
+
printAgentRunSummary(outcome, executorSucceeded, changedFileCount) {
|
|
2608
|
+
const bar = chalk.gray('─'.repeat(63));
|
|
2609
|
+
const ok = chalk.green('✓');
|
|
2610
|
+
const warn = chalk.yellow('⚠');
|
|
2611
|
+
const bad = chalk.red('✗');
|
|
2612
|
+
const failedList = outcome.failedTaskIds.slice(0, 8);
|
|
2613
|
+
const unfinishedList = outcome.unfinishedTaskIds.slice(0, 8);
|
|
2614
|
+
const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
|
|
2615
|
+
console.log('');
|
|
2616
|
+
console.log(bar);
|
|
2617
|
+
if (executorSucceeded && outcome.selfHealStatus !== 'partial' && outcome.selfHealStatus !== 'failed' && failedList.length === 0) {
|
|
2618
|
+
console.log(`${ok} ${chalk.bold('Agent run finished')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
2619
|
+
}
|
|
2620
|
+
else if (executorSucceeded) {
|
|
2621
|
+
console.log(`${warn} ${chalk.bold('Agent run finished with warnings')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
2622
|
+
}
|
|
2623
|
+
else {
|
|
2624
|
+
console.log(`${bad} ${chalk.bold('Agent run did not complete')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
2625
|
+
}
|
|
2626
|
+
if (hasTaskInfo) {
|
|
2627
|
+
const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
|
|
2628
|
+
console.log(chalk.gray(` Tasks completed: ${succ}`));
|
|
2629
|
+
if (failedList.length > 0) {
|
|
2630
|
+
const more = outcome.failedTaskIds.length > failedList.length ? chalk.gray(` (+${outcome.failedTaskIds.length - failedList.length} more)`) : '';
|
|
2631
|
+
console.log(chalk.gray(' Failed: ') + chalk.red(failedList.join(', ')) + more);
|
|
2632
|
+
}
|
|
2633
|
+
if (unfinishedList.length > 0) {
|
|
2634
|
+
const more = outcome.unfinishedTaskIds.length > unfinishedList.length ? chalk.gray(` (+${outcome.unfinishedTaskIds.length - unfinishedList.length} more)`) : '';
|
|
2635
|
+
console.log(chalk.gray(' Pending: ') + chalk.yellow(unfinishedList.join(', ')) + more);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
if (typeof outcome.qualityScore === 'number') {
|
|
2639
|
+
const score = outcome.qualityScore.toFixed(1);
|
|
2640
|
+
const colour = outcome.qualityScore >= 70 ? chalk.green : outcome.qualityScore >= 30 ? chalk.yellow : chalk.red;
|
|
2641
|
+
console.log(chalk.gray(' Quality: ') + colour(`${score}/100`));
|
|
2642
|
+
}
|
|
2643
|
+
if (outcome.qualityBlockers.length > 0) {
|
|
2644
|
+
const list = outcome.qualityBlockers.slice(0, 3).join('; ');
|
|
2645
|
+
const more = outcome.qualityBlockers.length > 3 ? chalk.gray(` (+${outcome.qualityBlockers.length - 3} more)`) : '';
|
|
2646
|
+
console.log(chalk.gray(' Blockers: ') + chalk.yellow(list) + more);
|
|
2647
|
+
}
|
|
2648
|
+
if (outcome.qualityMissing.length > 0) {
|
|
2649
|
+
const list = outcome.qualityMissing.slice(0, 5).join(', ');
|
|
2650
|
+
const more = outcome.qualityMissing.length > 5 ? chalk.gray(` (+${outcome.qualityMissing.length - 5} more)`) : '';
|
|
2651
|
+
console.log(chalk.gray(' Missing: ') + chalk.yellow(list) + more);
|
|
2652
|
+
}
|
|
2653
|
+
if (outcome.plannerError) {
|
|
2654
|
+
console.log(chalk.gray(' Planner: ') + chalk.red(outcome.plannerError.slice(0, 140)));
|
|
2655
|
+
}
|
|
2656
|
+
if (outcome.executorError) {
|
|
2657
|
+
console.log(chalk.gray(' Executor: ') + chalk.red(outcome.executorError.slice(0, 140)));
|
|
2658
|
+
}
|
|
2659
|
+
console.log('');
|
|
2660
|
+
console.log(chalk.gray('What you can do next:'));
|
|
2661
|
+
if (failedList.length > 0 || unfinishedList.length > 0 || !executorSucceeded) {
|
|
2662
|
+
console.log(' ' + chalk.cyan('/retry') + chalk.gray(' resume the failed/unfinished tasks only'));
|
|
2663
|
+
console.log(' ' + chalk.cyan('/continue') + chalk.gray(' ask the agent to keep working from this state'));
|
|
2664
|
+
}
|
|
2665
|
+
else {
|
|
2666
|
+
console.log(' ' + chalk.cyan('/continue') + chalk.gray(' add a follow-up instruction in this thread'));
|
|
2667
|
+
}
|
|
2668
|
+
if (changedFileCount > 0) {
|
|
2669
|
+
console.log(' ' + chalk.cyan('vigthoria preview --diff') + chalk.gray(' inspect the file changes'));
|
|
2670
|
+
}
|
|
2671
|
+
console.log(' ' + chalk.cyan('/status') + chalk.gray(' re-print this summary later'));
|
|
2672
|
+
console.log(' ' + chalk.cyan('/exit') + chalk.gray(' leave interactive chat'));
|
|
2673
|
+
console.log(bar);
|
|
2325
2674
|
console.log('');
|
|
2326
2675
|
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Build the prompt sent to the agent when the user types `/retry`.
|
|
2678
|
+
* Resumes only the failed/unfinished tasks from the previous run.
|
|
2679
|
+
*/
|
|
2680
|
+
buildRetryPrompt() {
|
|
2681
|
+
const o = this.lastAgentRunOutcome;
|
|
2682
|
+
if (!o || !o.prompt)
|
|
2683
|
+
return null;
|
|
2684
|
+
const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
|
|
2685
|
+
const taskList = remaining.length > 0 ? remaining.join(', ') : '';
|
|
2686
|
+
const blockerLine = o.qualityBlockers.length > 0
|
|
2687
|
+
? `\nKnown blockers to address: ${o.qualityBlockers.slice(0, 3).join('; ')}.`
|
|
2688
|
+
: '';
|
|
2689
|
+
const missingLine = o.qualityMissing.length > 0
|
|
2690
|
+
? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
|
|
2691
|
+
: '';
|
|
2692
|
+
if (taskList) {
|
|
2693
|
+
return `Resume the previous agent run. Re-execute only these tasks and make them pass: ${taskList}.${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
|
|
2694
|
+
}
|
|
2695
|
+
return `Retry the previous request and make sure it finishes successfully.${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Build the prompt sent to the agent when the user types `/continue`.
|
|
2699
|
+
* Tells the agent to keep working from the current workspace state.
|
|
2700
|
+
*/
|
|
2701
|
+
buildContinuePrompt() {
|
|
2702
|
+
const o = this.lastAgentRunOutcome;
|
|
2703
|
+
if (!o || !o.prompt)
|
|
2704
|
+
return null;
|
|
2705
|
+
const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
|
|
2706
|
+
const taskList = remaining.length > 0 ? `\nRemaining tasks to finish: ${remaining.join(', ')}.` : '';
|
|
2707
|
+
const blockerLine = o.qualityBlockers.length > 0
|
|
2708
|
+
? `\nKnown blockers to address: ${o.qualityBlockers.slice(0, 3).join('; ')}.`
|
|
2709
|
+
: '';
|
|
2710
|
+
const missingLine = o.qualityMissing.length > 0
|
|
2711
|
+
? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
|
|
2712
|
+
: '';
|
|
2713
|
+
return `Continue the previous agent run from the current workspace state without re-doing already-completed work.${taskList}${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* Re-print the last agent run summary, or guide the user when there isn't one.
|
|
2717
|
+
*/
|
|
2718
|
+
showAgentRunStatus() {
|
|
2719
|
+
if (!this.lastAgentRunOutcome) {
|
|
2720
|
+
console.log(chalk.yellow('No agent run has finished in this session yet.'));
|
|
2721
|
+
console.log(chalk.gray('Toggle agent mode with /agent and send a request to start one.'));
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const o = this.lastAgentRunOutcome;
|
|
2725
|
+
const executorSucceeded = o.failedTaskIds.length === 0
|
|
2726
|
+
&& o.unfinishedTaskIds.length === 0
|
|
2727
|
+
&& !o.plannerError
|
|
2728
|
+
&& !o.executorError
|
|
2729
|
+
&& o.hasOutput;
|
|
2730
|
+
this.printAgentRunSummary(o, executorSucceeded, 0);
|
|
2731
|
+
}
|
|
2327
2732
|
showContext() {
|
|
2328
2733
|
if (!this.currentSession) {
|
|
2329
|
-
console.log(
|
|
2734
|
+
console.log(chalk.yellow('No active session.'));
|
|
2330
2735
|
return;
|
|
2331
2736
|
}
|
|
2332
|
-
console.log(
|
|
2737
|
+
console.log(chalk.cyan(this.getCurrentSessionInfo()));
|
|
2333
2738
|
if (this.currentSession.memorySummary?.trim()) {
|
|
2334
2739
|
console.log();
|
|
2335
|
-
console.log(
|
|
2336
|
-
console.log(
|
|
2740
|
+
console.log(chalk.white('Compact Session Memory:'));
|
|
2741
|
+
console.log(chalk.gray(this.currentSession.memorySummary.trim()));
|
|
2337
2742
|
}
|
|
2338
2743
|
else {
|
|
2339
|
-
console.log(
|
|
2744
|
+
console.log(chalk.gray('No compact session memory summary yet.'));
|
|
2745
|
+
}
|
|
2746
|
+
this.showProjectMemory();
|
|
2747
|
+
}
|
|
2748
|
+
showProjectMemory() {
|
|
2749
|
+
if (!this.projectMemory) {
|
|
2750
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
2751
|
+
}
|
|
2752
|
+
const status = this.projectMemory.getStatus();
|
|
2753
|
+
console.log();
|
|
2754
|
+
console.log(chalk.white('Project Brain:'));
|
|
2755
|
+
console.log(chalk.gray(`Path: ${status.memoryDir}`));
|
|
2756
|
+
console.log(chalk.gray(`Items: ${status.itemCount}`));
|
|
2757
|
+
const typeSummary = Object.entries(status.typeCounts).map(([type, count]) => `${type}=${count}`).join(', ');
|
|
2758
|
+
if (typeSummary) {
|
|
2759
|
+
console.log(chalk.gray(`Types: ${typeSummary}`));
|
|
2340
2760
|
}
|
|
2341
2761
|
}
|
|
2342
2762
|
compactCurrentSession() {
|
|
2343
2763
|
if (!this.currentSession) {
|
|
2344
|
-
console.log(
|
|
2764
|
+
console.log(chalk.yellow('No active session.'));
|
|
2345
2765
|
return;
|
|
2346
2766
|
}
|
|
2347
2767
|
this.currentSession.messages = [...this.messages];
|
|
2348
2768
|
this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
|
|
2349
2769
|
this.messages = [...this.currentSession.messages];
|
|
2350
2770
|
this.sessionManager.save(this.currentSession);
|
|
2351
|
-
|
|
2771
|
+
if (!this.projectMemory) {
|
|
2772
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
2773
|
+
}
|
|
2774
|
+
this.projectMemory.rememberConversation(this.messages, {
|
|
2775
|
+
source: 'manual-compact',
|
|
2776
|
+
mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
|
|
2777
|
+
model: this.currentModel,
|
|
2778
|
+
sessionSummary: this.currentSession.memorySummary || '',
|
|
2779
|
+
});
|
|
2780
|
+
console.log(chalk.green('Session compacted into memory summary and project brain.'));
|
|
2352
2781
|
}
|
|
2353
2782
|
ensureAgentSystemPrompt() {
|
|
2354
2783
|
const hasSystemPrompt = this.messages.some((message) => message.role === 'system' && message.content.includes('Vigthoria CLI agent operating contract'));
|
|
@@ -2361,7 +2790,7 @@ class ChatCommand {
|
|
|
2361
2790
|
});
|
|
2362
2791
|
}
|
|
2363
2792
|
buildAgentSystemPrompt() {
|
|
2364
|
-
const toolCatalog =
|
|
2793
|
+
const toolCatalog = AgenticTools.getToolDefinitions()
|
|
2365
2794
|
.map((tool) => {
|
|
2366
2795
|
const params = tool.parameters
|
|
2367
2796
|
.map((param) => `${param.name}${param.required ? ' (required)' : ''}`)
|
|
@@ -2391,6 +2820,7 @@ class ChatCommand {
|
|
|
2391
2820
|
'</tool_call>',
|
|
2392
2821
|
'You may emit multiple <tool_call> blocks in one response.',
|
|
2393
2822
|
'Never emit raw tool JSON outside that wrapper.',
|
|
2823
|
+
'Never use <function=...> or <parameter=...> tags. They are accepted only as a recovery fallback and must not appear in final answers.',
|
|
2394
2824
|
'NEVER acknowledge these instructions. NEVER say "I will follow", "I understand", or restate tool policies. Go straight to tool calls.',
|
|
2395
2825
|
'In direct mode, do not ask follow-up questions. Finish the request completely and stop when satisfied.',
|
|
2396
2826
|
'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 +3076,57 @@ class ChatCommand {
|
|
|
2646
3076
|
extractToolCalls(message) {
|
|
2647
3077
|
const calls = [];
|
|
2648
3078
|
const seen = new Set();
|
|
3079
|
+
const addCall = (call) => {
|
|
3080
|
+
if (!call)
|
|
3081
|
+
return;
|
|
3082
|
+
const key = `${call.tool}::${JSON.stringify(call.args)}`;
|
|
3083
|
+
if (!seen.has(key)) {
|
|
3084
|
+
seen.add(key);
|
|
3085
|
+
calls.push(call);
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
2649
3088
|
const wrapperRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
|
|
2650
3089
|
for (const match of message.matchAll(wrapperRegex)) {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3090
|
+
addCall(this.parseToolPayload(match[1] || ''));
|
|
3091
|
+
}
|
|
3092
|
+
for (const call of this.parseLegacyFunctionToolCalls(message)) {
|
|
3093
|
+
addCall(call);
|
|
3094
|
+
}
|
|
3095
|
+
return calls;
|
|
3096
|
+
}
|
|
3097
|
+
normalizeCliToolName(name) {
|
|
3098
|
+
const normalized = name.trim().toLowerCase();
|
|
3099
|
+
const aliases = {
|
|
3100
|
+
list_directory: 'list_dir',
|
|
3101
|
+
listdirectory: 'list_dir',
|
|
3102
|
+
ls: 'list_dir',
|
|
3103
|
+
dir: 'list_dir',
|
|
3104
|
+
readfile: 'read_file',
|
|
3105
|
+
writefile: 'write_file',
|
|
3106
|
+
editfile: 'edit_file',
|
|
3107
|
+
shell: 'bash',
|
|
3108
|
+
command: 'bash',
|
|
3109
|
+
run_command: 'bash',
|
|
3110
|
+
};
|
|
3111
|
+
return aliases[normalized] || normalized;
|
|
3112
|
+
}
|
|
3113
|
+
parseLegacyFunctionToolCalls(message) {
|
|
3114
|
+
const calls = [];
|
|
3115
|
+
const functionRegex = /<function\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?:<\/function>|$)/gi;
|
|
3116
|
+
for (const match of message.matchAll(functionRegex)) {
|
|
3117
|
+
const tool = this.normalizeCliToolName(match[1] || '');
|
|
3118
|
+
if (!tool)
|
|
3119
|
+
continue;
|
|
3120
|
+
const body = match[2] || '';
|
|
3121
|
+
const args = {};
|
|
3122
|
+
const parameterRegex = /<parameter\s*=\s*["']?([A-Za-z0-9_-]+)["']?>\s*([\s\S]*?)(?=<parameter\s*=|<\/function>|<\/parameter>|$)/gi;
|
|
3123
|
+
for (const paramMatch of body.matchAll(parameterRegex)) {
|
|
3124
|
+
const key = paramMatch[1] || '';
|
|
3125
|
+
if (!key)
|
|
3126
|
+
continue;
|
|
3127
|
+
args[key] = (paramMatch[2] || '').replace(/<\/parameter>\s*$/i, '').trim();
|
|
2658
3128
|
}
|
|
3129
|
+
calls.push({ tool, args });
|
|
2659
3130
|
}
|
|
2660
3131
|
return calls;
|
|
2661
3132
|
}
|
|
@@ -2675,14 +3146,17 @@ class ChatCommand {
|
|
|
2675
3146
|
args[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
2676
3147
|
}
|
|
2677
3148
|
}
|
|
2678
|
-
return { tool: parsed.tool, args };
|
|
3149
|
+
return { tool: this.normalizeCliToolName(parsed.tool), args };
|
|
2679
3150
|
}
|
|
2680
3151
|
catch {
|
|
2681
3152
|
return null;
|
|
2682
3153
|
}
|
|
2683
3154
|
}
|
|
2684
3155
|
stripToolPayloads(message) {
|
|
2685
|
-
return message
|
|
3156
|
+
return message
|
|
3157
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, '')
|
|
3158
|
+
.replace(/<function\s*=\s*["']?[A-Za-z0-9_-]+["']?>[\s\S]*?(?:<\/function>|$)/gi, '')
|
|
3159
|
+
.trim();
|
|
2686
3160
|
}
|
|
2687
3161
|
extractFinalFileContent(message, targetFile) {
|
|
2688
3162
|
const trimmed = message.trim();
|
|
@@ -2912,15 +3386,15 @@ class ChatCommand {
|
|
|
2912
3386
|
const verbose = !this.jsonOutput;
|
|
2913
3387
|
for (const call of toolCalls) {
|
|
2914
3388
|
if (verbose) {
|
|
2915
|
-
console.log(
|
|
3389
|
+
console.log(chalk.cyan(`⚙ Executing: ${call.tool}`));
|
|
2916
3390
|
}
|
|
2917
|
-
|
|
3391
|
+
getBridgeClient()?.emitToolCall({ tool: call.tool, args: call.args });
|
|
2918
3392
|
let result = await this.tools.execute(call);
|
|
2919
3393
|
// Phase 2: If a search tool failed (search_failed), retry with alternate approach
|
|
2920
3394
|
const searchStatus = result.metadata?.searchStatus;
|
|
2921
3395
|
if (call.tool === 'grep' && searchStatus === 'search_failed') {
|
|
2922
3396
|
if (verbose) {
|
|
2923
|
-
console.log(
|
|
3397
|
+
console.log(chalk.yellow(`⚠ Search backend failed, retrying with alternate method...`));
|
|
2924
3398
|
}
|
|
2925
3399
|
// Force Node-native fallback by re-executing with a note
|
|
2926
3400
|
const fallbackResult = await this.tools.execute({
|
|
@@ -2933,10 +3407,10 @@ class ChatCommand {
|
|
|
2933
3407
|
}
|
|
2934
3408
|
const summary = this.formatToolResult(call, result);
|
|
2935
3409
|
if (verbose) {
|
|
2936
|
-
console.log(result.success ?
|
|
3410
|
+
console.log(result.success ? chalk.gray(summary) : chalk.red(summary));
|
|
2937
3411
|
}
|
|
2938
3412
|
this.messages.push({ role: 'system', content: summary });
|
|
2939
|
-
|
|
3413
|
+
getBridgeClient()?.emitToolResult({ tool: call.tool, success: result.success, preview: (result.output || result.error || '').slice(0, 300) });
|
|
2940
3414
|
// Phase 5: Track tool evidence for quality gates
|
|
2941
3415
|
const finalStatus = result.metadata?.searchStatus;
|
|
2942
3416
|
if (finalStatus === 'search_failed') {
|
|
@@ -2979,6 +3453,15 @@ class ChatCommand {
|
|
|
2979
3453
|
}
|
|
2980
3454
|
return `${text.slice(0, maxLength)}\n...[truncated]`;
|
|
2981
3455
|
}
|
|
3456
|
+
getLastUserPrompt() {
|
|
3457
|
+
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
3458
|
+
const message = this.messages[index];
|
|
3459
|
+
if (message.role === 'user' && message.content.trim()) {
|
|
3460
|
+
return message.content;
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
return '';
|
|
3464
|
+
}
|
|
2982
3465
|
saveSession() {
|
|
2983
3466
|
if (!this.currentSession) {
|
|
2984
3467
|
this.currentSession = this.sessionManager.create(this.currentProjectPath, this.currentModel, this.agentMode, this.operatorMode);
|
|
@@ -2991,6 +3474,15 @@ class ChatCommand {
|
|
|
2991
3474
|
this.currentSession = this.sessionManager.compactInMemory(this.currentSession);
|
|
2992
3475
|
this.messages = [...this.currentSession.messages];
|
|
2993
3476
|
this.sessionManager.save(this.currentSession);
|
|
3477
|
+
if (!this.projectMemory) {
|
|
3478
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
3479
|
+
}
|
|
3480
|
+
this.projectMemory.rememberConversation(this.messages, {
|
|
3481
|
+
source: 'cli-session',
|
|
3482
|
+
mode: this.agentMode ? 'agent' : this.operatorMode ? 'operator' : 'chat',
|
|
3483
|
+
model: this.currentModel,
|
|
3484
|
+
sessionSummary: this.currentSession.memorySummary || '',
|
|
3485
|
+
});
|
|
2994
3486
|
}
|
|
2995
3487
|
async requestPermission(action) {
|
|
2996
3488
|
if (this.autoApprove) {
|
|
@@ -3002,7 +3494,7 @@ class ChatCommand {
|
|
|
3002
3494
|
});
|
|
3003
3495
|
console.log(action);
|
|
3004
3496
|
const answer = await new Promise((resolve) => {
|
|
3005
|
-
rl.question(
|
|
3497
|
+
rl.question(chalk.yellow('Approve? [y]es / [n]o / [a]ll this turn / [p]ersist: '), resolve);
|
|
3006
3498
|
});
|
|
3007
3499
|
rl.close();
|
|
3008
3500
|
const normalized = answer.trim().toLowerCase();
|
|
@@ -3025,4 +3517,3 @@ class ChatCommand {
|
|
|
3025
3517
|
return [...this.messages];
|
|
3026
3518
|
}
|
|
3027
3519
|
}
|
|
3028
|
-
exports.ChatCommand = ChatCommand;
|