vigthoria-cli 1.10.47 → 1.10.49
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/dist/commands/agent-session-menu.js +2 -8
- package/dist/commands/auth.js +51 -68
- package/dist/commands/bridge.js +42 -22
- package/dist/commands/cancel.js +15 -22
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.js +326 -295
- package/dist/commands/config.js +33 -73
- package/dist/commands/deploy.js +83 -123
- package/dist/commands/device.js +21 -61
- package/dist/commands/edit.js +32 -39
- package/dist/commands/explain.js +18 -25
- package/dist/commands/fork.d.ts +17 -0
- package/dist/commands/fork.js +164 -0
- package/dist/commands/generate.js +37 -44
- package/dist/commands/history.d.ts +17 -0
- package/dist/commands/history.js +113 -0
- package/dist/commands/hub.js +95 -102
- package/dist/commands/index.js +41 -46
- package/dist/commands/legion.js +146 -186
- package/dist/commands/preview.d.ts +55 -0
- package/dist/commands/preview.js +467 -0
- package/dist/commands/replay.d.ts +18 -0
- package/dist/commands/replay.js +156 -0
- package/dist/commands/repo.d.ts +97 -0
- package/dist/commands/repo.js +773 -0
- package/dist/commands/review.js +29 -36
- package/dist/commands/security.js +5 -12
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +201 -0
- package/dist/commands/wallet.js +28 -35
- package/dist/commands/workflow.js +13 -20
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1652 -0
- package/dist/utils/api.d.ts +544 -0
- package/dist/utils/api.js +5486 -0
- package/dist/utils/brain-hub-client.js +1 -5
- 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/codebase-indexer.js +4 -41
- package/dist/utils/config.d.ts +82 -0
- package/dist/utils/config.js +269 -0
- package/dist/utils/context-ranker.js +15 -21
- package/dist/utils/desktop-bridge-client.d.ts +12 -0
- package/dist/utils/desktop-bridge-client.js +30 -0
- package/dist/utils/files.js +5 -42
- package/dist/utils/logger.js +42 -50
- package/dist/utils/persona.js +3 -8
- package/dist/utils/post-write-validator.js +26 -33
- package/dist/utils/project-memory.js +16 -23
- package/dist/utils/session.d.ts +118 -0
- package/dist/utils/session.js +423 -0
- package/dist/utils/task-display.js +13 -20
- package/dist/utils/tools.d.ts +269 -0
- package/dist/utils/tools.js +3450 -0
- package/dist/utils/workspace-brain-service.js +8 -45
- package/dist/utils/workspace-cache.js +18 -26
- package/dist/utils/workspace-stream.js +21 -63
- package/package.json +2 -1
- package/scripts/release/validate-no-go-gates.sh +7 -4
package/dist/commands/chat.js
CHANGED
|
@@ -1,57 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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");
|
|
52
|
-
const project_memory_js_1 = require("../utils/project-memory.js");
|
|
53
|
-
const persona_js_1 = require("../utils/persona.js");
|
|
54
|
-
const agent_session_menu_js_1 = require("./agent-session-menu.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, VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE } 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';
|
|
14
|
+
import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
|
|
15
|
+
import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
|
|
55
16
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
56
17
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
57
18
|
if (!rawValue) {
|
|
@@ -76,7 +37,7 @@ const DEFAULT_V3_AGENT_SOFT_TIMEOUT_MS = (() => {
|
|
|
76
37
|
const parsed = Number.parseInt(rawValue, 10);
|
|
77
38
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 180000;
|
|
78
39
|
})();
|
|
79
|
-
class ChatCommand {
|
|
40
|
+
export class ChatCommand {
|
|
80
41
|
config;
|
|
81
42
|
logger;
|
|
82
43
|
api;
|
|
@@ -103,6 +64,7 @@ class ChatCommand {
|
|
|
103
64
|
modelGovernanceFallback = null;
|
|
104
65
|
retryPromptSignature = null;
|
|
105
66
|
retryPromptStreak = 0;
|
|
67
|
+
v3SuppressThinkingStream = false;
|
|
106
68
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
107
69
|
lastAgentRunOutcome = null;
|
|
108
70
|
isJwtExpirationError(error) {
|
|
@@ -152,32 +114,32 @@ class ChatCommand {
|
|
|
152
114
|
message.includes('aborted');
|
|
153
115
|
}
|
|
154
116
|
toUserFacingApiError(error, context) {
|
|
155
|
-
const classified =
|
|
117
|
+
const classified = classifyError(error);
|
|
156
118
|
const status = classified.statusCode || (this.isJwtExpirationError(error) ? 401 : 500);
|
|
157
119
|
if (this.isJwtExpirationError(error) || classified.category === 'auth') {
|
|
158
|
-
return new
|
|
120
|
+
return new CLIError('Your Vigthoria session has expired. Run `vigthoria login` to authenticate again.', 'auth', { statusCode: 401 });
|
|
159
121
|
}
|
|
160
122
|
// Preserve structured API classification first (auth/model/network/etc.)
|
|
161
123
|
// so upstream responses are not relabeled by message heuristics.
|
|
162
124
|
if (classified.category === 'timeout') {
|
|
163
|
-
return new
|
|
125
|
+
return new CLIError(`${context} timed out. Check your connection and try again.`, 'timeout', { statusCode: status });
|
|
164
126
|
}
|
|
165
127
|
if (classified.category === 'network') {
|
|
166
|
-
return new
|
|
128
|
+
return new CLIError(`${context} could not reach the Vigthoria API. Check your network connection and try again.`, 'network', { statusCode: status });
|
|
167
129
|
}
|
|
168
130
|
if (classified.category === 'model_backend') {
|
|
169
|
-
return new
|
|
131
|
+
return new CLIError(VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE, 'model_backend', { statusCode: status, endpoint: classified.endpoint });
|
|
170
132
|
}
|
|
171
|
-
const message =
|
|
172
|
-
return new
|
|
133
|
+
const message = sanitizeUserFacingErrorText(classified.message || `${context} failed`);
|
|
134
|
+
return new CLIError(message, 'model_backend', { statusCode: status, endpoint: classified.endpoint });
|
|
173
135
|
}
|
|
174
136
|
handleApiError(error, context) {
|
|
175
137
|
const userFacingError = this.toUserFacingApiError(error, context);
|
|
176
138
|
if (!this.jsonOutput) {
|
|
177
|
-
console.error(
|
|
139
|
+
console.error(chalk.red(`${context} failed: ${userFacingError.message}`));
|
|
178
140
|
}
|
|
179
141
|
const original = error && typeof error === 'object' ? error : { message: String(error) };
|
|
180
|
-
|
|
142
|
+
propagateError({
|
|
181
143
|
...original,
|
|
182
144
|
message: userFacingError.message,
|
|
183
145
|
statusCode: userFacingError.statusCode,
|
|
@@ -378,10 +340,10 @@ class ChatCommand {
|
|
|
378
340
|
getActivePersonaMode() {
|
|
379
341
|
if (this.personaOverride)
|
|
380
342
|
return this.personaOverride;
|
|
381
|
-
return
|
|
343
|
+
return normalizePersonaMode(this.config.get('persona')) || 'default';
|
|
382
344
|
}
|
|
383
345
|
buildActivePersonaOverlay() {
|
|
384
|
-
return
|
|
346
|
+
return buildPersonaOverlay(this.getActivePersonaMode(), this.getLastUserPrompt());
|
|
385
347
|
}
|
|
386
348
|
isDiagnosticPrompt(prompt) {
|
|
387
349
|
return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
|
|
@@ -489,12 +451,12 @@ class ChatCommand {
|
|
|
489
451
|
return candidate;
|
|
490
452
|
}
|
|
491
453
|
if (!this.jsonOutput) {
|
|
492
|
-
console.log(
|
|
454
|
+
console.log(chalk.yellow(`Ignoring path outside allowed workspace roots: ${candidate}`));
|
|
493
455
|
if (allowedRoots.length > 0) {
|
|
494
456
|
const displayRoots = allowedRoots.map((root) => root.replace(/\\/g, '/')).join(', ');
|
|
495
|
-
console.log(
|
|
457
|
+
console.log(chalk.gray(`Allowed roots: ${displayRoots}`));
|
|
496
458
|
}
|
|
497
|
-
console.log(
|
|
459
|
+
console.log(chalk.gray('To allow unrestricted prompt path overrides, set VIGTHORIA_ALLOW_UNSCOPED_PROMPT_PATHS=1.'));
|
|
498
460
|
}
|
|
499
461
|
return null;
|
|
500
462
|
}
|
|
@@ -596,7 +558,7 @@ class ChatCommand {
|
|
|
596
558
|
}
|
|
597
559
|
try {
|
|
598
560
|
if (!this.projectMemory) {
|
|
599
|
-
this.projectMemory = new
|
|
561
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
600
562
|
}
|
|
601
563
|
const status = this.projectMemory.getStatus();
|
|
602
564
|
const context = this.projectMemory.buildContextForPrompt(prompt);
|
|
@@ -622,7 +584,7 @@ class ChatCommand {
|
|
|
622
584
|
}
|
|
623
585
|
try {
|
|
624
586
|
if (!this.projectMemory) {
|
|
625
|
-
this.projectMemory = new
|
|
587
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
626
588
|
}
|
|
627
589
|
this.projectMemory.remember(type, text, { source: 'vigthoria-cli', mode, model: this.currentModel });
|
|
628
590
|
}
|
|
@@ -644,7 +606,7 @@ class ChatCommand {
|
|
|
644
606
|
const devtoolsBridgeAllowed = /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_DEVTOOLS_BRIDGE_ALLOWED || process.env.VIGTHORIA_BRIDGE_ALLOWED || ''));
|
|
645
607
|
if (!devtoolsBridgeAllowed) {
|
|
646
608
|
if (!this.jsonOutput) {
|
|
647
|
-
console.log(
|
|
609
|
+
console.log(chalk.yellow('Browser task detected. DevTools Bridge is opt-in; the agent will ask before relying on local browser tooling.'));
|
|
648
610
|
}
|
|
649
611
|
return {
|
|
650
612
|
...runtimeContext,
|
|
@@ -656,10 +618,10 @@ class ChatCommand {
|
|
|
656
618
|
}
|
|
657
619
|
const bridgeStatus = await this.callApi('Checking DevTools Bridge status', () => this.api.getDevtoolsBridgeStatus(), 0);
|
|
658
620
|
if (!this.jsonOutput && bridgeStatus.ok) {
|
|
659
|
-
console.log(
|
|
621
|
+
console.log(chalk.gray(`Browser task detected. DevTools Bridge is reachable at ${bridgeStatus.endpoint}.`));
|
|
660
622
|
}
|
|
661
623
|
else if (!this.jsonOutput) {
|
|
662
|
-
console.log(
|
|
624
|
+
console.log(chalk.yellow(`Browser task detected. DevTools Bridge is not running at ${bridgeStatus.endpoint}.`));
|
|
663
625
|
}
|
|
664
626
|
return {
|
|
665
627
|
...runtimeContext,
|
|
@@ -680,7 +642,7 @@ class ChatCommand {
|
|
|
680
642
|
sanitizeServerPath(text) {
|
|
681
643
|
if (!text)
|
|
682
644
|
return text;
|
|
683
|
-
return
|
|
645
|
+
return sanitizeUserFacingPathText(this.stripHiddenThoughtBlocks(text));
|
|
684
646
|
}
|
|
685
647
|
stripHiddenThoughtBlocks(text) {
|
|
686
648
|
if (!text)
|
|
@@ -722,12 +684,12 @@ class ChatCommand {
|
|
|
722
684
|
const source = (event && typeof event === 'object' && !Buffer.isBuffer(event) && !(event instanceof Uint8Array))
|
|
723
685
|
? (event.body ?? event.stream ?? event.response ?? event)
|
|
724
686
|
: event;
|
|
725
|
-
for await (const chunk of
|
|
687
|
+
for await (const chunk of robustifyStreamResponse(source)) {
|
|
726
688
|
this.v3LastActivity = Date.now();
|
|
727
689
|
if (chunk.type === 'error') {
|
|
728
690
|
if (spinner.isSpinning)
|
|
729
691
|
spinner.stop();
|
|
730
|
-
process.stderr.write(
|
|
692
|
+
process.stderr.write(chalk.red(`\nStream error: ${chunk.content}\n`));
|
|
731
693
|
continue;
|
|
732
694
|
}
|
|
733
695
|
if (chunk.content) {
|
|
@@ -739,11 +701,11 @@ class ChatCommand {
|
|
|
739
701
|
const message = error instanceof Error ? error.message : String(error);
|
|
740
702
|
if (spinner.isSpinning)
|
|
741
703
|
spinner.stop();
|
|
742
|
-
process.stderr.write(
|
|
704
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
743
705
|
}
|
|
744
706
|
}
|
|
745
707
|
writeV3StreamText(spinner, text) {
|
|
746
|
-
const safeText = this.sanitizeServerPath(text);
|
|
708
|
+
const safeText = this.sanitizeV3VisibleStreamText(this.sanitizeServerPath(text));
|
|
747
709
|
if (!safeText) {
|
|
748
710
|
return;
|
|
749
711
|
}
|
|
@@ -759,13 +721,41 @@ class ChatCommand {
|
|
|
759
721
|
}
|
|
760
722
|
process.stdout.write(safeText);
|
|
761
723
|
}
|
|
724
|
+
sanitizeV3VisibleStreamText(text) {
|
|
725
|
+
let output = String(text || '');
|
|
726
|
+
if (!output)
|
|
727
|
+
return '';
|
|
728
|
+
output = output.replace(/<\/think>/gi, '</thinking>');
|
|
729
|
+
output = output.replace(/<think>/gi, '<thinking>');
|
|
730
|
+
if (this.v3SuppressThinkingStream) {
|
|
731
|
+
const closeIdx = output.search(/<\/thinking>/i);
|
|
732
|
+
if (closeIdx < 0) {
|
|
733
|
+
return '';
|
|
734
|
+
}
|
|
735
|
+
output = output.slice(closeIdx).replace(/^<\/thinking>/i, '');
|
|
736
|
+
this.v3SuppressThinkingStream = false;
|
|
737
|
+
}
|
|
738
|
+
output = output.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
739
|
+
if (/<thinking>/i.test(output)) {
|
|
740
|
+
output = output.replace(/<thinking>[\s\S]*$/i, '');
|
|
741
|
+
this.v3SuppressThinkingStream = true;
|
|
742
|
+
}
|
|
743
|
+
output = output
|
|
744
|
+
.replace(/```json\s*\[\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*\]\s*```/gi, '')
|
|
745
|
+
.replace(/```json\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*```/gi, '')
|
|
746
|
+
.replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
|
|
747
|
+
.replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
|
|
748
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
|
|
749
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
750
|
+
return output;
|
|
751
|
+
}
|
|
762
752
|
updateV3AgentSpinner(spinner, event) {
|
|
763
753
|
if (this.isRawV3StreamPayload(event)) {
|
|
764
754
|
this.consumeV3StreamPayload(spinner, event).catch((error) => {
|
|
765
755
|
const message = error instanceof Error ? error.message : String(error);
|
|
766
756
|
if (spinner.isSpinning)
|
|
767
757
|
spinner.stop();
|
|
768
|
-
process.stderr.write(
|
|
758
|
+
process.stderr.write(chalk.red(`\nStream error: ${message}\n`));
|
|
769
759
|
});
|
|
770
760
|
return;
|
|
771
761
|
}
|
|
@@ -779,7 +769,7 @@ class ChatCommand {
|
|
|
779
769
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
780
770
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
781
771
|
const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
782
|
-
const stepLabel =
|
|
772
|
+
const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
783
773
|
if (spinner.isSpinning)
|
|
784
774
|
spinner.stop();
|
|
785
775
|
process.stderr.write(stepLabel + '\n');
|
|
@@ -788,11 +778,11 @@ class ChatCommand {
|
|
|
788
778
|
const toolName = event.name || event.tool || '';
|
|
789
779
|
if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
|
|
790
780
|
const len = args.content.length;
|
|
791
|
-
process.stderr.write(
|
|
781
|
+
process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
|
|
792
782
|
}
|
|
793
783
|
else if (toolName === 'bash' && typeof args.command === 'string') {
|
|
794
784
|
const command = this.sanitizeServerPath(args.command);
|
|
795
|
-
process.stderr.write(
|
|
785
|
+
process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
|
|
796
786
|
}
|
|
797
787
|
spinner.start();
|
|
798
788
|
spinner.text = `Running ${toolDesc}...`;
|
|
@@ -801,7 +791,7 @@ class ChatCommand {
|
|
|
801
791
|
if (event.type === 'tool_result') {
|
|
802
792
|
const success = event.success !== false;
|
|
803
793
|
const toolName = event.name || event.tool || '';
|
|
804
|
-
const indicator = success ?
|
|
794
|
+
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
805
795
|
if (spinner.isSpinning)
|
|
806
796
|
spinner.stop();
|
|
807
797
|
process.stderr.write(`${indicator} ${toolName}\n`);
|
|
@@ -811,11 +801,11 @@ class ChatCommand {
|
|
|
811
801
|
if (!success && output) {
|
|
812
802
|
const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
|
|
813
803
|
const lines = sanitizedError.split('\n').slice(0, 4);
|
|
814
|
-
process.stderr.write(
|
|
804
|
+
process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
|
|
815
805
|
}
|
|
816
806
|
else if (success && output && output.length > 0) {
|
|
817
807
|
const brief = output.split('\n')[0].slice(0, 120);
|
|
818
|
-
process.stderr.write(
|
|
808
|
+
process.stderr.write(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
|
|
819
809
|
}
|
|
820
810
|
spinner.start();
|
|
821
811
|
spinner.text = 'Next step...';
|
|
@@ -830,7 +820,7 @@ class ChatCommand {
|
|
|
830
820
|
const iterText = this.sanitizeServerPath(event.content || '');
|
|
831
821
|
if (spinner.isSpinning)
|
|
832
822
|
spinner.stop();
|
|
833
|
-
process.stderr.write(
|
|
823
|
+
process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
|
|
834
824
|
spinner.start();
|
|
835
825
|
spinner.text = 'Analyzing...';
|
|
836
826
|
return;
|
|
@@ -844,7 +834,7 @@ class ChatCommand {
|
|
|
844
834
|
this.writeV3StreamText(spinner, text);
|
|
845
835
|
}
|
|
846
836
|
else {
|
|
847
|
-
spinner.text =
|
|
837
|
+
spinner.text = chalk.cyan('[Response] ') + 'Writing response...';
|
|
848
838
|
}
|
|
849
839
|
return;
|
|
850
840
|
}
|
|
@@ -862,15 +852,15 @@ class ChatCommand {
|
|
|
862
852
|
const fail = event.tasks_failed ?? 0;
|
|
863
853
|
statLine += ` — ${ok} tasks done`;
|
|
864
854
|
if (fail > 0)
|
|
865
|
-
statLine +=
|
|
855
|
+
statLine += chalk.yellow(`, ${fail} failed`);
|
|
866
856
|
}
|
|
867
|
-
process.stderr.write(
|
|
857
|
+
process.stderr.write(chalk.green(`\n✓ Complete`) + ` — ${statLine}\n`);
|
|
868
858
|
// Show seal quality score if available
|
|
869
859
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
870
860
|
const score = event.seal_score.overall;
|
|
871
861
|
const tier = event.seal_score.tier || '';
|
|
872
|
-
const scoreColor = score >= 7 ?
|
|
873
|
-
process.stderr.write(
|
|
862
|
+
const scoreColor = score >= 7 ? chalk.green : score >= 5 ? chalk.yellow : chalk.red;
|
|
863
|
+
process.stderr.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
|
|
874
864
|
}
|
|
875
865
|
return;
|
|
876
866
|
}
|
|
@@ -887,33 +877,33 @@ class ChatCommand {
|
|
|
887
877
|
}
|
|
888
878
|
if (spinner.isSpinning)
|
|
889
879
|
spinner.stop();
|
|
890
|
-
process.stderr.write(
|
|
880
|
+
process.stderr.write(chalk.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}`);
|
|
891
881
|
if (quality)
|
|
892
|
-
process.stderr.write(
|
|
882
|
+
process.stderr.write(chalk.gray(` (${quality})`));
|
|
893
883
|
process.stderr.write('\n');
|
|
894
884
|
if (summary) {
|
|
895
|
-
process.stderr.write(
|
|
885
|
+
process.stderr.write(chalk.gray(` ${summary}\n`));
|
|
896
886
|
}
|
|
897
887
|
if (status === 'planning' && Number.isFinite(Number(plan.elapsed_seconds))) {
|
|
898
|
-
process.stderr.write(
|
|
888
|
+
process.stderr.write(chalk.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
|
|
899
889
|
}
|
|
900
890
|
if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
|
|
901
|
-
process.stderr.write(
|
|
891
|
+
process.stderr.write(chalk.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
|
|
902
892
|
for (const t of plan.tasks.slice(0, 10)) {
|
|
903
893
|
const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.map((target) => this.sanitizeServerPath(String(target))).join(', ')}` : '';
|
|
904
|
-
process.stderr.write(
|
|
894
|
+
process.stderr.write(chalk.gray(` • ${this.sanitizeServerPath(String(t.title || t.id))}${targets}\n`));
|
|
905
895
|
}
|
|
906
896
|
if (plan.tasks.length > 10) {
|
|
907
|
-
process.stderr.write(
|
|
897
|
+
process.stderr.write(chalk.gray(` ... and ${plan.tasks.length - 10} more\n`));
|
|
908
898
|
}
|
|
909
899
|
}
|
|
910
900
|
if (Array.isArray(plan.notes) && plan.notes.length > 0) {
|
|
911
901
|
for (const note of plan.notes.slice(0, 3)) {
|
|
912
|
-
process.stderr.write(
|
|
902
|
+
process.stderr.write(chalk.gray(` note: ${this.sanitizeServerPath(String(note))}\n`));
|
|
913
903
|
}
|
|
914
904
|
}
|
|
915
905
|
if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
|
|
916
|
-
process.stderr.write(
|
|
906
|
+
process.stderr.write(chalk.gray(` Files: ${plan.target_files.map((filePath) => this.sanitizeServerPath(String(filePath))).join(', ')}\n`));
|
|
917
907
|
}
|
|
918
908
|
spinner.start();
|
|
919
909
|
spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
|
|
@@ -922,7 +912,7 @@ class ChatCommand {
|
|
|
922
912
|
if (event.type === 'executor_start') {
|
|
923
913
|
if (spinner.isSpinning)
|
|
924
914
|
spinner.stop();
|
|
925
|
-
process.stderr.write(
|
|
915
|
+
process.stderr.write(chalk.cyan(' [Executor] ') + `Starting ${this.sanitizeServerPath(String(event.task_id || 'task'))}${event.title ? ` - ${this.sanitizeServerPath(String(event.title))}` : ''}
|
|
926
916
|
`);
|
|
927
917
|
spinner.start();
|
|
928
918
|
spinner.text = 'Vigthoria Executor running...';
|
|
@@ -931,8 +921,8 @@ class ChatCommand {
|
|
|
931
921
|
if (event.type === 'executor_error') {
|
|
932
922
|
if (spinner.isSpinning)
|
|
933
923
|
spinner.stop();
|
|
934
|
-
const msg =
|
|
935
|
-
process.stderr.write(
|
|
924
|
+
const msg = sanitizeUserFacingErrorText(String(event.error || 'Executor error')) || 'Executor error';
|
|
925
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${msg}
|
|
936
926
|
`);
|
|
937
927
|
spinner.start();
|
|
938
928
|
spinner.text = 'Recovering executor...';
|
|
@@ -945,11 +935,11 @@ class ChatCommand {
|
|
|
945
935
|
const status = String(summary.status || 'completed');
|
|
946
936
|
const changed = Array.isArray(summary.changed_files) ? summary.changed_files.length : 0;
|
|
947
937
|
if (status === 'failed') {
|
|
948
|
-
process.stderr.write(
|
|
938
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor task failed${summary.task_id ? ` (${summary.task_id})` : ''}.
|
|
949
939
|
`);
|
|
950
940
|
}
|
|
951
941
|
else {
|
|
952
|
-
process.stderr.write(
|
|
942
|
+
process.stderr.write(chalk.green(' [Executor] ') + `Task completed${summary.task_id ? ` (${summary.task_id})` : ''}${changed ? `, ${changed} files changed` : ''}.
|
|
953
943
|
`);
|
|
954
944
|
}
|
|
955
945
|
spinner.start();
|
|
@@ -959,11 +949,11 @@ class ChatCommand {
|
|
|
959
949
|
if (event.type === 'file_mutation') {
|
|
960
950
|
const rawPath = typeof event.path === 'string' ? this.sanitizeServerPath(event.path) : '';
|
|
961
951
|
const filePath = rawPath ? rawPath.replace(/\\/g, '/').split('/').slice(-2).join('/') : '';
|
|
962
|
-
const action = event.action === 'delete' ?
|
|
952
|
+
const action = event.action === 'delete' ? chalk.red('deleted') : chalk.green('wrote');
|
|
963
953
|
if (filePath) {
|
|
964
954
|
if (spinner.isSpinning)
|
|
965
955
|
spinner.stop();
|
|
966
|
-
process.stderr.write(
|
|
956
|
+
process.stderr.write(chalk.cyan(' [File] ') + `${action} ${filePath}\n`);
|
|
967
957
|
spinner.start();
|
|
968
958
|
}
|
|
969
959
|
return;
|
|
@@ -972,23 +962,23 @@ class ChatCommand {
|
|
|
972
962
|
if (event.checkpointed) {
|
|
973
963
|
if (spinner.isSpinning)
|
|
974
964
|
spinner.stop();
|
|
975
|
-
process.stderr.write(
|
|
965
|
+
process.stderr.write(chalk.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
|
|
976
966
|
spinner.start();
|
|
977
967
|
}
|
|
978
968
|
else {
|
|
979
969
|
if (spinner.isSpinning)
|
|
980
970
|
spinner.stop();
|
|
981
|
-
const message =
|
|
971
|
+
const message = sanitizeUserFacingErrorText(String(event.message || 'Agent error')) || 'Agent error';
|
|
982
972
|
const plannerLike = /plan|planner|dependency graph/i.test(message);
|
|
983
973
|
const executorLike = /executor|task failed|iteration/i.test(message);
|
|
984
974
|
if (plannerLike) {
|
|
985
|
-
process.stderr.write(
|
|
975
|
+
process.stderr.write(chalk.red(' [Planner] ') + `Vigthoria Planner encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
986
976
|
}
|
|
987
977
|
else if (executorLike) {
|
|
988
|
-
process.stderr.write(
|
|
978
|
+
process.stderr.write(chalk.red(' [Executor] ') + `Vigthoria Executor encountered an issue: ${this.sanitizeServerPath(message)}\n`);
|
|
989
979
|
}
|
|
990
980
|
else {
|
|
991
|
-
process.stderr.write(
|
|
981
|
+
process.stderr.write(chalk.red(' [Error] ') + this.sanitizeServerPath(message) + '\n');
|
|
992
982
|
}
|
|
993
983
|
}
|
|
994
984
|
return;
|
|
@@ -996,7 +986,7 @@ class ChatCommand {
|
|
|
996
986
|
if (event.type === 'context') {
|
|
997
987
|
if (spinner.isSpinning)
|
|
998
988
|
spinner.stop();
|
|
999
|
-
process.stderr.write(
|
|
989
|
+
process.stderr.write(chalk.cyan(' [Context] ') + 'Workspace bound\n');
|
|
1000
990
|
spinner.start();
|
|
1001
991
|
spinner.text = 'Starting agent...';
|
|
1002
992
|
return;
|
|
@@ -1006,7 +996,7 @@ class ChatCommand {
|
|
|
1006
996
|
this.v3ToolCallCount = 0;
|
|
1007
997
|
if (spinner.isSpinning)
|
|
1008
998
|
spinner.stop();
|
|
1009
|
-
process.stderr.write(
|
|
999
|
+
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
1010
1000
|
spinner.start();
|
|
1011
1001
|
spinner.text = 'Working...';
|
|
1012
1002
|
return;
|
|
@@ -1020,7 +1010,7 @@ class ChatCommand {
|
|
|
1020
1010
|
if (spinner.isSpinning)
|
|
1021
1011
|
spinner.stop();
|
|
1022
1012
|
if (fallbackMessage) {
|
|
1023
|
-
process.stderr.write(
|
|
1013
|
+
process.stderr.write(chalk.cyan(' [V3] ') + `${fallbackMessage}\n`);
|
|
1024
1014
|
}
|
|
1025
1015
|
spinner.start();
|
|
1026
1016
|
spinner.text = fallbackStatus || fallbackStage || 'Working...';
|
|
@@ -1034,7 +1024,7 @@ class ChatCommand {
|
|
|
1034
1024
|
if (event.type === 'started') {
|
|
1035
1025
|
if (spinner.isSpinning)
|
|
1036
1026
|
spinner.stop();
|
|
1037
|
-
process.stderr.write(
|
|
1027
|
+
process.stderr.write(chalk.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
|
|
1038
1028
|
spinner.start();
|
|
1039
1029
|
spinner.text = 'Connecting...';
|
|
1040
1030
|
return;
|
|
@@ -1042,7 +1032,7 @@ class ChatCommand {
|
|
|
1042
1032
|
if (event.type === 'connected') {
|
|
1043
1033
|
if (spinner.isSpinning)
|
|
1044
1034
|
spinner.stop();
|
|
1045
|
-
process.stderr.write(
|
|
1035
|
+
process.stderr.write(chalk.green(' ✓') + ' Connected to BMAD stream\n');
|
|
1046
1036
|
spinner.start();
|
|
1047
1037
|
spinner.text = 'Working...';
|
|
1048
1038
|
return;
|
|
@@ -1051,7 +1041,7 @@ class ChatCommand {
|
|
|
1051
1041
|
const agentName = event.agent || 'BMAD agent';
|
|
1052
1042
|
if (spinner.isSpinning)
|
|
1053
1043
|
spinner.stop();
|
|
1054
|
-
process.stderr.write(
|
|
1044
|
+
process.stderr.write(chalk.cyan(` [Agent] `) + agentName + '\n');
|
|
1055
1045
|
spinner.start();
|
|
1056
1046
|
spinner.text = `Running ${agentName}...`;
|
|
1057
1047
|
return;
|
|
@@ -1061,7 +1051,7 @@ class ChatCommand {
|
|
|
1061
1051
|
const statusText = `${event.status || 'Working'}${progress}`;
|
|
1062
1052
|
if (spinner.isSpinning)
|
|
1063
1053
|
spinner.stop();
|
|
1064
|
-
process.stderr.write(
|
|
1054
|
+
process.stderr.write(chalk.cyan(' [Status] ') + statusText + '\n');
|
|
1065
1055
|
spinner.start();
|
|
1066
1056
|
spinner.text = statusText;
|
|
1067
1057
|
return;
|
|
@@ -1069,15 +1059,15 @@ class ChatCommand {
|
|
|
1069
1059
|
if (event.type === 'result') {
|
|
1070
1060
|
if (spinner.isSpinning)
|
|
1071
1061
|
spinner.stop();
|
|
1072
|
-
process.stderr.write(
|
|
1062
|
+
process.stderr.write(chalk.green('\n ✓ Operator workflow complete\n'));
|
|
1073
1063
|
return;
|
|
1074
1064
|
}
|
|
1075
1065
|
}
|
|
1076
1066
|
constructor(config, logger) {
|
|
1077
1067
|
this.config = config;
|
|
1078
1068
|
this.logger = logger;
|
|
1079
|
-
this.api = new
|
|
1080
|
-
this.sessionManager = new
|
|
1069
|
+
this.api = new APIClient(config, logger);
|
|
1070
|
+
this.sessionManager = new SessionManager();
|
|
1081
1071
|
}
|
|
1082
1072
|
async run(options) {
|
|
1083
1073
|
if (!this.config.isAuthenticated()) {
|
|
@@ -1103,8 +1093,8 @@ class ChatCommand {
|
|
|
1103
1093
|
this.currentModel = this.resolveInitialModel(options);
|
|
1104
1094
|
this.applyNoAgentGovernance(String(options.model || this.currentModel || ''));
|
|
1105
1095
|
this.currentProjectPath = this.resolveProjectPath(options);
|
|
1106
|
-
if ((this.agentMode || this.operatorMode) &&
|
|
1107
|
-
const sessionConfig = await
|
|
1096
|
+
if ((this.agentMode || this.operatorMode) && shouldShowAgentSessionMenu(options)) {
|
|
1097
|
+
const sessionConfig = await runAgentSessionMenu({
|
|
1108
1098
|
workspacePath: this.currentProjectPath,
|
|
1109
1099
|
autoApprove: this.autoApprove,
|
|
1110
1100
|
});
|
|
@@ -1114,7 +1104,7 @@ class ChatCommand {
|
|
|
1114
1104
|
if (sessionConfig.debugMode) {
|
|
1115
1105
|
process.env.DEBUG = process.env.DEBUG || 'vigthoria:*';
|
|
1116
1106
|
}
|
|
1117
|
-
console.log(
|
|
1107
|
+
console.log(chalk.green(`Using workspace: ${this.currentProjectPath}`));
|
|
1118
1108
|
}
|
|
1119
1109
|
if (this.jsonOutput && !options.prompt) {
|
|
1120
1110
|
throw new Error('--json is only supported together with --prompt.');
|
|
@@ -1122,11 +1112,11 @@ class ChatCommand {
|
|
|
1122
1112
|
this.ensureProjectWorkspace();
|
|
1123
1113
|
this.directPromptMode = Boolean(options.prompt);
|
|
1124
1114
|
this.directToolContinuationCount = 0;
|
|
1125
|
-
this.tools = new
|
|
1115
|
+
this.tools = new AgenticTools(this.logger, this.currentProjectPath, async (action) => this.requestPermission(action), this.autoApprove);
|
|
1126
1116
|
this.initializeSession(options.resume === true);
|
|
1127
1117
|
// ── Commando Bridge: connect if --bridge was specified ──────────
|
|
1128
1118
|
if (options.bridge) {
|
|
1129
|
-
const bridgeClient = new
|
|
1119
|
+
const bridgeClient = new BridgeClient({
|
|
1130
1120
|
bridgeUrl: options.bridge,
|
|
1131
1121
|
apiKey: this.config.get('authToken'),
|
|
1132
1122
|
onAdminCommand: (cmd) => this.handleAdminCommand(cmd),
|
|
@@ -1161,7 +1151,7 @@ class ChatCommand {
|
|
|
1161
1151
|
const timeoutId = options.bridge && bridgePromptTimeoutMs > 0
|
|
1162
1152
|
? setTimeout(() => {
|
|
1163
1153
|
timedOut = true;
|
|
1164
|
-
const b =
|
|
1154
|
+
const b = getBridgeClient();
|
|
1165
1155
|
if (b) {
|
|
1166
1156
|
b.emitEnd({ reason: 'timeout' });
|
|
1167
1157
|
b.destroy();
|
|
@@ -1176,7 +1166,7 @@ class ChatCommand {
|
|
|
1176
1166
|
if (timeoutId)
|
|
1177
1167
|
clearTimeout(timeoutId);
|
|
1178
1168
|
if (!timedOut) {
|
|
1179
|
-
const bridge =
|
|
1169
|
+
const bridge = getBridgeClient();
|
|
1180
1170
|
if (bridge) {
|
|
1181
1171
|
bridge.emitEnd({ reason: 'prompt-complete' });
|
|
1182
1172
|
bridge.destroy();
|
|
@@ -1188,7 +1178,7 @@ class ChatCommand {
|
|
|
1188
1178
|
process.exit(process.exitCode ?? 0);
|
|
1189
1179
|
}
|
|
1190
1180
|
await this.startInteractiveChat();
|
|
1191
|
-
const bridge =
|
|
1181
|
+
const bridge = getBridgeClient();
|
|
1192
1182
|
if (bridge) {
|
|
1193
1183
|
bridge.emitEnd({ reason: 'interactive-exit' });
|
|
1194
1184
|
bridge.destroy();
|
|
@@ -1198,24 +1188,24 @@ class ChatCommand {
|
|
|
1198
1188
|
handleAdminCommand(cmd) {
|
|
1199
1189
|
switch (cmd.action) {
|
|
1200
1190
|
case 'ping':
|
|
1201
|
-
|
|
1191
|
+
getBridgeClient()?.emitModelResponse({ model: this.currentModel, chars: 0, hasToolCalls: false, preview: 'pong' });
|
|
1202
1192
|
break;
|
|
1203
1193
|
case 'set-model':
|
|
1204
1194
|
if (cmd.params?.value && typeof cmd.params.value === 'string') {
|
|
1205
1195
|
this.currentModel = cmd.params.value;
|
|
1206
|
-
|
|
1196
|
+
getBridgeClient()?.emitModeChange({ mode: this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat', model: this.currentModel });
|
|
1207
1197
|
if (!this.jsonOutput)
|
|
1208
|
-
console.log(
|
|
1198
|
+
console.log(chalk.yellow(`[bridge] Model changed to: ${this.currentModel}`));
|
|
1209
1199
|
}
|
|
1210
1200
|
break;
|
|
1211
1201
|
case 'abort':
|
|
1212
1202
|
if (!this.jsonOutput)
|
|
1213
|
-
console.log(
|
|
1214
|
-
|
|
1203
|
+
console.log(chalk.red(`[bridge] Abort requested by admin`));
|
|
1204
|
+
getBridgeClient()?.emitEnd({ reason: 'admin-abort' });
|
|
1215
1205
|
process.exit(0);
|
|
1216
1206
|
break;
|
|
1217
1207
|
default:
|
|
1218
|
-
|
|
1208
|
+
getBridgeClient()?.emitError({ message: `Unknown admin command: ${cmd.action}` });
|
|
1219
1209
|
}
|
|
1220
1210
|
}
|
|
1221
1211
|
ensureProjectWorkspace() {
|
|
@@ -1251,13 +1241,13 @@ class ChatCommand {
|
|
|
1251
1241
|
// Guardrail: do not watch broad home/root scopes on interactive shells.
|
|
1252
1242
|
if (normalized.toLowerCase() === normalizedHome.toLowerCase()) {
|
|
1253
1243
|
if (!this.jsonOutput) {
|
|
1254
|
-
console.log(
|
|
1244
|
+
console.log(chalk.gray('Info: workspace watcher disabled for home directory scope.'));
|
|
1255
1245
|
}
|
|
1256
1246
|
return false;
|
|
1257
1247
|
}
|
|
1258
1248
|
if (/^[a-zA-Z]:\/$/.test(normalized) || normalized === '/') {
|
|
1259
1249
|
if (!this.jsonOutput) {
|
|
1260
|
-
console.log(
|
|
1250
|
+
console.log(chalk.gray('Info: workspace watcher disabled for filesystem root scope.'));
|
|
1261
1251
|
}
|
|
1262
1252
|
return false;
|
|
1263
1253
|
}
|
|
@@ -1273,7 +1263,7 @@ class ChatCommand {
|
|
|
1273
1263
|
const explicitPath = this.resolvePromptWorkspacePath(options.prompt, process.cwd());
|
|
1274
1264
|
if (explicitPath) {
|
|
1275
1265
|
if (!this.jsonOutput) {
|
|
1276
|
-
console.log(
|
|
1266
|
+
console.log(chalk.gray(`📁 Using project path from prompt: ${explicitPath}`));
|
|
1277
1267
|
}
|
|
1278
1268
|
return explicitPath;
|
|
1279
1269
|
}
|
|
@@ -1375,7 +1365,7 @@ class ChatCommand {
|
|
|
1375
1365
|
return path.join(rootPath, `${folderName}-${Date.now()}`);
|
|
1376
1366
|
}
|
|
1377
1367
|
initializeSession(resume) {
|
|
1378
|
-
this.projectMemory = new
|
|
1368
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
1379
1369
|
if (resume) {
|
|
1380
1370
|
this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
|
|
1381
1371
|
if (this.currentSession) {
|
|
@@ -1421,11 +1411,11 @@ class ChatCommand {
|
|
|
1421
1411
|
// Suppress all setup banners in direct-prompt mode so only the final
|
|
1422
1412
|
// answer reaches stdout. Interactive (REPL) mode still shows them.
|
|
1423
1413
|
if (!this.jsonOutput) {
|
|
1424
|
-
console.log(
|
|
1425
|
-
console.log(
|
|
1426
|
-
console.log(
|
|
1414
|
+
console.log(chalk.cyan('Running single prompt in direct mode.'));
|
|
1415
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
1416
|
+
console.log(chalk.gray(`Project: ${this.currentProjectPath}`));
|
|
1427
1417
|
if (this.workflowTarget) {
|
|
1428
|
-
console.log(
|
|
1418
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
1429
1419
|
}
|
|
1430
1420
|
console.log();
|
|
1431
1421
|
}
|
|
@@ -1489,7 +1479,7 @@ class ChatCommand {
|
|
|
1489
1479
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1490
1480
|
const resolvedWorkflow = await this.callApi('Resolve VigFlow workflow', () => this.api.resolveVigFlowWorkflow(selector));
|
|
1491
1481
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
1492
|
-
const spinner = this.jsonOutput ? null :
|
|
1482
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
1493
1483
|
try {
|
|
1494
1484
|
const execution = await this.callApi('Run VigFlow workflow', () => this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
1495
1485
|
data: {
|
|
@@ -1538,9 +1528,9 @@ class ChatCommand {
|
|
|
1538
1528
|
return;
|
|
1539
1529
|
}
|
|
1540
1530
|
this.logger.success(`Workflow target ${resolvedWorkflow.name} ${execution.status}`);
|
|
1541
|
-
console.log(
|
|
1542
|
-
console.log(
|
|
1543
|
-
console.log(
|
|
1531
|
+
console.log(chalk.gray(`Workflow ID: ${resolvedWorkflow.id}`));
|
|
1532
|
+
console.log(chalk.gray(`Execution ID: ${execution.executionId}`));
|
|
1533
|
+
console.log(chalk.gray(`Mode: ${invocationMode}`));
|
|
1544
1534
|
if (content) {
|
|
1545
1535
|
console.log(content);
|
|
1546
1536
|
}
|
|
@@ -1583,9 +1573,9 @@ class ChatCommand {
|
|
|
1583
1573
|
await this.runLocalAgentLoop(prompt);
|
|
1584
1574
|
return;
|
|
1585
1575
|
}
|
|
1586
|
-
|
|
1576
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
|
|
1587
1577
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
1588
|
-
const spinner = this.jsonOutput ? null :
|
|
1578
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
1589
1579
|
const workflowType = 'full';
|
|
1590
1580
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
1591
1581
|
try {
|
|
@@ -1613,7 +1603,7 @@ class ChatCommand {
|
|
|
1613
1603
|
const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(responseText)
|
|
1614
1604
|
|| (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
|
|
1615
1605
|
if (isPolicyAck) {
|
|
1616
|
-
throw new
|
|
1606
|
+
throw new CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1617
1607
|
}
|
|
1618
1608
|
if (this.jsonOutput) {
|
|
1619
1609
|
console.log(JSON.stringify({
|
|
@@ -1629,7 +1619,7 @@ class ChatCommand {
|
|
|
1629
1619
|
else {
|
|
1630
1620
|
console.log(response.content || 'Operator workflow completed.');
|
|
1631
1621
|
if (response.savedWorkflow?.id) {
|
|
1632
|
-
console.log(
|
|
1622
|
+
console.log(chalk.gray(`Saved VigFlow workflow: ${response.savedWorkflow.id}${response.savedWorkflow.name ? ` (${response.savedWorkflow.name})` : ''}`));
|
|
1633
1623
|
}
|
|
1634
1624
|
}
|
|
1635
1625
|
this.rememberBrainEvent('validation', `GoA operator workflow completed${response.workflowId ? ` workflow ${response.workflowId}` : ''}${response.savedWorkflow?.id ? ` saved VigFlow ${response.savedWorkflow.id}` : ''}.`, 'operator');
|
|
@@ -1640,8 +1630,8 @@ class ChatCommand {
|
|
|
1640
1630
|
if (spinner) {
|
|
1641
1631
|
spinner.stop();
|
|
1642
1632
|
}
|
|
1643
|
-
const cliErr = error instanceof
|
|
1644
|
-
const errorMsg =
|
|
1633
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1634
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1645
1635
|
if (!this.jsonOutput) {
|
|
1646
1636
|
this.logger.error('Operator workflow failed');
|
|
1647
1637
|
}
|
|
@@ -1668,8 +1658,8 @@ class ChatCommand {
|
|
|
1668
1658
|
* BMAD orchestrator to scan the workspace.
|
|
1669
1659
|
*/
|
|
1670
1660
|
async runOperatorDirectAnswer(prompt) {
|
|
1671
|
-
|
|
1672
|
-
const spinner = this.jsonOutput ? null :
|
|
1661
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
|
|
1662
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Operator (direct)...', spinner: 'clock' }).start();
|
|
1673
1663
|
try {
|
|
1674
1664
|
let operatorGrounding = [
|
|
1675
1665
|
'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
|
|
@@ -1701,7 +1691,7 @@ class ChatCommand {
|
|
|
1701
1691
|
const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(content)
|
|
1702
1692
|
|| (content.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(content));
|
|
1703
1693
|
if (isPolicyAck || !content) {
|
|
1704
|
-
throw new
|
|
1694
|
+
throw new CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
|
|
1705
1695
|
}
|
|
1706
1696
|
if (this.jsonOutput) {
|
|
1707
1697
|
console.log(JSON.stringify({
|
|
@@ -1721,8 +1711,8 @@ class ChatCommand {
|
|
|
1721
1711
|
catch (error) {
|
|
1722
1712
|
if (spinner)
|
|
1723
1713
|
spinner.stop();
|
|
1724
|
-
const cliErr = error instanceof
|
|
1725
|
-
const errorMsg =
|
|
1714
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1715
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1726
1716
|
if (this.jsonOutput) {
|
|
1727
1717
|
process.exitCode = 1;
|
|
1728
1718
|
console.log(JSON.stringify({
|
|
@@ -1754,8 +1744,8 @@ class ChatCommand {
|
|
|
1754
1744
|
this.currentSession.agentMode = true;
|
|
1755
1745
|
this.currentSession.model = this.currentModel;
|
|
1756
1746
|
}
|
|
1757
|
-
console.log(
|
|
1758
|
-
console.log(
|
|
1747
|
+
console.log(chalk.yellow('This request needs file access, so I am switching to Agent mode and working in this workspace now.'));
|
|
1748
|
+
console.log(chalk.gray('You do not need to confirm with "yes"; I will continue until the agent run finishes or reports a blocker.'));
|
|
1759
1749
|
await this.runAgentTurn(promptToRun);
|
|
1760
1750
|
return;
|
|
1761
1751
|
}
|
|
@@ -1814,8 +1804,8 @@ class ChatCommand {
|
|
|
1814
1804
|
}
|
|
1815
1805
|
}
|
|
1816
1806
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
1817
|
-
|
|
1818
|
-
const spinner = this.jsonOutput ? null :
|
|
1807
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: 'chat', model: this.currentModel });
|
|
1808
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
1819
1809
|
try {
|
|
1820
1810
|
const response = await this.callApi('Send chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel));
|
|
1821
1811
|
if (spinner)
|
|
@@ -1839,7 +1829,7 @@ class ChatCommand {
|
|
|
1839
1829
|
console.log(finalText);
|
|
1840
1830
|
}
|
|
1841
1831
|
else {
|
|
1842
|
-
console.log(
|
|
1832
|
+
console.log(chalk.yellow('The model returned an empty response. Try rephrasing your question, or use --agent mode for grounded repo analysis.'));
|
|
1843
1833
|
}
|
|
1844
1834
|
this.messages.push({ role: 'assistant', content: response.message || '' });
|
|
1845
1835
|
this.saveSession();
|
|
@@ -1847,8 +1837,8 @@ class ChatCommand {
|
|
|
1847
1837
|
catch (error) {
|
|
1848
1838
|
if (spinner)
|
|
1849
1839
|
spinner.stop();
|
|
1850
|
-
const cliErr = error instanceof
|
|
1851
|
-
const errorMsg =
|
|
1840
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
1841
|
+
const errorMsg = formatCLIError(cliErr);
|
|
1852
1842
|
if (this.jsonOutput) {
|
|
1853
1843
|
process.exitCode = 1;
|
|
1854
1844
|
console.log(JSON.stringify({
|
|
@@ -1905,13 +1895,13 @@ class ChatCommand {
|
|
|
1905
1895
|
this.directToolContinuationCount = 0;
|
|
1906
1896
|
this.agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
1907
1897
|
this.tools.clearSessionApprovals();
|
|
1908
|
-
|
|
1898
|
+
getBridgeClient()?.emitPrompt({ prompt, mode: this.operatorMode ? 'operator' : 'agent', model: this.currentModel });
|
|
1909
1899
|
this.ensureAgentSystemPrompt();
|
|
1910
1900
|
this.messages.push({ role: 'user', content: this.buildScopedUserPrompt(prompt) });
|
|
1911
1901
|
this.saveSession();
|
|
1912
1902
|
const maxTurns = 10;
|
|
1913
1903
|
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
1914
|
-
const spinner = this.jsonOutput ? null :
|
|
1904
|
+
const spinner = this.jsonOutput ? null : createSpinner({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
1915
1905
|
let response;
|
|
1916
1906
|
try {
|
|
1917
1907
|
response = await this.callApi('Send agent chat message', () => this.api.chat(this.getMessagesForModel(), this.currentModel), 0);
|
|
@@ -1952,8 +1942,8 @@ class ChatCommand {
|
|
|
1952
1942
|
}
|
|
1953
1943
|
}
|
|
1954
1944
|
else {
|
|
1955
|
-
const cliErr = firstErr instanceof
|
|
1956
|
-
const formatted =
|
|
1945
|
+
const cliErr = firstErr instanceof CLIError ? firstErr : classifyError(firstErr);
|
|
1946
|
+
const formatted = formatCLIError(cliErr);
|
|
1957
1947
|
if (spinner)
|
|
1958
1948
|
spinner.stop();
|
|
1959
1949
|
this.rememberBrainEvent('issue', 'Agent API preflight failed: ' + formatted, 'agent');
|
|
@@ -1983,7 +1973,7 @@ class ChatCommand {
|
|
|
1983
1973
|
this.messages.push({ role: 'assistant', content: assistantMessage });
|
|
1984
1974
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
1985
1975
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
1986
|
-
|
|
1976
|
+
getBridgeClient()?.emitModelResponse({
|
|
1987
1977
|
model: this.currentModel,
|
|
1988
1978
|
chars: assistantMessage.length,
|
|
1989
1979
|
hasToolCalls: toolCalls.length > 0,
|
|
@@ -2064,8 +2054,8 @@ class ChatCommand {
|
|
|
2064
2054
|
catch (error) {
|
|
2065
2055
|
if (spinner)
|
|
2066
2056
|
spinner.stop();
|
|
2067
|
-
const cliErr = error instanceof
|
|
2068
|
-
const errorMsg =
|
|
2057
|
+
const cliErr = error instanceof CLIError ? error : classifyError(error);
|
|
2058
|
+
const errorMsg = formatCLIError(cliErr);
|
|
2069
2059
|
this.rememberBrainEvent('issue', `Agent turn failed: ${errorMsg}`, 'agent');
|
|
2070
2060
|
if (this.jsonOutput) {
|
|
2071
2061
|
process.exitCode = 1;
|
|
@@ -2125,12 +2115,12 @@ class ChatCommand {
|
|
|
2125
2115
|
args: { path: targetFile },
|
|
2126
2116
|
};
|
|
2127
2117
|
if (!this.jsonOutput) {
|
|
2128
|
-
console.log(
|
|
2118
|
+
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
2129
2119
|
}
|
|
2130
2120
|
const readResult = await this.tools.execute(readCall);
|
|
2131
2121
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
2132
2122
|
if (!this.jsonOutput) {
|
|
2133
|
-
console.log(readResult.success ?
|
|
2123
|
+
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
2134
2124
|
}
|
|
2135
2125
|
if (readResult.success && readResult.output) {
|
|
2136
2126
|
this.messages.push({ role: 'system', content: readSummary });
|
|
@@ -2152,12 +2142,12 @@ class ChatCommand {
|
|
|
2152
2142
|
args: { path: targetFile },
|
|
2153
2143
|
};
|
|
2154
2144
|
if (!this.jsonOutput) {
|
|
2155
|
-
console.log(
|
|
2145
|
+
console.log(chalk.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
2156
2146
|
}
|
|
2157
2147
|
const readResult = await this.tools.execute(readCall);
|
|
2158
2148
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
2159
2149
|
if (!this.jsonOutput) {
|
|
2160
|
-
console.log(readResult.success ?
|
|
2150
|
+
console.log(readResult.success ? chalk.gray(readSummary) : chalk.red(readSummary));
|
|
2161
2151
|
}
|
|
2162
2152
|
this.messages.push({ role: 'system', content: readSummary });
|
|
2163
2153
|
if (!readResult.success || !readResult.output) {
|
|
@@ -2200,12 +2190,12 @@ class ChatCommand {
|
|
|
2200
2190
|
},
|
|
2201
2191
|
};
|
|
2202
2192
|
if (!this.jsonOutput) {
|
|
2203
|
-
console.log(
|
|
2193
|
+
console.log(chalk.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
2204
2194
|
}
|
|
2205
2195
|
const writeResult = await this.tools.execute(writeCall);
|
|
2206
2196
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
2207
2197
|
if (!this.jsonOutput) {
|
|
2208
|
-
console.log(writeResult.success ?
|
|
2198
|
+
console.log(writeResult.success ? chalk.gray(writeSummary) : chalk.red(writeSummary));
|
|
2209
2199
|
}
|
|
2210
2200
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
2211
2201
|
if (!writeResult.success) {
|
|
@@ -2241,10 +2231,10 @@ class ChatCommand {
|
|
|
2241
2231
|
console.log(`Updated ${targetFile}.`);
|
|
2242
2232
|
if (previewGate.required) {
|
|
2243
2233
|
if (previewGate.passed) {
|
|
2244
|
-
console.log(
|
|
2234
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
2245
2235
|
}
|
|
2246
2236
|
else {
|
|
2247
|
-
console.log(
|
|
2237
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
2248
2238
|
}
|
|
2249
2239
|
}
|
|
2250
2240
|
}
|
|
@@ -2254,6 +2244,9 @@ class ChatCommand {
|
|
|
2254
2244
|
const normalized = prompt.trim().toLowerCase().replace(/[.!?]+$/g, '').replace(/\s+/g, ' ');
|
|
2255
2245
|
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);
|
|
2256
2246
|
}
|
|
2247
|
+
taskRequiresWorkspaceChanges(prompt) {
|
|
2248
|
+
return /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|game|app|website|html5|frontend|component|feature)\b/i.test(prompt);
|
|
2249
|
+
}
|
|
2257
2250
|
getPreviousActionablePrompt() {
|
|
2258
2251
|
if (this.lastActionableUserInput && !this.isConfirmationFollowUp(this.lastActionableUserInput)) {
|
|
2259
2252
|
return this.lastActionableUserInput;
|
|
@@ -2297,7 +2290,7 @@ class ChatCommand {
|
|
|
2297
2290
|
try {
|
|
2298
2291
|
promptWorkspacePath = this.resolvePromptWorkspacePath(prompt, this.currentProjectPath);
|
|
2299
2292
|
if (promptWorkspacePath && !this.jsonOutput) {
|
|
2300
|
-
console.log(
|
|
2293
|
+
console.log(chalk.cyan(`📁 Workspace from prompt: ${promptWorkspacePath}`));
|
|
2301
2294
|
}
|
|
2302
2295
|
}
|
|
2303
2296
|
catch {
|
|
@@ -2314,15 +2307,15 @@ class ChatCommand {
|
|
|
2314
2307
|
// STREAMING: Log routing decision transparently to user
|
|
2315
2308
|
if (!this.jsonOutput) {
|
|
2316
2309
|
console.log();
|
|
2317
|
-
console.log(
|
|
2318
|
-
console.log(
|
|
2319
|
-
console.log(
|
|
2320
|
-
console.log(
|
|
2321
|
-
console.log(
|
|
2310
|
+
console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
|
|
2311
|
+
console.log(chalk.gray(`Reason: ${routingPolicy.routeReason}`));
|
|
2312
|
+
console.log(chalk.gray(`Model: ${routingPolicy.selectedModel}`));
|
|
2313
|
+
console.log(chalk.gray(`Cloud Eligible: ${routingPolicy.cloudEligible}`));
|
|
2314
|
+
console.log(chalk.gray(`Cloud Selected: ${routingPolicy.cloudSelected}`));
|
|
2322
2315
|
if (routingPolicy.heavyTask) {
|
|
2323
|
-
console.log(
|
|
2316
|
+
console.log(chalk.gray(`Task Complexity: HEAVY`));
|
|
2324
2317
|
}
|
|
2325
|
-
console.log(
|
|
2318
|
+
console.log(chalk.gray('━'.repeat(30)));
|
|
2326
2319
|
console.log();
|
|
2327
2320
|
}
|
|
2328
2321
|
// Reset streaming counters for new workflow
|
|
@@ -2330,9 +2323,9 @@ class ChatCommand {
|
|
|
2330
2323
|
this.v3ToolCallCount = 0;
|
|
2331
2324
|
this.v3LastActivity = Date.now();
|
|
2332
2325
|
this.v3StreamingStarted = false;
|
|
2333
|
-
const taskDisplay = new
|
|
2326
|
+
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
2334
2327
|
taskDisplay.start(0);
|
|
2335
|
-
const spinner = this.jsonOutput ? null :
|
|
2328
|
+
const spinner = this.jsonOutput ? null : createSpinner({
|
|
2336
2329
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
2337
2330
|
spinner: 'clock',
|
|
2338
2331
|
}).start();
|
|
@@ -2401,7 +2394,7 @@ class ChatCommand {
|
|
|
2401
2394
|
// Start workspace watcher for bidirectional real-time sync
|
|
2402
2395
|
let watcher = null;
|
|
2403
2396
|
if (this.shouldStartWorkspaceWatcher(workspacePath)) {
|
|
2404
|
-
watcher = new
|
|
2397
|
+
watcher = new WorkspaceWatcher({
|
|
2405
2398
|
workspaceRoot: workspacePath,
|
|
2406
2399
|
onFileChange: (relativePath, content, action) => {
|
|
2407
2400
|
this.logger.debug(`Local change detected: ${action} ${relativePath}`);
|
|
@@ -2510,6 +2503,8 @@ class ChatCommand {
|
|
|
2510
2503
|
}
|
|
2511
2504
|
const previewGate = (response.metadata?.previewGate || null);
|
|
2512
2505
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2506
|
+
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2507
|
+
const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
2513
2508
|
const success = previewGate?.required === true
|
|
2514
2509
|
? (previewGate?.passed === true || workspaceHasOutput)
|
|
2515
2510
|
: true;
|
|
@@ -2545,7 +2540,7 @@ class ChatCommand {
|
|
|
2545
2540
|
return true;
|
|
2546
2541
|
}
|
|
2547
2542
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
2548
|
-
console.log(
|
|
2543
|
+
console.log(chalk.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
|
|
2549
2544
|
}
|
|
2550
2545
|
if (this.jsonOutput) {
|
|
2551
2546
|
console.log(JSON.stringify({
|
|
@@ -2563,41 +2558,41 @@ class ChatCommand {
|
|
|
2563
2558
|
else if (this.v3StreamingStarted) {
|
|
2564
2559
|
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
2565
2560
|
if (!this.jsonOutput) {
|
|
2566
|
-
console.log(
|
|
2561
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2567
2562
|
}
|
|
2568
2563
|
}
|
|
2569
2564
|
else if (response.content) {
|
|
2570
2565
|
if (!this.directPromptMode) {
|
|
2571
|
-
console.log(
|
|
2566
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2572
2567
|
}
|
|
2573
2568
|
console.log(response.content);
|
|
2574
2569
|
}
|
|
2575
2570
|
else {
|
|
2576
2571
|
if (!this.directPromptMode) {
|
|
2577
|
-
console.log(
|
|
2572
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2578
2573
|
}
|
|
2579
2574
|
console.log('V3 agent workflow completed.');
|
|
2580
2575
|
}
|
|
2581
2576
|
if (!this.jsonOutput && previewGate?.required) {
|
|
2582
2577
|
if (previewGate.passed) {
|
|
2583
|
-
console.log(
|
|
2578
|
+
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
2584
2579
|
}
|
|
2585
2580
|
else {
|
|
2586
|
-
console.log(
|
|
2581
|
+
console.log(chalk.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
2587
2582
|
}
|
|
2588
2583
|
}
|
|
2589
2584
|
// Show change summary for files touched by the agent
|
|
2590
2585
|
if (!this.jsonOutput && !this.directPromptMode && response.changedFiles) {
|
|
2591
|
-
const fileCount =
|
|
2586
|
+
const fileCount = changedFileCount;
|
|
2592
2587
|
if (fileCount > 0) {
|
|
2593
|
-
console.log(
|
|
2588
|
+
console.log(chalk.gray(`\nFiles changed: ${fileCount}`));
|
|
2594
2589
|
for (const relPath of Object.keys(response.changedFiles).slice(0, 15)) {
|
|
2595
|
-
console.log(
|
|
2590
|
+
console.log(chalk.gray(` ${chalk.green('+')} ${relPath}`));
|
|
2596
2591
|
}
|
|
2597
2592
|
if (fileCount > 15) {
|
|
2598
|
-
console.log(
|
|
2593
|
+
console.log(chalk.gray(` ... and ${fileCount - 15} more`));
|
|
2599
2594
|
}
|
|
2600
|
-
console.log(
|
|
2595
|
+
console.log(chalk.gray(`Run ${chalk.cyan('vigthoria preview --diff')} for full visual diffs.`));
|
|
2601
2596
|
}
|
|
2602
2597
|
}
|
|
2603
2598
|
// Resolve the Execute-tasks spinner: only mark it ✓ when the run actually
|
|
@@ -2608,7 +2603,11 @@ class ChatCommand {
|
|
|
2608
2603
|
const executorSucceeded = !liveOutcome.executorFailed
|
|
2609
2604
|
&& !liveOutcome.plannerError
|
|
2610
2605
|
&& !liveOutcome.executorError
|
|
2611
|
-
&& workspaceHasOutput
|
|
2606
|
+
&& workspaceHasOutput
|
|
2607
|
+
&& (!requiresWorkspaceChanges || changedFileCount > 0);
|
|
2608
|
+
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
|
|
2609
|
+
liveOutcome.executorError = 'No workspace files were changed for a build/edit request.';
|
|
2610
|
+
}
|
|
2612
2611
|
if (executorSucceeded) {
|
|
2613
2612
|
taskDisplay.complete(1);
|
|
2614
2613
|
}
|
|
@@ -2635,8 +2634,8 @@ class ChatCommand {
|
|
|
2635
2634
|
}
|
|
2636
2635
|
selfHealTool = healResult.tool || null;
|
|
2637
2636
|
if (!this.directPromptMode) {
|
|
2638
|
-
const hs = healResult.passed ?
|
|
2639
|
-
console.log(
|
|
2637
|
+
const hs = healResult.passed ? chalk.green('passed') : chalk.yellow('partial');
|
|
2638
|
+
console.log(chalk.gray(`Self-healing: ${hs} (${healResult.tool})`));
|
|
2640
2639
|
}
|
|
2641
2640
|
}
|
|
2642
2641
|
else {
|
|
@@ -2673,12 +2672,11 @@ class ChatCommand {
|
|
|
2673
2672
|
hasOutput: workspaceHasOutput,
|
|
2674
2673
|
selfHealStatus,
|
|
2675
2674
|
selfHealTool,
|
|
2676
|
-
plannerError: liveOutcome.plannerError ?
|
|
2677
|
-
executorError: liveOutcome.executorError ?
|
|
2675
|
+
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
2676
|
+
executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : null,
|
|
2678
2677
|
finishedAt: Date.now(),
|
|
2679
2678
|
};
|
|
2680
2679
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
2681
|
-
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2682
2680
|
this.printAgentRunSummary(this.lastAgentRunOutcome, executorSucceeded, changedFileCount);
|
|
2683
2681
|
}
|
|
2684
2682
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
@@ -2698,13 +2696,13 @@ class ChatCommand {
|
|
|
2698
2696
|
spinner.stop();
|
|
2699
2697
|
}
|
|
2700
2698
|
this.logger.warn('Falling back to legacy CLI agent loop');
|
|
2701
|
-
this.logger.debug(`V3 agent workflow unavailable: ${
|
|
2699
|
+
this.logger.debug(`V3 agent workflow unavailable: ${sanitizeUserFacingErrorText(error.message || '')}`);
|
|
2702
2700
|
return false;
|
|
2703
2701
|
}
|
|
2704
2702
|
if (spinner) {
|
|
2705
2703
|
spinner.stop();
|
|
2706
2704
|
}
|
|
2707
|
-
const safeDetail =
|
|
2705
|
+
const safeDetail = sanitizeUserFacingErrorText(error.message || '');
|
|
2708
2706
|
const errorMessage = safeDetail
|
|
2709
2707
|
? `Agent mode is unavailable right now. ${safeDetail}`
|
|
2710
2708
|
: 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
|
|
@@ -2733,8 +2731,8 @@ class ChatCommand {
|
|
|
2733
2731
|
hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
|
|
2734
2732
|
selfHealStatus: 'skipped',
|
|
2735
2733
|
selfHealTool: null,
|
|
2736
|
-
plannerError: liveOutcome.plannerError ?
|
|
2737
|
-
executorError: liveOutcome.executorError ?
|
|
2734
|
+
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
2735
|
+
executorError: liveOutcome.executorError ? sanitizeUserFacingErrorText(liveOutcome.executorError) : safeDetail || null,
|
|
2738
2736
|
finishedAt: Date.now(),
|
|
2739
2737
|
};
|
|
2740
2738
|
if (this.jsonOutput) {
|
|
@@ -2764,7 +2762,7 @@ class ChatCommand {
|
|
|
2764
2762
|
spinner.stop();
|
|
2765
2763
|
}
|
|
2766
2764
|
if (!this.jsonOutput) {
|
|
2767
|
-
console.log(
|
|
2765
|
+
console.log(chalk.yellow(`V3 recovery: ${recovery.message} Retrying once...`));
|
|
2768
2766
|
}
|
|
2769
2767
|
this.v3IterationCount = 0;
|
|
2770
2768
|
this.v3ToolCallCount = 0;
|
|
@@ -2808,7 +2806,7 @@ class ChatCommand {
|
|
|
2808
2806
|
}, null, 2));
|
|
2809
2807
|
}
|
|
2810
2808
|
else if (!this.v3StreamingStarted && retryResponse.content) {
|
|
2811
|
-
console.log(
|
|
2809
|
+
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2812
2810
|
console.log(retryResponse.content);
|
|
2813
2811
|
}
|
|
2814
2812
|
this.messages.push({ role: 'assistant', content: retryResponse.content || 'V3 agent workflow completed after recovery.' });
|
|
@@ -2825,10 +2823,10 @@ class ChatCommand {
|
|
|
2825
2823
|
? 'Interactive Agent Chat'
|
|
2826
2824
|
: 'Interactive Chat';
|
|
2827
2825
|
this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
|
|
2828
|
-
console.log(
|
|
2829
|
-
console.log(
|
|
2826
|
+
console.log(chalk.gray('Type /help for commands. Type /exit to quit.'));
|
|
2827
|
+
console.log(chalk.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
|
|
2830
2828
|
if (this.workflowTarget) {
|
|
2831
|
-
console.log(
|
|
2829
|
+
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
2832
2830
|
}
|
|
2833
2831
|
const rl = readline.createInterface({
|
|
2834
2832
|
input: process.stdin,
|
|
@@ -2838,17 +2836,17 @@ class ChatCommand {
|
|
|
2838
2836
|
const readMultiLineInput = async () => {
|
|
2839
2837
|
const lines = [];
|
|
2840
2838
|
let firstLine = await new Promise((resolve) => {
|
|
2841
|
-
rl.question(
|
|
2839
|
+
rl.question(chalk.blue('> '), resolve);
|
|
2842
2840
|
});
|
|
2843
2841
|
// Check for {{{ block mode
|
|
2844
2842
|
if (firstLine.trim() === '{{{' || firstLine.trim().endsWith('{{{')) {
|
|
2845
2843
|
if (firstLine.trim() !== '{{{') {
|
|
2846
2844
|
lines.push(firstLine.trim().replace(/\{\{\{$/, '').trim());
|
|
2847
2845
|
}
|
|
2848
|
-
console.log(
|
|
2846
|
+
console.log(chalk.gray(' (multi-line mode: type }}} on its own line to finish)'));
|
|
2849
2847
|
while (true) {
|
|
2850
2848
|
const line = await new Promise((resolve) => {
|
|
2851
|
-
rl.question(
|
|
2849
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2852
2850
|
});
|
|
2853
2851
|
if (line.trim() === '}}}')
|
|
2854
2852
|
break;
|
|
@@ -2860,7 +2858,7 @@ class ChatCommand {
|
|
|
2860
2858
|
while (firstLine.endsWith('\\')) {
|
|
2861
2859
|
lines.push(firstLine.slice(0, -1));
|
|
2862
2860
|
firstLine = await new Promise((resolve) => {
|
|
2863
|
-
rl.question(
|
|
2861
|
+
rl.question(chalk.gray(' '), resolve);
|
|
2864
2862
|
});
|
|
2865
2863
|
}
|
|
2866
2864
|
lines.push(firstLine);
|
|
@@ -2901,8 +2899,8 @@ class ChatCommand {
|
|
|
2901
2899
|
else {
|
|
2902
2900
|
this.syncInteractiveModeModel('chat');
|
|
2903
2901
|
}
|
|
2904
|
-
console.log(
|
|
2905
|
-
console.log(
|
|
2902
|
+
console.log(chalk.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
|
|
2903
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2906
2904
|
if (this.currentSession) {
|
|
2907
2905
|
this.currentSession.agentMode = this.agentMode;
|
|
2908
2906
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2924,8 +2922,8 @@ class ChatCommand {
|
|
|
2924
2922
|
else {
|
|
2925
2923
|
this.syncInteractiveModeModel('chat');
|
|
2926
2924
|
}
|
|
2927
|
-
console.log(
|
|
2928
|
-
console.log(
|
|
2925
|
+
console.log(chalk.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
|
|
2926
|
+
console.log(chalk.gray(`Model: ${this.currentModel}`));
|
|
2929
2927
|
if (this.currentSession) {
|
|
2930
2928
|
this.currentSession.agentMode = this.agentMode;
|
|
2931
2929
|
this.currentSession.operatorMode = this.operatorMode;
|
|
@@ -2936,7 +2934,7 @@ class ChatCommand {
|
|
|
2936
2934
|
}
|
|
2937
2935
|
if (trimmed === '/clear') {
|
|
2938
2936
|
this.messages = [];
|
|
2939
|
-
console.log(
|
|
2937
|
+
console.log(chalk.yellow('Conversation cleared.'));
|
|
2940
2938
|
continue;
|
|
2941
2939
|
}
|
|
2942
2940
|
if (trimmed === '/status') {
|
|
@@ -2946,7 +2944,7 @@ class ChatCommand {
|
|
|
2946
2944
|
if (trimmed === '/retry') {
|
|
2947
2945
|
const followUp = this.buildRetryPrompt();
|
|
2948
2946
|
if (!followUp) {
|
|
2949
|
-
console.log(
|
|
2947
|
+
console.log(chalk.yellow('Nothing to retry — run an agent task first.'));
|
|
2950
2948
|
continue;
|
|
2951
2949
|
}
|
|
2952
2950
|
const nextSignature = this.computeRetryPromptSignature();
|
|
@@ -2960,11 +2958,11 @@ class ChatCommand {
|
|
|
2960
2958
|
if (this.retryPromptStreak >= 3) {
|
|
2961
2959
|
const continuePrompt = this.buildContinuePrompt();
|
|
2962
2960
|
if (continuePrompt) {
|
|
2963
|
-
console.log(
|
|
2961
|
+
console.log(chalk.yellow('Repeated /retry detected with the same failing task set. Escalating to /continue to avoid planner loops.'));
|
|
2964
2962
|
if (!this.agentMode) {
|
|
2965
2963
|
this.agentMode = true;
|
|
2966
2964
|
this.syncInteractiveModeModel('agent');
|
|
2967
|
-
console.log(
|
|
2965
|
+
console.log(chalk.gray('Agent mode re-enabled for continuation.'));
|
|
2968
2966
|
}
|
|
2969
2967
|
await this.runAgentTurn(continuePrompt);
|
|
2970
2968
|
continue;
|
|
@@ -2973,34 +2971,34 @@ class ChatCommand {
|
|
|
2973
2971
|
if (!this.agentMode) {
|
|
2974
2972
|
this.agentMode = true;
|
|
2975
2973
|
this.syncInteractiveModeModel('agent');
|
|
2976
|
-
console.log(
|
|
2974
|
+
console.log(chalk.gray('Agent mode re-enabled for retry.'));
|
|
2977
2975
|
}
|
|
2978
2976
|
await this.runAgentTurn(followUp);
|
|
2979
2977
|
continue;
|
|
2980
2978
|
}
|
|
2981
|
-
if (trimmed === '/continue') {
|
|
2979
|
+
if (trimmed === '/continue' || trimmed === '/contiunue' || trimmed === '/contiune') {
|
|
2982
2980
|
const followUp = this.buildContinuePrompt();
|
|
2983
2981
|
if (!followUp) {
|
|
2984
|
-
console.log(
|
|
2982
|
+
console.log(chalk.yellow('Nothing to continue — run an agent task first.'));
|
|
2985
2983
|
continue;
|
|
2986
2984
|
}
|
|
2987
2985
|
if (!this.agentMode) {
|
|
2988
2986
|
this.agentMode = true;
|
|
2989
2987
|
this.syncInteractiveModeModel('agent');
|
|
2990
|
-
console.log(
|
|
2988
|
+
console.log(chalk.gray('Agent mode re-enabled for continuation.'));
|
|
2991
2989
|
}
|
|
2992
2990
|
await this.runAgentTurn(followUp);
|
|
2993
2991
|
continue;
|
|
2994
2992
|
}
|
|
2995
2993
|
if (trimmed === '/save') {
|
|
2996
2994
|
this.saveSession();
|
|
2997
|
-
console.log(
|
|
2995
|
+
console.log(chalk.green('Session saved.'));
|
|
2998
2996
|
continue;
|
|
2999
2997
|
}
|
|
3000
2998
|
if (trimmed.startsWith('/model ')) {
|
|
3001
2999
|
this.currentModel = trimmed.slice(7).trim() || this.currentModel;
|
|
3002
3000
|
this.modelExplicitlySelected = true;
|
|
3003
|
-
console.log(
|
|
3001
|
+
console.log(chalk.yellow(`Model changed to: ${this.currentModel}`));
|
|
3004
3002
|
if (this.currentSession) {
|
|
3005
3003
|
this.currentSession.model = this.currentModel;
|
|
3006
3004
|
this.saveSession();
|
|
@@ -3048,71 +3046,102 @@ class ChatCommand {
|
|
|
3048
3046
|
* - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
|
|
3049
3047
|
*/
|
|
3050
3048
|
printAgentRunSummary(outcome, executorSucceeded, changedFileCount) {
|
|
3051
|
-
const bar =
|
|
3052
|
-
const ok =
|
|
3053
|
-
const warn =
|
|
3054
|
-
const bad =
|
|
3049
|
+
const bar = chalk.gray('─'.repeat(63));
|
|
3050
|
+
const ok = chalk.green('✓');
|
|
3051
|
+
const warn = chalk.yellow('⚠');
|
|
3052
|
+
const bad = chalk.red('✗');
|
|
3055
3053
|
const failedList = outcome.failedTaskIds.slice(0, 8);
|
|
3056
3054
|
const unfinishedList = outcome.unfinishedTaskIds.slice(0, 8);
|
|
3057
3055
|
const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
|
|
3058
3056
|
console.log('');
|
|
3059
3057
|
console.log(bar);
|
|
3060
3058
|
if (executorSucceeded && outcome.selfHealStatus !== 'partial' && outcome.selfHealStatus !== 'failed' && failedList.length === 0) {
|
|
3061
|
-
console.log(`${ok} ${
|
|
3059
|
+
console.log(`${ok} ${chalk.bold('Agent run finished')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3062
3060
|
}
|
|
3063
3061
|
else if (executorSucceeded) {
|
|
3064
|
-
console.log(`${warn} ${
|
|
3062
|
+
console.log(`${warn} ${chalk.bold('Agent run finished with warnings')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3065
3063
|
}
|
|
3066
3064
|
else {
|
|
3067
|
-
console.log(`${bad} ${
|
|
3065
|
+
console.log(`${bad} ${chalk.bold('Agent run did not complete')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3068
3066
|
}
|
|
3069
3067
|
if (hasTaskInfo) {
|
|
3070
3068
|
const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
|
|
3071
|
-
console.log(
|
|
3069
|
+
console.log(chalk.gray(` Tasks completed: ${succ}`));
|
|
3072
3070
|
if (failedList.length > 0) {
|
|
3073
|
-
const more = outcome.failedTaskIds.length > failedList.length ?
|
|
3074
|
-
console.log(
|
|
3071
|
+
const more = outcome.failedTaskIds.length > failedList.length ? chalk.gray(` (+${outcome.failedTaskIds.length - failedList.length} more)`) : '';
|
|
3072
|
+
console.log(chalk.gray(' Failed: ') + chalk.red(failedList.join(', ')) + more);
|
|
3075
3073
|
}
|
|
3076
3074
|
if (unfinishedList.length > 0) {
|
|
3077
|
-
const more = outcome.unfinishedTaskIds.length > unfinishedList.length ?
|
|
3078
|
-
console.log(
|
|
3075
|
+
const more = outcome.unfinishedTaskIds.length > unfinishedList.length ? chalk.gray(` (+${outcome.unfinishedTaskIds.length - unfinishedList.length} more)`) : '';
|
|
3076
|
+
console.log(chalk.gray(' Pending: ') + chalk.yellow(unfinishedList.join(', ')) + more);
|
|
3079
3077
|
}
|
|
3080
3078
|
}
|
|
3081
3079
|
if (typeof outcome.qualityScore === 'number') {
|
|
3082
3080
|
const score = outcome.qualityScore.toFixed(1);
|
|
3083
|
-
const colour = outcome.qualityScore >= 70 ?
|
|
3084
|
-
console.log(
|
|
3081
|
+
const colour = outcome.qualityScore >= 70 ? chalk.green : outcome.qualityScore >= 30 ? chalk.yellow : chalk.red;
|
|
3082
|
+
console.log(chalk.gray(' Quality: ') + colour(`${score}/100`));
|
|
3085
3083
|
}
|
|
3086
3084
|
if (outcome.qualityBlockers.length > 0) {
|
|
3087
3085
|
const list = outcome.qualityBlockers.slice(0, 3).join('; ');
|
|
3088
|
-
const more = outcome.qualityBlockers.length > 3 ?
|
|
3089
|
-
console.log(
|
|
3086
|
+
const more = outcome.qualityBlockers.length > 3 ? chalk.gray(` (+${outcome.qualityBlockers.length - 3} more)`) : '';
|
|
3087
|
+
console.log(chalk.gray(' Blockers: ') + chalk.yellow(list) + more);
|
|
3090
3088
|
}
|
|
3091
3089
|
if (outcome.qualityMissing.length > 0) {
|
|
3092
3090
|
const list = outcome.qualityMissing.slice(0, 5).join(', ');
|
|
3093
|
-
const more = outcome.qualityMissing.length > 5 ?
|
|
3094
|
-
console.log(
|
|
3091
|
+
const more = outcome.qualityMissing.length > 5 ? chalk.gray(` (+${outcome.qualityMissing.length - 5} more)`) : '';
|
|
3092
|
+
console.log(chalk.gray(' Missing: ') + chalk.yellow(list) + more);
|
|
3095
3093
|
}
|
|
3096
3094
|
if (outcome.plannerError) {
|
|
3097
|
-
console.log(
|
|
3095
|
+
console.log(chalk.gray(' Planner: ') + chalk.red(outcome.plannerError.slice(0, 140)));
|
|
3098
3096
|
}
|
|
3099
3097
|
if (outcome.executorError) {
|
|
3100
|
-
console.log(
|
|
3098
|
+
console.log(chalk.gray(' Executor: ') + chalk.red(outcome.executorError.slice(0, 140)));
|
|
3099
|
+
}
|
|
3100
|
+
console.log('');
|
|
3101
|
+
console.log(chalk.gray('Result:'));
|
|
3102
|
+
if (executorSucceeded) {
|
|
3103
|
+
const filePart = changedFileCount > 0
|
|
3104
|
+
? ` and updated ${changedFileCount} workspace file${changedFileCount === 1 ? '' : 's'}`
|
|
3105
|
+
: '';
|
|
3106
|
+
console.log(' ' + chalk.green(`The ${chalk.bold('Agent')} mode completed the request${filePart}.`));
|
|
3107
|
+
}
|
|
3108
|
+
else {
|
|
3109
|
+
console.log(' ' + chalk.yellow('The Agent mode stopped before every requested task was confirmed complete.'));
|
|
3101
3110
|
}
|
|
3102
3111
|
console.log('');
|
|
3103
|
-
console.log(
|
|
3112
|
+
console.log(chalk.gray('What was done:'));
|
|
3113
|
+
if (hasTaskInfo) {
|
|
3114
|
+
const taskLine = outcome.tasksTotal > 0
|
|
3115
|
+
? `${outcome.tasksSucceeded} of ${outcome.tasksTotal} planned task${outcome.tasksTotal === 1 ? '' : 's'} finished`
|
|
3116
|
+
: `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
|
|
3117
|
+
console.log(' ' + chalk.white(taskLine + '.'));
|
|
3118
|
+
}
|
|
3119
|
+
else if (changedFileCount > 0) {
|
|
3120
|
+
console.log(' ' + chalk.white(`Workspace changes were produced and are ready for review.`));
|
|
3121
|
+
}
|
|
3122
|
+
else if (outcome.hasOutput) {
|
|
3123
|
+
console.log(' ' + chalk.white('The assistant returned a final answer for your request.'));
|
|
3124
|
+
}
|
|
3125
|
+
else {
|
|
3126
|
+
console.log(' ' + chalk.white('No final workspace output was confirmed.'));
|
|
3127
|
+
}
|
|
3128
|
+
if (outcome.selfHealStatus && outcome.selfHealStatus !== 'skipped') {
|
|
3129
|
+
console.log(' ' + chalk.white(`Self-check status: ${outcome.selfHealStatus}.`));
|
|
3130
|
+
}
|
|
3131
|
+
console.log('');
|
|
3132
|
+
console.log(chalk.gray('What you can do next:'));
|
|
3104
3133
|
if (failedList.length > 0 || unfinishedList.length > 0 || !executorSucceeded) {
|
|
3105
|
-
console.log(' ' +
|
|
3106
|
-
console.log(' ' +
|
|
3134
|
+
console.log(' ' + chalk.cyan('/retry') + chalk.gray(' resume the failed/unfinished tasks only'));
|
|
3135
|
+
console.log(' ' + chalk.cyan('/continue') + chalk.gray(' ask the agent to keep working from this state'));
|
|
3107
3136
|
}
|
|
3108
3137
|
else {
|
|
3109
|
-
console.log(' ' +
|
|
3138
|
+
console.log(' ' + chalk.cyan('type your next request') + chalk.gray(' continue the conversation'));
|
|
3110
3139
|
}
|
|
3111
3140
|
if (changedFileCount > 0) {
|
|
3112
|
-
console.log(' ' +
|
|
3141
|
+
console.log(' ' + chalk.cyan('vigthoria preview --diff') + chalk.gray(' inspect the file changes'));
|
|
3113
3142
|
}
|
|
3114
|
-
console.log(' ' +
|
|
3115
|
-
console.log(' ' +
|
|
3143
|
+
console.log(' ' + chalk.cyan('/status') + chalk.gray(' re-print this summary later'));
|
|
3144
|
+
console.log(' ' + chalk.cyan('/exit') + chalk.gray(' leave interactive chat'));
|
|
3116
3145
|
console.log(bar);
|
|
3117
3146
|
console.log('');
|
|
3118
3147
|
}
|
|
@@ -3160,21 +3189,24 @@ class ChatCommand {
|
|
|
3160
3189
|
return null;
|
|
3161
3190
|
const remaining = [...new Set([...o.failedTaskIds, ...o.unfinishedTaskIds])];
|
|
3162
3191
|
const taskList = remaining.length > 0 ? `\nRemaining tasks to finish: ${remaining.join(', ')}.` : '';
|
|
3192
|
+
const mustChangeFiles = this.taskRequiresWorkspaceChanges(o.prompt)
|
|
3193
|
+
? '\nThis was a build/edit request. Do not stop after analysis or tool JSON; make real workspace file changes and verify them before finishing.'
|
|
3194
|
+
: '';
|
|
3163
3195
|
const blockerLine = o.qualityBlockers.length > 0
|
|
3164
3196
|
? `\nKnown blockers to address: ${o.qualityBlockers.slice(0, 3).join('; ')}.`
|
|
3165
3197
|
: '';
|
|
3166
3198
|
const missingLine = o.qualityMissing.length > 0
|
|
3167
3199
|
? `\nMissing pieces: ${o.qualityMissing.slice(0, 6).join(', ')}.`
|
|
3168
3200
|
: '';
|
|
3169
|
-
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}`;
|
|
3201
|
+
return `Continue the previous agent run from the current workspace state without re-doing already-completed work.${taskList}${mustChangeFiles}${blockerLine}${missingLine}\nOriginal request was: ${o.prompt}`;
|
|
3170
3202
|
}
|
|
3171
3203
|
/**
|
|
3172
3204
|
* Re-print the last agent run summary, or guide the user when there isn't one.
|
|
3173
3205
|
*/
|
|
3174
3206
|
showAgentRunStatus() {
|
|
3175
3207
|
if (!this.lastAgentRunOutcome) {
|
|
3176
|
-
console.log(
|
|
3177
|
-
console.log(
|
|
3208
|
+
console.log(chalk.yellow('No agent run has finished in this session yet.'));
|
|
3209
|
+
console.log(chalk.gray('Toggle agent mode with /agent and send a request to start one.'));
|
|
3178
3210
|
return;
|
|
3179
3211
|
}
|
|
3180
3212
|
const o = this.lastAgentRunOutcome;
|
|
@@ -3187,45 +3219,45 @@ class ChatCommand {
|
|
|
3187
3219
|
}
|
|
3188
3220
|
showContext() {
|
|
3189
3221
|
if (!this.currentSession) {
|
|
3190
|
-
console.log(
|
|
3222
|
+
console.log(chalk.yellow('No active session.'));
|
|
3191
3223
|
return;
|
|
3192
3224
|
}
|
|
3193
|
-
console.log(
|
|
3225
|
+
console.log(chalk.cyan(this.getCurrentSessionInfo()));
|
|
3194
3226
|
if (this.currentSession.memorySummary?.trim()) {
|
|
3195
3227
|
console.log();
|
|
3196
|
-
console.log(
|
|
3197
|
-
console.log(
|
|
3228
|
+
console.log(chalk.white('Compact Session Memory:'));
|
|
3229
|
+
console.log(chalk.gray(this.currentSession.memorySummary.trim()));
|
|
3198
3230
|
}
|
|
3199
3231
|
else {
|
|
3200
|
-
console.log(
|
|
3232
|
+
console.log(chalk.gray('No compact session memory summary yet.'));
|
|
3201
3233
|
}
|
|
3202
3234
|
const runtime = this.getRuntimeEnvironmentContext();
|
|
3203
3235
|
console.log();
|
|
3204
|
-
console.log(
|
|
3205
|
-
console.log(
|
|
3206
|
-
console.log(
|
|
3207
|
-
console.log(
|
|
3208
|
-
console.log(
|
|
3209
|
-
console.log(
|
|
3236
|
+
console.log(chalk.white('Runtime Environment:'));
|
|
3237
|
+
console.log(chalk.gray(`Platform: ${runtime.platform} (${runtime.osPlatform})`));
|
|
3238
|
+
console.log(chalk.gray(`Machine scope: ${runtime.machineScope}`));
|
|
3239
|
+
console.log(chalk.gray(`Workspace: ${runtime.workspacePath}`));
|
|
3240
|
+
console.log(chalk.gray(`CWD: ${runtime.cwd}`));
|
|
3241
|
+
console.log(chalk.gray(`Server-bindable workspace: ${runtime.serverBindableWorkspace ? 'yes' : 'no'}`));
|
|
3210
3242
|
this.showProjectMemory();
|
|
3211
3243
|
}
|
|
3212
3244
|
showProjectMemory() {
|
|
3213
3245
|
if (!this.projectMemory) {
|
|
3214
|
-
this.projectMemory = new
|
|
3246
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
3215
3247
|
}
|
|
3216
3248
|
const status = this.projectMemory.getStatus();
|
|
3217
3249
|
console.log();
|
|
3218
|
-
console.log(
|
|
3219
|
-
console.log(
|
|
3220
|
-
console.log(
|
|
3250
|
+
console.log(chalk.white('Project Brain:'));
|
|
3251
|
+
console.log(chalk.gray(`Path: ${status.memoryDir}`));
|
|
3252
|
+
console.log(chalk.gray(`Items: ${status.itemCount}`));
|
|
3221
3253
|
const typeSummary = Object.entries(status.typeCounts).map(([type, count]) => `${type}=${count}`).join(', ');
|
|
3222
3254
|
if (typeSummary) {
|
|
3223
|
-
console.log(
|
|
3255
|
+
console.log(chalk.gray(`Types: ${typeSummary}`));
|
|
3224
3256
|
}
|
|
3225
3257
|
}
|
|
3226
3258
|
compactCurrentSession() {
|
|
3227
3259
|
if (!this.currentSession) {
|
|
3228
|
-
console.log(
|
|
3260
|
+
console.log(chalk.yellow('No active session.'));
|
|
3229
3261
|
return;
|
|
3230
3262
|
}
|
|
3231
3263
|
this.currentSession.messages = [...this.messages];
|
|
@@ -3233,7 +3265,7 @@ class ChatCommand {
|
|
|
3233
3265
|
this.messages = [...this.currentSession.messages];
|
|
3234
3266
|
this.sessionManager.save(this.currentSession);
|
|
3235
3267
|
if (!this.projectMemory) {
|
|
3236
|
-
this.projectMemory = new
|
|
3268
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
3237
3269
|
}
|
|
3238
3270
|
this.projectMemory.rememberConversation(this.messages, {
|
|
3239
3271
|
source: 'manual-compact',
|
|
@@ -3241,7 +3273,7 @@ class ChatCommand {
|
|
|
3241
3273
|
model: this.currentModel,
|
|
3242
3274
|
sessionSummary: this.currentSession.memorySummary || '',
|
|
3243
3275
|
});
|
|
3244
|
-
console.log(
|
|
3276
|
+
console.log(chalk.green('Session compacted into memory summary and project brain.'));
|
|
3245
3277
|
}
|
|
3246
3278
|
ensureAgentSystemPrompt() {
|
|
3247
3279
|
const hasSystemPrompt = this.messages.some((message) => message.role === 'system' && message.content.includes('Vigthoria CLI agent operating contract'));
|
|
@@ -3254,7 +3286,7 @@ class ChatCommand {
|
|
|
3254
3286
|
});
|
|
3255
3287
|
}
|
|
3256
3288
|
buildAgentSystemPrompt() {
|
|
3257
|
-
const toolCatalog =
|
|
3289
|
+
const toolCatalog = AgenticTools.getToolDefinitions()
|
|
3258
3290
|
.map((tool) => {
|
|
3259
3291
|
const params = tool.parameters
|
|
3260
3292
|
.map((param) => `${param.name}${param.required ? ' (required)' : ''}`)
|
|
@@ -3907,15 +3939,15 @@ class ChatCommand {
|
|
|
3907
3939
|
const verbose = !this.jsonOutput;
|
|
3908
3940
|
for (const call of toolCalls) {
|
|
3909
3941
|
if (verbose) {
|
|
3910
|
-
console.log(
|
|
3942
|
+
console.log(chalk.cyan(`⚙ Executing: ${call.tool}`));
|
|
3911
3943
|
}
|
|
3912
|
-
|
|
3944
|
+
getBridgeClient()?.emitToolCall({ tool: call.tool, args: call.args });
|
|
3913
3945
|
let result = await this.tools.execute(call);
|
|
3914
3946
|
// Phase 2: If a search tool failed (search_failed), retry with alternate approach
|
|
3915
3947
|
const searchStatus = result.metadata?.searchStatus;
|
|
3916
3948
|
if (call.tool === 'grep' && searchStatus === 'search_failed') {
|
|
3917
3949
|
if (verbose) {
|
|
3918
|
-
console.log(
|
|
3950
|
+
console.log(chalk.yellow(`⚠ Search backend failed, retrying with alternate method...`));
|
|
3919
3951
|
}
|
|
3920
3952
|
// Force Node-native fallback by re-executing with a note
|
|
3921
3953
|
const fallbackResult = await this.tools.execute({
|
|
@@ -3928,10 +3960,10 @@ class ChatCommand {
|
|
|
3928
3960
|
}
|
|
3929
3961
|
const summary = this.formatToolResult(call, result);
|
|
3930
3962
|
if (verbose) {
|
|
3931
|
-
console.log(result.success ?
|
|
3963
|
+
console.log(result.success ? chalk.gray(summary) : chalk.red(summary));
|
|
3932
3964
|
}
|
|
3933
3965
|
this.messages.push({ role: 'system', content: summary });
|
|
3934
|
-
|
|
3966
|
+
getBridgeClient()?.emitToolResult({ tool: call.tool, success: result.success, preview: (result.output || result.error || '').slice(0, 300) });
|
|
3935
3967
|
// Phase 5: Track tool evidence for quality gates
|
|
3936
3968
|
const finalStatus = result.metadata?.searchStatus;
|
|
3937
3969
|
if (finalStatus === 'search_failed') {
|
|
@@ -3996,7 +4028,7 @@ class ChatCommand {
|
|
|
3996
4028
|
this.messages = [...this.currentSession.messages];
|
|
3997
4029
|
this.sessionManager.save(this.currentSession);
|
|
3998
4030
|
if (!this.projectMemory) {
|
|
3999
|
-
this.projectMemory = new
|
|
4031
|
+
this.projectMemory = new ProjectMemoryService(this.currentProjectPath);
|
|
4000
4032
|
}
|
|
4001
4033
|
this.projectMemory.rememberConversation(this.messages, {
|
|
4002
4034
|
source: 'cli-session',
|
|
@@ -4015,7 +4047,7 @@ class ChatCommand {
|
|
|
4015
4047
|
});
|
|
4016
4048
|
console.log(action);
|
|
4017
4049
|
const answer = await new Promise((resolve) => {
|
|
4018
|
-
rl.question(
|
|
4050
|
+
rl.question(chalk.yellow('Approve? [y]es / [n]o / [a]ll this turn / [p]ersist: '), resolve);
|
|
4019
4051
|
});
|
|
4020
4052
|
rl.close();
|
|
4021
4053
|
const normalized = answer.trim().toLowerCase();
|
|
@@ -4038,4 +4070,3 @@ class ChatCommand {
|
|
|
4038
4070
|
return [...this.messages];
|
|
4039
4071
|
}
|
|
4040
4072
|
}
|
|
4041
|
-
exports.ChatCommand = ChatCommand;
|