vigthoria-cli 1.11.7 → 1.11.9
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 +13 -4
- package/dist/commands/chat.d.ts +7 -0
- package/dist/commands/chat.js +94 -36
- package/dist/index.js +23 -11
- package/dist/utils/agentRunOutcome.d.ts +3 -1
- package/dist/utils/agentRunOutcome.js +50 -2
- package/dist/utils/api.js +10 -4
- package/dist/utils/logger.js +6 -1
- package/package.json +1 -1
|
@@ -6,6 +6,15 @@ import inquirer from 'inquirer';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
+
function terminalColumns() {
|
|
10
|
+
const raw = Number(process.stdout.columns || 80);
|
|
11
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 80;
|
|
12
|
+
}
|
|
13
|
+
function fitLine(text, reserve = 0) {
|
|
14
|
+
const width = Math.max(24, terminalColumns() - reserve);
|
|
15
|
+
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
16
|
+
return clean.length > width ? `${clean.slice(0, Math.max(1, width - 1))}…` : clean;
|
|
17
|
+
}
|
|
9
18
|
function validateExistingDirectory(input) {
|
|
10
19
|
const trimmed = String(input || '').trim().replace(/^['"]|['"]$/g, '');
|
|
11
20
|
if (!trimmed) {
|
|
@@ -84,8 +93,8 @@ export async function runAgentSessionMenu(defaults = {}) {
|
|
|
84
93
|
};
|
|
85
94
|
console.log();
|
|
86
95
|
console.log(chalk.cyan('═══ Agent Session Setup ═══'));
|
|
87
|
-
console.log(chalk.gray(`Workspace: ${session.workspacePath}`));
|
|
88
|
-
console.log(chalk.gray(`Debug: ${session.debugMode ? 'on' : 'off'} | Auto-approve: ${session.autoApprove ? 'on' : 'off'} | Auto-save: ${session.autoSave ? 'on' : 'off'}`));
|
|
96
|
+
console.log(chalk.gray(fitLine(`Workspace: ${session.workspacePath}`)));
|
|
97
|
+
console.log(chalk.gray(fitLine(`Debug: ${session.debugMode ? 'on' : 'off'} | Auto-approve: ${session.autoApprove ? 'on' : 'off'} | Auto-save: ${session.autoSave ? 'on' : 'off'}`)));
|
|
89
98
|
console.log();
|
|
90
99
|
const { action } = await inquirer.prompt([
|
|
91
100
|
{
|
|
@@ -153,8 +162,8 @@ export async function runAgentSessionMenu(defaults = {}) {
|
|
|
153
162
|
}
|
|
154
163
|
console.log();
|
|
155
164
|
console.log(chalk.cyan('── Session ready ──'));
|
|
156
|
-
console.log(chalk.gray(`Workspace: ${session.workspacePath}`));
|
|
157
|
-
console.log(chalk.gray(`Debug: ${session.debugMode ? 'on' : 'off'} | Auto-approve: ${session.autoApprove ? 'on' : 'off'} | Auto-save: ${session.autoSave ? 'on' : 'off'}`));
|
|
165
|
+
console.log(chalk.gray(fitLine(`Workspace: ${session.workspacePath}`)));
|
|
166
|
+
console.log(chalk.gray(fitLine(`Debug: ${session.debugMode ? 'on' : 'off'} | Auto-approve: ${session.autoApprove ? 'on' : 'off'} | Auto-save: ${session.autoSave ? 'on' : 'off'}`)));
|
|
158
167
|
console.log();
|
|
159
168
|
return session;
|
|
160
169
|
}
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export declare class ChatCommand {
|
|
|
48
48
|
private retryPromptStreak;
|
|
49
49
|
private v3SuppressThinkingStream;
|
|
50
50
|
private v3StreamedAnswerBuffer;
|
|
51
|
+
private v3SeenToolCalls;
|
|
52
|
+
private v3SeenToolResults;
|
|
51
53
|
private lastAgentRunOutcome;
|
|
52
54
|
private isJwtExpirationError;
|
|
53
55
|
private isNetworkError;
|
|
@@ -64,6 +66,7 @@ export declare class ChatCommand {
|
|
|
64
66
|
private resolveInitialModel;
|
|
65
67
|
private isLegacyAgentFallbackAllowed;
|
|
66
68
|
private shouldRescueFailedAnalysis;
|
|
69
|
+
private isDeepAnalysisPrompt;
|
|
67
70
|
private tryAnalysisLocalRescue;
|
|
68
71
|
private resolveAgentExecutionPolicy;
|
|
69
72
|
private getMessagesForModel;
|
|
@@ -113,6 +116,10 @@ export declare class ChatCommand {
|
|
|
113
116
|
private sanitizeServerPath;
|
|
114
117
|
private stripHiddenThoughtBlocks;
|
|
115
118
|
private describeV3AgentTool;
|
|
119
|
+
private terminalColumns;
|
|
120
|
+
private fitTerminalText;
|
|
121
|
+
private normalizeToolOutputForDisplay;
|
|
122
|
+
private toolEventKey;
|
|
116
123
|
private isRawV3StreamPayload;
|
|
117
124
|
private consumeV3StreamPayload;
|
|
118
125
|
private writeV3StreamText;
|
package/dist/commands/chat.js
CHANGED
|
@@ -67,6 +67,8 @@ export class ChatCommand {
|
|
|
67
67
|
retryPromptStreak = 0;
|
|
68
68
|
v3SuppressThinkingStream = false;
|
|
69
69
|
v3StreamedAnswerBuffer = '';
|
|
70
|
+
v3SeenToolCalls = new Set();
|
|
71
|
+
v3SeenToolResults = new Set();
|
|
70
72
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
71
73
|
lastAgentRunOutcome = null;
|
|
72
74
|
isJwtExpirationError(error) {
|
|
@@ -262,6 +264,10 @@ export class ChatCommand {
|
|
|
262
264
|
shouldRescueFailedAnalysis() {
|
|
263
265
|
return process.env.VIGTHORIA_DISABLE_ANALYSIS_RESCUE !== '1';
|
|
264
266
|
}
|
|
267
|
+
isDeepAnalysisPrompt(prompt) {
|
|
268
|
+
return /\b(deep|deeper|full|complete|thorough|systematic|architecture|architectural)\s+(analyse|analyze|analysis|review|dive|scan)\b/i.test(prompt)
|
|
269
|
+
|| /\b(analyse|analyze|analysis|review)\b[\s\S]{0,40}\b(deep|deeper|full|complete|thorough|systematic|architecture|architectural)\b/i.test(prompt);
|
|
270
|
+
}
|
|
265
271
|
async tryAnalysisLocalRescue(prompt, workspacePath) {
|
|
266
272
|
if (!this.shouldRescueFailedAnalysis() || !this.tools) {
|
|
267
273
|
return false;
|
|
@@ -702,6 +708,33 @@ export class ChatCommand {
|
|
|
702
708
|
}
|
|
703
709
|
return 'Working';
|
|
704
710
|
}
|
|
711
|
+
terminalColumns() {
|
|
712
|
+
const raw = Number(process.stdout.columns || process.stderr.columns || 80);
|
|
713
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 80;
|
|
714
|
+
}
|
|
715
|
+
fitTerminalText(text, reserve = 0) {
|
|
716
|
+
const width = Math.max(24, this.terminalColumns() - reserve);
|
|
717
|
+
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
718
|
+
const visible = clean.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
|
|
719
|
+
if (visible.length <= width)
|
|
720
|
+
return clean;
|
|
721
|
+
return `${visible.slice(0, Math.max(1, width - 1))}…`;
|
|
722
|
+
}
|
|
723
|
+
normalizeToolOutputForDisplay(output) {
|
|
724
|
+
return String(output || '')
|
|
725
|
+
.replace(/\/r\/n|\/n|\\n/g, '\n')
|
|
726
|
+
.replace(/\/r|\\r/g, '')
|
|
727
|
+
.replace(/\t/g, ' ');
|
|
728
|
+
}
|
|
729
|
+
toolEventKey(event) {
|
|
730
|
+
const name = String(event?.name || event?.tool || event?.tool_name || '').trim();
|
|
731
|
+
const args = event?.arguments && typeof event.arguments === 'object' ? event.arguments : {};
|
|
732
|
+
const target = String(args.path || args.file_path || args.pattern || args.command || event?.target || '').trim();
|
|
733
|
+
if (target)
|
|
734
|
+
return `${name}:${target}`;
|
|
735
|
+
const output = typeof event?.output === 'string' ? event.output.replace(/\s+/g, ' ').slice(0, 160) : '';
|
|
736
|
+
return `${name}:${output}`;
|
|
737
|
+
}
|
|
705
738
|
isRawV3StreamPayload(event) {
|
|
706
739
|
if (!event) {
|
|
707
740
|
return false;
|
|
@@ -784,6 +817,9 @@ export class ChatCommand {
|
|
|
784
817
|
.replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
|
|
785
818
|
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
|
|
786
819
|
.replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
|
|
820
|
+
.replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
|
|
821
|
+
.replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
|
|
822
|
+
.replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
|
|
787
823
|
.replace(/\n{3,}/g, '\n\n');
|
|
788
824
|
return output;
|
|
789
825
|
}
|
|
@@ -807,15 +843,20 @@ export class ChatCommand {
|
|
|
807
843
|
}
|
|
808
844
|
this.v3LastActivity = Date.now();
|
|
809
845
|
if (event.type === 'tool_call') {
|
|
846
|
+
const eventKey = this.toolEventKey(event);
|
|
847
|
+
if (this.v3SeenToolCalls.has(eventKey)) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this.v3SeenToolCalls.add(eventKey);
|
|
810
851
|
this.v3ToolCallCount += 1;
|
|
811
852
|
const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
812
853
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
813
854
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
814
|
-
const shortTarget = sanitizedTarget ? `
|
|
855
|
+
const shortTarget = sanitizedTarget ? ` -> ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
815
856
|
const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
816
857
|
if (spinner.isSpinning)
|
|
817
858
|
spinner.stop();
|
|
818
|
-
this.writeAgentActivityLine(stepLabel + '\n');
|
|
859
|
+
this.writeAgentActivityLine(this.fitTerminalText(stepLabel) + '\n');
|
|
819
860
|
// Show extra detail for key tools
|
|
820
861
|
const args = event.arguments || {};
|
|
821
862
|
const toolName = event.name || event.tool || '';
|
|
@@ -825,16 +866,21 @@ export class ChatCommand {
|
|
|
825
866
|
}
|
|
826
867
|
else if (toolName === 'bash' && typeof args.command === 'string') {
|
|
827
868
|
const command = this.sanitizeServerPath(args.command);
|
|
828
|
-
this.writeAgentActivityLine(chalk.gray(` $
|
|
869
|
+
this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`$ ${command}`, 6)}\n`));
|
|
829
870
|
}
|
|
830
871
|
else if (toolName === 'read_file') {
|
|
831
|
-
this.writeAgentActivityLine(chalk.gray(` reading ${sanitizedTarget || 'file'}\n`));
|
|
872
|
+
this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`reading ${sanitizedTarget || 'file'}`, 6)}\n`));
|
|
832
873
|
}
|
|
833
|
-
spinner.start();
|
|
834
874
|
spinner.text = `Running ${toolDesc}...`;
|
|
835
875
|
return;
|
|
836
876
|
}
|
|
837
877
|
if (event.type === 'tool_result') {
|
|
878
|
+
const eventKey = this.toolEventKey(event);
|
|
879
|
+
if (eventKey && this.v3SeenToolResults.has(eventKey)) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (eventKey)
|
|
883
|
+
this.v3SeenToolResults.add(eventKey);
|
|
838
884
|
const success = event.success !== false;
|
|
839
885
|
const toolName = event.name || event.tool || '';
|
|
840
886
|
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
@@ -843,27 +889,30 @@ export class ChatCommand {
|
|
|
843
889
|
this.writeAgentActivityLine(`${indicator} ${toolName}\n`);
|
|
844
890
|
// Show output for failures, or brief summary for successes — sanitize server paths
|
|
845
891
|
const rawOutput = typeof event.output === 'string' ? event.output.trim() : '';
|
|
846
|
-
const output = this.sanitizeServerPath(rawOutput);
|
|
892
|
+
const output = this.normalizeToolOutputForDisplay(this.sanitizeServerPath(rawOutput));
|
|
847
893
|
if (!success && output) {
|
|
848
894
|
const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
|
|
849
895
|
const lines = sanitizedError.split('\n').slice(0, 4);
|
|
850
|
-
this.writeAgentActivityLine(chalk.red(` ${
|
|
896
|
+
this.writeAgentActivityLine(chalk.red(`${lines.map((line) => ` ${this.fitTerminalText(line, 6)}`).join('\n')}\n`));
|
|
851
897
|
}
|
|
852
898
|
else if (success && output && output.length > 0) {
|
|
853
899
|
if (toolName === 'read_file' || toolName === 'grep' || toolName === 'search_files' || toolName === 'list_directory') {
|
|
854
900
|
const lineCount = output.split('\n').length;
|
|
855
|
-
const preview = output.split('\n')
|
|
901
|
+
const preview = output.split('\n')
|
|
902
|
+
.map((line) => line.trim())
|
|
903
|
+
.filter((line) => line && !line.startsWith('.vigthoria/memory/brain.json:'))
|
|
904
|
+
.slice(0, 3)
|
|
905
|
+
.join('\n');
|
|
856
906
|
this.writeAgentActivityLine(chalk.gray(` ${lineCount} line${lineCount === 1 ? '' : 's'} returned\n`));
|
|
857
907
|
if (preview && preview.length > 20 && !/^\(no matches\)$/i.test(preview.trim())) {
|
|
858
|
-
this.writeAgentActivityLine(chalk.gray(`${preview.split('\n').map((
|
|
908
|
+
this.writeAgentActivityLine(chalk.gray(`${preview.split('\n').map((line) => ` ${this.fitTerminalText(line, 6)}`).join('\n')}\n`));
|
|
859
909
|
}
|
|
860
910
|
}
|
|
861
911
|
else {
|
|
862
|
-
const brief = output.split('\n')[0]
|
|
863
|
-
this.writeAgentActivityLine(chalk.gray(` ${brief
|
|
912
|
+
const brief = output.split('\n')[0];
|
|
913
|
+
this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(brief, 6)}\n`));
|
|
864
914
|
}
|
|
865
915
|
}
|
|
866
|
-
spinner.start();
|
|
867
916
|
spinner.text = 'Next step...';
|
|
868
917
|
return;
|
|
869
918
|
}
|
|
@@ -879,8 +928,7 @@ export class ChatCommand {
|
|
|
879
928
|
const display = iterText && !/read-only analysis turn|run at least one discovery tool/i.test(iterText)
|
|
880
929
|
? iterText
|
|
881
930
|
: `Step ${this.v3IterationCount}: inspecting workspace…`;
|
|
882
|
-
this.writeAgentActivityLine(chalk.cyan(`\n── ${display} ──\n`));
|
|
883
|
-
spinner.start();
|
|
931
|
+
this.writeAgentActivityLine(chalk.cyan(`\n── ${this.fitTerminalText(display, 6)} ──\n`));
|
|
884
932
|
spinner.text = 'Analyzing...';
|
|
885
933
|
return;
|
|
886
934
|
}
|
|
@@ -913,13 +961,13 @@ export class ChatCommand {
|
|
|
913
961
|
if (fail > 0)
|
|
914
962
|
statLine += chalk.yellow(`, ${fail} failed`);
|
|
915
963
|
}
|
|
916
|
-
process.
|
|
964
|
+
process.stdout.write(chalk.green(`\n✓ Complete`) + ` - ${this.fitTerminalText(statLine, 14)}\n`);
|
|
917
965
|
// Show seal quality score if available
|
|
918
966
|
if (event.seal_score && typeof event.seal_score.overall === 'number') {
|
|
919
967
|
const score = event.seal_score.overall;
|
|
920
968
|
const tier = event.seal_score.tier || '';
|
|
921
969
|
const scoreColor = score >= 7 ? chalk.green : score >= 5 ? chalk.yellow : chalk.red;
|
|
922
|
-
process.
|
|
970
|
+
process.stdout.write(chalk.cyan(' [Quality] ') + scoreColor(`${score}/10`) + (tier ? chalk.gray(` (${tier})`) : '') + '\n');
|
|
923
971
|
}
|
|
924
972
|
return;
|
|
925
973
|
}
|
|
@@ -936,35 +984,31 @@ export class ChatCommand {
|
|
|
936
984
|
}
|
|
937
985
|
if (spinner.isSpinning)
|
|
938
986
|
spinner.stop();
|
|
939
|
-
process.
|
|
940
|
-
if (quality)
|
|
941
|
-
process.stderr.write(chalk.gray(` (${quality})`));
|
|
942
|
-
process.stderr.write('\n');
|
|
987
|
+
process.stdout.write(chalk.cyan(` [Plan] `) + this.fitTerminalText(`Task: ${planKind || 'analyzing'}${quality ? ` (${quality})` : ''}`, 9) + '\n');
|
|
943
988
|
if (summary) {
|
|
944
|
-
process.
|
|
989
|
+
process.stdout.write(chalk.gray(` ${this.fitTerminalText(summary, 6)}\n`));
|
|
945
990
|
}
|
|
946
991
|
if (status === 'planning' && Number.isFinite(Number(plan.elapsed_seconds))) {
|
|
947
|
-
process.
|
|
992
|
+
process.stdout.write(chalk.gray(` elapsed: ${plan.elapsed_seconds}s\n`));
|
|
948
993
|
}
|
|
949
994
|
if (Array.isArray(plan.tasks) && plan.tasks.length > 0) {
|
|
950
|
-
process.
|
|
995
|
+
process.stdout.write(chalk.gray(` ${plan.total_tasks || plan.tasks.length} tasks:\n`));
|
|
951
996
|
for (const t of plan.tasks.slice(0, 10)) {
|
|
952
997
|
const targets = Array.isArray(t.targets) && t.targets.length ? ` → ${t.targets.map((target) => this.sanitizeServerPath(String(target))).join(', ')}` : '';
|
|
953
|
-
process.
|
|
998
|
+
process.stdout.write(chalk.gray(` - ${this.fitTerminalText(`${this.sanitizeServerPath(String(t.title || t.id))}${targets}`, 10)}\n`));
|
|
954
999
|
}
|
|
955
1000
|
if (plan.tasks.length > 10) {
|
|
956
|
-
process.
|
|
1001
|
+
process.stdout.write(chalk.gray(` ... and ${plan.tasks.length - 10} more\n`));
|
|
957
1002
|
}
|
|
958
1003
|
}
|
|
959
1004
|
if (Array.isArray(plan.notes) && plan.notes.length > 0) {
|
|
960
1005
|
for (const note of plan.notes.slice(0, 3)) {
|
|
961
|
-
process.
|
|
1006
|
+
process.stdout.write(chalk.gray(` note: ${this.fitTerminalText(this.sanitizeServerPath(String(note)), 12)}\n`));
|
|
962
1007
|
}
|
|
963
1008
|
}
|
|
964
1009
|
if (Array.isArray(plan.target_files) && plan.target_files.length > 0) {
|
|
965
|
-
process.
|
|
1010
|
+
process.stdout.write(chalk.gray(` ${this.fitTerminalText(`Files: ${plan.target_files.map((filePath) => this.sanitizeServerPath(String(filePath))).join(', ')}`, 6)}\n`));
|
|
966
1011
|
}
|
|
967
|
-
spinner.start();
|
|
968
1012
|
spinner.text = status === 'planning' ? 'Planning...' : 'Executing plan...';
|
|
969
1013
|
return;
|
|
970
1014
|
}
|
|
@@ -1187,7 +1231,7 @@ export class ChatCommand {
|
|
|
1187
1231
|
if (sessionConfig.debugMode) {
|
|
1188
1232
|
process.env.DEBUG = process.env.DEBUG || 'vigthoria:*';
|
|
1189
1233
|
}
|
|
1190
|
-
console.log(chalk.green(`Using workspace: ${this.currentProjectPath}`));
|
|
1234
|
+
console.log(chalk.green(this.fitTerminalText(`Using workspace: ${this.currentProjectPath}`)));
|
|
1191
1235
|
}
|
|
1192
1236
|
if (this.jsonOutput && !options.prompt && !options.retry && !options.continue) {
|
|
1193
1237
|
throw new Error('--json is only supported together with --prompt, --retry, or --continue.');
|
|
@@ -1988,6 +2032,7 @@ export class ChatCommand {
|
|
|
1988
2032
|
buildLocalLoopLiveOutcome(prompt) {
|
|
1989
2033
|
const liveOutcome = createLiveOutcome();
|
|
1990
2034
|
liveOutcome.requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
2035
|
+
liveOutcome.deepAnalysisRequired = this.isDeepAnalysisPrompt(prompt);
|
|
1991
2036
|
liveOutcome.analysisToolsUsed = this.agentToolEvidence.discovery;
|
|
1992
2037
|
liveOutcome.changedFileCount = this.agentToolEvidence.mutation;
|
|
1993
2038
|
liveOutcome.workspaceHasOutput = this.agentToolEvidence.mutation > 0;
|
|
@@ -2500,11 +2545,14 @@ export class ChatCommand {
|
|
|
2500
2545
|
this.v3LastActivity = Date.now();
|
|
2501
2546
|
this.v3StreamingStarted = false;
|
|
2502
2547
|
this.v3StreamedAnswerBuffer = '';
|
|
2503
|
-
|
|
2548
|
+
this.v3SeenToolCalls.clear();
|
|
2549
|
+
this.v3SeenToolResults.clear();
|
|
2550
|
+
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], false);
|
|
2504
2551
|
taskDisplay.start(0);
|
|
2505
2552
|
const spinner = this.jsonOutput ? null : createSpinner({
|
|
2506
2553
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
2507
2554
|
spinner: 'clock',
|
|
2555
|
+
isSilent: true,
|
|
2508
2556
|
}).start();
|
|
2509
2557
|
// Live run telemetry, used both for the final summary block and for /retry, /continue.
|
|
2510
2558
|
const liveOutcome = {
|
|
@@ -2514,6 +2562,7 @@ export class ChatCommand {
|
|
|
2514
2562
|
qualityMissing: [],
|
|
2515
2563
|
qualityBlockers: [],
|
|
2516
2564
|
};
|
|
2565
|
+
liveOutcome.deepAnalysisRequired = this.isDeepAnalysisPrompt(prompt);
|
|
2517
2566
|
const parsePlannerSummary = (raw) => {
|
|
2518
2567
|
if (!raw || typeof raw !== 'string')
|
|
2519
2568
|
return;
|
|
@@ -2648,6 +2697,12 @@ export class ChatCommand {
|
|
|
2648
2697
|
if (Number(event.discovery_tools_used) > 0) {
|
|
2649
2698
|
liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
|
|
2650
2699
|
}
|
|
2700
|
+
if (Number(event.discovery_read_files) > 0) {
|
|
2701
|
+
liveOutcome.analysisReadToolsUsed = Math.max(liveOutcome.analysisReadToolsUsed, Number(event.discovery_read_files));
|
|
2702
|
+
}
|
|
2703
|
+
if (event.analysis_deep_required === true) {
|
|
2704
|
+
liveOutcome.deepAnalysisRequired = true;
|
|
2705
|
+
}
|
|
2651
2706
|
taskDisplay.complete(1);
|
|
2652
2707
|
}
|
|
2653
2708
|
else if (event.type === 'executor_error') {
|
|
@@ -2759,6 +2814,9 @@ export class ChatCommand {
|
|
|
2759
2814
|
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2760
2815
|
let executorSucceeded = runEvaluation.executorSucceeded;
|
|
2761
2816
|
let finalAnswer = answerContent;
|
|
2817
|
+
if (isToolEvidenceStubAnswer(finalAnswer)) {
|
|
2818
|
+
finalAnswer = '';
|
|
2819
|
+
}
|
|
2762
2820
|
const needsAnalysisRescue = !requiresWorkspaceChanges && !this.jsonOutput
|
|
2763
2821
|
&& (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
|
|
2764
2822
|
if (needsAnalysisRescue) {
|
|
@@ -2876,7 +2934,7 @@ export class ChatCommand {
|
|
|
2876
2934
|
qualityMissing: liveOutcome.qualityMissing,
|
|
2877
2935
|
qualityBlockers: liveOutcome.qualityBlockers,
|
|
2878
2936
|
hasOutput: workspaceHasOutput,
|
|
2879
|
-
answerContent:
|
|
2937
|
+
answerContent: finalAnswer || null,
|
|
2880
2938
|
selfHealStatus,
|
|
2881
2939
|
selfHealTool,
|
|
2882
2940
|
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
@@ -2904,7 +2962,7 @@ export class ChatCommand {
|
|
|
2904
2962
|
taskId: response.taskId || null,
|
|
2905
2963
|
contextId: response.contextId || null,
|
|
2906
2964
|
partial: response.partial === true || (!executorSucceeded && liveOutcome.tasksSucceeded > 0),
|
|
2907
|
-
content:
|
|
2965
|
+
content: finalAnswer || runEvaluation.statusHeadline,
|
|
2908
2966
|
statusHeadline: runEvaluation.statusHeadline,
|
|
2909
2967
|
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2910
2968
|
tasksTotal: liveOutcome.tasksTotal,
|
|
@@ -2920,7 +2978,7 @@ export class ChatCommand {
|
|
|
2920
2978
|
},
|
|
2921
2979
|
}, null, 2));
|
|
2922
2980
|
}
|
|
2923
|
-
this.messages.push({ role: 'assistant', content:
|
|
2981
|
+
this.messages.push({ role: 'assistant', content: finalAnswer || runEvaluation.statusHeadline });
|
|
2924
2982
|
watcher?.stop();
|
|
2925
2983
|
return true;
|
|
2926
2984
|
}
|
|
@@ -3087,10 +3145,10 @@ export class ChatCommand {
|
|
|
3087
3145
|
? 'Interactive Agent Chat'
|
|
3088
3146
|
: 'Interactive Chat';
|
|
3089
3147
|
this.logger.section(this.workflowTarget ? `${chatTitle} Via Workflow Target` : chatTitle);
|
|
3090
|
-
console.log(chalk.gray('Type /help for commands. Type /exit to quit.'));
|
|
3091
|
-
console.log(chalk.gray('Multi-line: end a line with \\ or start a block with {{{ and end with }}}'));
|
|
3148
|
+
console.log(chalk.gray(this.fitTerminalText('Type /help for commands. Type /exit to quit.')));
|
|
3149
|
+
console.log(chalk.gray(this.fitTerminalText('Multi-line: end a line with \\ or start a block with {{{ and end with }}}')));
|
|
3092
3150
|
if (this.workflowTarget) {
|
|
3093
|
-
console.log(chalk.gray(`Workflow target: ${this.workflowTarget}`));
|
|
3151
|
+
console.log(chalk.gray(this.fitTerminalText(`Workflow target: ${this.workflowTarget}`)));
|
|
3094
3152
|
}
|
|
3095
3153
|
const rl = readline.createInterface({
|
|
3096
3154
|
input: process.stdin,
|
package/dist/index.js
CHANGED
|
@@ -567,22 +567,34 @@ export async function main(args) {
|
|
|
567
567
|
return;
|
|
568
568
|
}
|
|
569
569
|
}
|
|
570
|
-
// Banner - Fixed alignment with proper padding
|
|
571
|
-
const boxWidth = 61; // Inner content width
|
|
572
|
-
const titleText = 'VIGTHORIA CLI - AI-Powered Coding Assistant';
|
|
573
|
-
const versionText = `Version ${VERSION}`;
|
|
574
|
-
// Calculate padding for centering
|
|
575
|
-
const titlePad = Math.floor((boxWidth - 4 - titleText.length) / 2);
|
|
576
|
-
const versionPad = Math.floor((boxWidth - 4 - versionText.length) / 2);
|
|
577
570
|
if (process.env.VIGTHORIA_NO_BANNER !== '1' &&
|
|
578
571
|
!jsonOutputRequested &&
|
|
579
572
|
!directPromptRequested &&
|
|
580
573
|
!helpOrVersionRequested) {
|
|
574
|
+
const terminalWidth = Math.max(36, Number(process.stdout.columns || 80));
|
|
575
|
+
const boxWidth = Math.max(34, Math.min(61, terminalWidth - 2));
|
|
576
|
+
const innerWidth = boxWidth;
|
|
577
|
+
const fit = (text) => {
|
|
578
|
+
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
579
|
+
return clean.length > innerWidth ? `${clean.slice(0, Math.max(1, innerWidth - 1))}…` : clean;
|
|
580
|
+
};
|
|
581
|
+
const center = (text, colour = (value) => value) => {
|
|
582
|
+
const fitted = fit(text);
|
|
583
|
+
const left = Math.max(0, Math.floor((innerWidth - fitted.length) / 2));
|
|
584
|
+
const right = Math.max(0, innerWidth - fitted.length - left);
|
|
585
|
+
return chalk.cyan(CH.dV) + ' '.repeat(left) + colour(fitted) + ' '.repeat(right) + chalk.cyan(CH.dV);
|
|
586
|
+
};
|
|
587
|
+
const titleLine = boxWidth < 48 ? 'VIGTHORIA CLI' : 'VIGTHORIA CLI - AI Coding Assistant';
|
|
588
|
+
const subtitleLine = boxWidth < 48 ? 'AI Coding Assistant' : `Version ${VERSION}`;
|
|
589
|
+
const versionLine = boxWidth < 48 ? `Version ${VERSION}` : '';
|
|
581
590
|
console.log(chalk.cyan(CH.dTl + CH.dH.repeat(boxWidth) + CH.dTr));
|
|
582
|
-
console.log(
|
|
583
|
-
console.log(
|
|
584
|
-
console.log(
|
|
585
|
-
|
|
591
|
+
console.log(center(''));
|
|
592
|
+
console.log(center(titleLine, (value) => chalk.bold.white(value)));
|
|
593
|
+
console.log(center(subtitleLine, (value) => chalk.gray(value)));
|
|
594
|
+
if (versionLine) {
|
|
595
|
+
console.log(center(versionLine, (value) => chalk.gray(value)));
|
|
596
|
+
}
|
|
597
|
+
console.log(center(''));
|
|
586
598
|
console.log(chalk.cyan(CH.dBl + CH.dH.repeat(boxWidth) + CH.dBr));
|
|
587
599
|
console.log();
|
|
588
600
|
}
|
|
@@ -18,6 +18,8 @@ export interface LiveOutcome {
|
|
|
18
18
|
analysisReadToolsUsed: number;
|
|
19
19
|
/** Final user-facing answer text (stream + response body) */
|
|
20
20
|
answerContent?: string;
|
|
21
|
+
/** User asked for deep/project-scale analysis, so shallow evidence must not pass. */
|
|
22
|
+
deepAnalysisRequired?: boolean;
|
|
21
23
|
streamAborted?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface TaskSummaryEvent {
|
|
@@ -31,7 +33,7 @@ export interface RunEvaluation {
|
|
|
31
33
|
uiTheme: 'success' | 'warning' | 'error';
|
|
32
34
|
}
|
|
33
35
|
export declare function createLiveOutcome(): LiveOutcome;
|
|
34
|
-
/** Tool-trace stubs like `[read_file] "index.html"` — not a user-facing answer. */
|
|
36
|
+
/** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
|
|
35
37
|
export declare function isToolEvidenceStubAnswer(text: string): boolean;
|
|
36
38
|
/** True when text looks like a real answer, not an executor/system placeholder. */
|
|
37
39
|
export declare function isSubstantiveAgentAnswer(text: string): boolean;
|
|
@@ -17,10 +17,12 @@ export function createLiveOutcome() {
|
|
|
17
17
|
analysisToolsUsed: 0,
|
|
18
18
|
analysisReadToolsUsed: 0,
|
|
19
19
|
answerContent: '',
|
|
20
|
+
deepAnalysisRequired: false,
|
|
20
21
|
streamAborted: false,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
const EXECUTOR_PLACEHOLDER_RE = /read-only analysis turn|run at least one discovery tool|reviewing attached files and workspace findings/i;
|
|
25
|
+
const BOOTSTRAP_PLACEHOLDER_RE = /bootstrap discovery completed|inspect the most relevant files next|reviewing attached files and workspace findings/i;
|
|
24
26
|
const LIST_ONLY_DISCOVERY_TOOLS = new Set([
|
|
25
27
|
'list_directory',
|
|
26
28
|
'list_dir',
|
|
@@ -28,19 +30,53 @@ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
|
|
|
28
30
|
'dir',
|
|
29
31
|
]);
|
|
30
32
|
const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
|
|
31
|
-
|
|
33
|
+
function looksLikeRawV3EventPayload(text) {
|
|
34
|
+
const trimmed = String(text || '').trim();
|
|
35
|
+
if (!/^[{[]/.test(trimmed))
|
|
36
|
+
return false;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(trimmed);
|
|
39
|
+
const root = Array.isArray(parsed) ? { events: parsed } : parsed;
|
|
40
|
+
if (!root || typeof root !== 'object')
|
|
41
|
+
return false;
|
|
42
|
+
if (Array.isArray(root.events) && root.events.some((event) => event && typeof event === 'object' && typeof event.type === 'string')) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return Boolean(root.context_id
|
|
46
|
+
|| root.mcp_context_id
|
|
47
|
+
|| root.workspace_uri
|
|
48
|
+
|| root.execution_surface
|
|
49
|
+
|| root.task_id);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
|
|
32
56
|
export function isToolEvidenceStubAnswer(text) {
|
|
33
57
|
const trimmed = String(text || '').trim();
|
|
34
58
|
if (!trimmed)
|
|
35
59
|
return true;
|
|
60
|
+
if (looksLikeRawV3EventPayload(trimmed))
|
|
61
|
+
return true;
|
|
62
|
+
if (BOOTSTRAP_PLACEHOLDER_RE.test(trimmed))
|
|
63
|
+
return true;
|
|
36
64
|
if (GENERIC_SUMMARY_RE.test(trimmed.replace(/\.\s*\d+\s+tool steps.*$/i, '').trim()))
|
|
37
65
|
return true;
|
|
38
66
|
const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
39
67
|
if (lines.length === 0)
|
|
40
68
|
return true;
|
|
41
69
|
const stubLine = /^\[(?:read_file|grep|list_directory|search_files|glob|tool_call)\]/i;
|
|
70
|
+
const xmlToolLine = /^<\/?(?:read_file|grep|list_directory|search_files|glob|tool_call|tool_result|path)\b[^>]*>/i;
|
|
71
|
+
const xmlToolBlockOnly = trimmed
|
|
72
|
+
.replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
|
|
73
|
+
.replace(/<(?:grep|list_directory|search_files|glob|tool_call|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_call|tool_result)>/gi, '')
|
|
74
|
+
.replace(/\s+/g, '')
|
|
75
|
+
.trim();
|
|
76
|
+
if (!xmlToolBlockOnly)
|
|
77
|
+
return true;
|
|
42
78
|
const filesOnly = /^Files inspected:/i;
|
|
43
|
-
const stubLines = lines.filter((line) => stubLine.test(line) || filesOnly.test(line)).length;
|
|
79
|
+
const stubLines = lines.filter((line) => stubLine.test(line) || xmlToolLine.test(line) || filesOnly.test(line)).length;
|
|
44
80
|
if (stubLines === lines.length)
|
|
45
81
|
return true;
|
|
46
82
|
if (stubLines > 0 && stubLines / lines.length >= 0.6 && trimmed.length < 400)
|
|
@@ -101,6 +137,7 @@ const READ_DISCOVERY_TOOLS = new Set([
|
|
|
101
137
|
'grep',
|
|
102
138
|
'list_directory',
|
|
103
139
|
'glob',
|
|
140
|
+
'search_files',
|
|
104
141
|
'search_project',
|
|
105
142
|
'list_dir',
|
|
106
143
|
'dir',
|
|
@@ -171,6 +208,10 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
171
208
|
const answerText = normalizeAgentAnswerContent(liveOutcome.answerContent);
|
|
172
209
|
const hasSubstantiveAnswer = isSubstantiveAgentAnswer(answerText);
|
|
173
210
|
const hasDeepRead = liveOutcome.analysisReadToolsUsed > 0;
|
|
211
|
+
const meetsDeepAnalysisDepth = !liveOutcome.deepAnalysisRequired
|
|
212
|
+
|| (liveOutcome.analysisToolsUsed >= 4
|
|
213
|
+
&& liveOutcome.analysisReadToolsUsed >= 3
|
|
214
|
+
&& answerText.length >= 700);
|
|
174
215
|
if (hasFatalError && !hasSubstantiveAnswer) {
|
|
175
216
|
return {
|
|
176
217
|
executorSucceeded: false,
|
|
@@ -199,6 +240,13 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
199
240
|
uiTheme: 'warning',
|
|
200
241
|
};
|
|
201
242
|
}
|
|
243
|
+
if (!meetsDeepAnalysisDepth) {
|
|
244
|
+
return {
|
|
245
|
+
executorSucceeded: false,
|
|
246
|
+
statusHeadline: '⚠ Deep analysis was too shallow — retrying needs a project map, multiple file reads, and a structured report',
|
|
247
|
+
uiTheme: 'warning',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
202
250
|
return {
|
|
203
251
|
executorSucceeded: true,
|
|
204
252
|
statusHeadline: '✓ Analysis completed',
|
package/dist/utils/api.js
CHANGED
|
@@ -3358,11 +3358,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3358
3358
|
if (Array.isArray(data?.events)) {
|
|
3359
3359
|
const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string' && event.summary.trim());
|
|
3360
3360
|
if (completionEvent) {
|
|
3361
|
-
|
|
3361
|
+
const text = this.sanitizeV3AgentResponseText(completionEvent.summary);
|
|
3362
|
+
if (isSubstantiveAgentAnswer(text))
|
|
3363
|
+
return text;
|
|
3362
3364
|
}
|
|
3363
3365
|
const messageEvent = [...data.events].reverse().find((event) => event && event.type === 'message' && typeof event.content === 'string' && event.content.trim());
|
|
3364
3366
|
if (messageEvent) {
|
|
3365
|
-
|
|
3367
|
+
const text = this.sanitizeV3AgentResponseText(messageEvent.content);
|
|
3368
|
+
if (isSubstantiveAgentAnswer(text))
|
|
3369
|
+
return text;
|
|
3366
3370
|
}
|
|
3367
3371
|
// Synthesize a grounded answer from the tool-call evidence the
|
|
3368
3372
|
// agent produced, rather than dumping the raw event trace.
|
|
@@ -3376,8 +3380,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3376
3380
|
const fileList = Object.keys(data.files).join(', ');
|
|
3377
3381
|
return `Agent wrote workspace files: ${sanitizeUserFacingPathText(fileList)}`;
|
|
3378
3382
|
}
|
|
3379
|
-
|
|
3380
|
-
return text.length > 12000 ? `${text.slice(0, 12000)}\n\n[V3 agent output truncated]` : text;
|
|
3383
|
+
return '';
|
|
3381
3384
|
}
|
|
3382
3385
|
sanitizeV3AgentResponseText(input) {
|
|
3383
3386
|
let text = sanitizeUserFacingPathText(String(input || ''));
|
|
@@ -3387,6 +3390,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3387
3390
|
.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
|
|
3388
3391
|
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
|
|
3389
3392
|
.replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
|
|
3393
|
+
.replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
|
|
3394
|
+
.replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
|
|
3395
|
+
.replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
|
|
3390
3396
|
.replace(/```json\s*\[\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*\]\s*```/gi, '')
|
|
3391
3397
|
.replace(/```json\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*```/gi, '')
|
|
3392
3398
|
.replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
|
package/dist/utils/logger.js
CHANGED
|
@@ -162,8 +162,13 @@ export class Logger {
|
|
|
162
162
|
}
|
|
163
163
|
// Section header
|
|
164
164
|
section(title) {
|
|
165
|
+
const width = Math.max(24, Number(process.stdout.columns || 80));
|
|
166
|
+
const cleanTitle = String(title || '').replace(/\s+/g, ' ').trim();
|
|
167
|
+
const maxTitle = Math.max(10, width - 10);
|
|
168
|
+
const fittedTitle = cleanTitle.length > maxTitle ? `${cleanTitle.slice(0, maxTitle - 1)}…` : cleanTitle;
|
|
169
|
+
const side = Math.max(3, Math.floor((Math.min(width, 72) - fittedTitle.length - 2) / 2));
|
|
165
170
|
console.log();
|
|
166
|
-
console.log(chalk.bold.cyan(`${CH.hDouble.repeat(
|
|
171
|
+
console.log(chalk.bold.cyan(`${CH.hDouble.repeat(side)} ${fittedTitle} ${CH.hDouble.repeat(side)}`));
|
|
167
172
|
console.log();
|
|
168
173
|
}
|
|
169
174
|
// Progress
|