vigthoria-cli 1.11.6 → 1.11.8

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.
@@ -64,6 +64,7 @@ export declare class ChatCommand {
64
64
  private resolveInitialModel;
65
65
  private isLegacyAgentFallbackAllowed;
66
66
  private shouldRescueFailedAnalysis;
67
+ private isDeepAnalysisPrompt;
67
68
  private tryAnalysisLocalRescue;
68
69
  private resolveAgentExecutionPolicy;
69
70
  private getMessagesForModel;
@@ -117,6 +118,7 @@ export declare class ChatCommand {
117
118
  private consumeV3StreamPayload;
118
119
  private writeV3StreamText;
119
120
  private sanitizeV3VisibleStreamText;
121
+ private writeAgentActivityLine;
120
122
  private updateV3AgentSpinner;
121
123
  private updateOperatorSpinner;
122
124
  constructor(config: Config, logger: Logger);
@@ -13,7 +13,7 @@ import { TaskDisplay } from '../utils/task-display.js';
13
13
  import { ProjectMemoryService } from '../utils/project-memory.js';
14
14
  import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
15
15
  import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
16
- import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
16
+ import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
17
17
  const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
18
18
  const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
19
19
  if (!rawValue) {
@@ -262,6 +262,10 @@ export class ChatCommand {
262
262
  shouldRescueFailedAnalysis() {
263
263
  return process.env.VIGTHORIA_DISABLE_ANALYSIS_RESCUE !== '1';
264
264
  }
265
+ isDeepAnalysisPrompt(prompt) {
266
+ return /\b(deep|deeper|full|complete|thorough|systematic|architecture|architectural)\s+(analyse|analyze|analysis|review|dive|scan)\b/i.test(prompt)
267
+ || /\b(analyse|analyze|analysis|review)\b[\s\S]{0,40}\b(deep|deeper|full|complete|thorough|systematic|architecture|architectural)\b/i.test(prompt);
268
+ }
265
269
  async tryAnalysisLocalRescue(prompt, workspacePath) {
266
270
  if (!this.shouldRescueFailedAnalysis() || !this.tools) {
267
271
  return false;
@@ -784,9 +788,17 @@ export class ChatCommand {
784
788
  .replace(/^\s*(?:list_dir|read_file|write_file|edit_file|glob|grep|bash)\s*$/gim, '')
785
789
  .replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
786
790
  .replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
791
+ .replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
792
+ .replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
793
+ .replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
787
794
  .replace(/\n{3,}/g, '\n\n');
788
795
  return output;
789
796
  }
797
+ writeAgentActivityLine(text) {
798
+ if (this.jsonOutput)
799
+ return;
800
+ process.stdout.write(text);
801
+ }
790
802
  updateV3AgentSpinner(spinner, event) {
791
803
  if (this.isRawV3StreamPayload(event)) {
792
804
  this.consumeV3StreamPayload(spinner, event).catch((error) => {
@@ -807,20 +819,23 @@ export class ChatCommand {
807
819
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
808
820
  const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
809
821
  const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
810
- const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
822
+ const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
811
823
  if (spinner.isSpinning)
812
824
  spinner.stop();
813
- process.stderr.write(stepLabel + '\n');
825
+ this.writeAgentActivityLine(stepLabel + '\n');
814
826
  // Show extra detail for key tools
815
827
  const args = event.arguments || {};
816
828
  const toolName = event.name || event.tool || '';
817
829
  if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
818
830
  const len = args.content.length;
819
- process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
831
+ this.writeAgentActivityLine(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
820
832
  }
821
833
  else if (toolName === 'bash' && typeof args.command === 'string') {
822
834
  const command = this.sanitizeServerPath(args.command);
823
- process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
835
+ this.writeAgentActivityLine(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
836
+ }
837
+ else if (toolName === 'read_file') {
838
+ this.writeAgentActivityLine(chalk.gray(` reading ${sanitizedTarget || 'file'}\n`));
824
839
  }
825
840
  spinner.start();
826
841
  spinner.text = `Running ${toolDesc}...`;
@@ -832,18 +847,28 @@ export class ChatCommand {
832
847
  const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
833
848
  if (spinner.isSpinning)
834
849
  spinner.stop();
835
- process.stderr.write(`${indicator} ${toolName}\n`);
850
+ this.writeAgentActivityLine(`${indicator} ${toolName}\n`);
836
851
  // Show output for failures, or brief summary for successes — sanitize server paths
837
852
  const rawOutput = typeof event.output === 'string' ? event.output.trim() : '';
838
853
  const output = this.sanitizeServerPath(rawOutput);
839
854
  if (!success && output) {
840
855
  const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
841
856
  const lines = sanitizedError.split('\n').slice(0, 4);
842
- process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
857
+ this.writeAgentActivityLine(chalk.red(` ${lines.join('\n ')}\n`));
843
858
  }
844
859
  else if (success && output && output.length > 0) {
845
- const brief = output.split('\n')[0].slice(0, 120);
846
- process.stderr.write(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
860
+ if (toolName === 'read_file' || toolName === 'grep' || toolName === 'search_files' || toolName === 'list_directory') {
861
+ const lineCount = output.split('\n').length;
862
+ const preview = output.split('\n').slice(0, 3).join('\n');
863
+ this.writeAgentActivityLine(chalk.gray(` ${lineCount} line${lineCount === 1 ? '' : 's'} returned\n`));
864
+ if (preview && preview.length > 20 && !/^\(no matches\)$/i.test(preview.trim())) {
865
+ this.writeAgentActivityLine(chalk.gray(`${preview.split('\n').map((l) => ` ${l}`).join('\n')}\n`));
866
+ }
867
+ }
868
+ else {
869
+ const brief = output.split('\n')[0].slice(0, 120);
870
+ this.writeAgentActivityLine(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
871
+ }
847
872
  }
848
873
  spinner.start();
849
874
  spinner.text = 'Next step...';
@@ -858,7 +883,10 @@ export class ChatCommand {
858
883
  const iterText = this.sanitizeServerPath(event.content || '');
859
884
  if (spinner.isSpinning)
860
885
  spinner.stop();
861
- process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
886
+ const display = iterText && !/read-only analysis turn|run at least one discovery tool/i.test(iterText)
887
+ ? iterText
888
+ : `Step ${this.v3IterationCount}: inspecting workspace…`;
889
+ this.writeAgentActivityLine(chalk.cyan(`\n── ${display} ──\n`));
862
890
  spinner.start();
863
891
  spinner.text = 'Analyzing...';
864
892
  return;
@@ -1967,6 +1995,7 @@ export class ChatCommand {
1967
1995
  buildLocalLoopLiveOutcome(prompt) {
1968
1996
  const liveOutcome = createLiveOutcome();
1969
1997
  liveOutcome.requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
1998
+ liveOutcome.deepAnalysisRequired = this.isDeepAnalysisPrompt(prompt);
1970
1999
  liveOutcome.analysisToolsUsed = this.agentToolEvidence.discovery;
1971
2000
  liveOutcome.changedFileCount = this.agentToolEvidence.mutation;
1972
2001
  liveOutcome.workspaceHasOutput = this.agentToolEvidence.mutation > 0;
@@ -2493,6 +2522,7 @@ export class ChatCommand {
2493
2522
  qualityMissing: [],
2494
2523
  qualityBlockers: [],
2495
2524
  };
2525
+ liveOutcome.deepAnalysisRequired = this.isDeepAnalysisPrompt(prompt);
2496
2526
  const parsePlannerSummary = (raw) => {
2497
2527
  if (!raw || typeof raw !== 'string')
2498
2528
  return;
@@ -2627,6 +2657,12 @@ export class ChatCommand {
2627
2657
  if (Number(event.discovery_tools_used) > 0) {
2628
2658
  liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
2629
2659
  }
2660
+ if (Number(event.discovery_read_files) > 0) {
2661
+ liveOutcome.analysisReadToolsUsed = Math.max(liveOutcome.analysisReadToolsUsed, Number(event.discovery_read_files));
2662
+ }
2663
+ if (event.analysis_deep_required === true) {
2664
+ liveOutcome.deepAnalysisRequired = true;
2665
+ }
2630
2666
  taskDisplay.complete(1);
2631
2667
  }
2632
2668
  else if (event.type === 'executor_error') {
@@ -2705,39 +2741,6 @@ export class ChatCommand {
2705
2741
  if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
2706
2742
  console.log(chalk.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
2707
2743
  }
2708
- if (this.v3StreamingStarted) {
2709
- // Content was already streamed to stdout in real-time; skip duplicate print.
2710
- if (!this.jsonOutput) {
2711
- console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2712
- }
2713
- }
2714
- else if (answerContent) {
2715
- if (!this.directPromptMode) {
2716
- console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2717
- }
2718
- console.log('');
2719
- console.log(chalk.bold('Answer:'));
2720
- console.log(answerContent);
2721
- }
2722
- else if (response.content) {
2723
- if (!this.directPromptMode) {
2724
- console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2725
- }
2726
- console.log(response.content);
2727
- }
2728
- else if (!requiresWorkspaceChanges) {
2729
- if (!this.directPromptMode) {
2730
- console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2731
- }
2732
- console.log(chalk.yellow('The agent finished without producing an answer to your question.'));
2733
- console.log(chalk.gray('Try /retry or ask again with more detail (e.g. file name or feature).'));
2734
- }
2735
- else {
2736
- if (!this.directPromptMode) {
2737
- console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
2738
- }
2739
- console.log('V3 agent workflow completed.');
2740
- }
2741
2744
  if (!this.jsonOutput && previewGate?.required) {
2742
2745
  if (previewGate.passed) {
2743
2746
  console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
@@ -2765,14 +2768,45 @@ export class ChatCommand {
2765
2768
  // executor errors and the spinner needs to clearly say "failed", not "✓".
2766
2769
  let selfHealStatus = null;
2767
2770
  let selfHealTool = null;
2771
+ if (isToolEvidenceStubAnswer(answerContent)) {
2772
+ liveOutcome.answerContent = '';
2773
+ }
2768
2774
  const runEvaluation = evaluateExecutorSuccess(liveOutcome);
2769
2775
  let executorSucceeded = runEvaluation.executorSucceeded;
2770
- if (!executorSucceeded && !requiresWorkspaceChanges && !this.jsonOutput) {
2776
+ let finalAnswer = answerContent;
2777
+ if (isToolEvidenceStubAnswer(finalAnswer)) {
2778
+ finalAnswer = '';
2779
+ }
2780
+ const needsAnalysisRescue = !requiresWorkspaceChanges && !this.jsonOutput
2781
+ && (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
2782
+ if (needsAnalysisRescue) {
2771
2783
  const rescued = await this.tryAnalysisLocalRescue(prompt, workspacePath);
2772
2784
  if (rescued) {
2773
2785
  watcher?.stop();
2774
2786
  return true;
2775
2787
  }
2788
+ executorSucceeded = false;
2789
+ }
2790
+ if (!this.jsonOutput && !this.directPromptMode) {
2791
+ if (this.v3StreamingStarted && isSubstantiveAgentAnswer(this.v3StreamedAnswerBuffer)) {
2792
+ console.log('');
2793
+ console.log(chalk.bold('Answer:'));
2794
+ console.log(this.v3StreamedAnswerBuffer.trim());
2795
+ }
2796
+ else if (isSubstantiveAgentAnswer(finalAnswer)) {
2797
+ console.log('');
2798
+ console.log(chalk.bold('Answer:'));
2799
+ console.log(finalAnswer);
2800
+ }
2801
+ else if (!requiresWorkspaceChanges) {
2802
+ console.log('');
2803
+ console.log(chalk.yellow('No written analysis was produced for your question.'));
2804
+ console.log(chalk.gray('The agent inspected files but did not synthesize findings. Try /retry or rephrase (e.g. "Summarize architecture of index.html and game.js").'));
2805
+ }
2806
+ else if (finalAnswer) {
2807
+ console.log('');
2808
+ console.log(finalAnswer);
2809
+ }
2776
2810
  }
2777
2811
  if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
2778
2812
  const clientToolErrors = this.api.getClientToolErrors();
@@ -2860,7 +2894,7 @@ export class ChatCommand {
2860
2894
  qualityMissing: liveOutcome.qualityMissing,
2861
2895
  qualityBlockers: liveOutcome.qualityBlockers,
2862
2896
  hasOutput: workspaceHasOutput,
2863
- answerContent: answerContent || null,
2897
+ answerContent: finalAnswer || null,
2864
2898
  selfHealStatus,
2865
2899
  selfHealTool,
2866
2900
  plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
@@ -2888,7 +2922,7 @@ export class ChatCommand {
2888
2922
  taskId: response.taskId || null,
2889
2923
  contextId: response.contextId || null,
2890
2924
  partial: response.partial === true || (!executorSucceeded && liveOutcome.tasksSucceeded > 0),
2891
- content: answerContent || response.content || runEvaluation.statusHeadline,
2925
+ content: finalAnswer || runEvaluation.statusHeadline,
2892
2926
  statusHeadline: runEvaluation.statusHeadline,
2893
2927
  tasksSucceeded: liveOutcome.tasksSucceeded,
2894
2928
  tasksTotal: liveOutcome.tasksTotal,
@@ -2904,7 +2938,7 @@ export class ChatCommand {
2904
2938
  },
2905
2939
  }, null, 2));
2906
2940
  }
2907
- this.messages.push({ role: 'assistant', content: answerContent || response.content || runEvaluation.statusHeadline });
2941
+ this.messages.push({ role: 'assistant', content: finalAnswer || runEvaluation.statusHeadline });
2908
2942
  watcher?.stop();
2909
2943
  return true;
2910
2944
  }
@@ -3381,7 +3415,7 @@ export class ChatCommand {
3381
3415
  : `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
3382
3416
  console.log(' ' + chalk.white(taskLine + '.'));
3383
3417
  }
3384
- else if (outcome.answerContent) {
3418
+ else if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
3385
3419
  const preview = outcome.answerContent.length > 160
3386
3420
  ? `${outcome.answerContent.slice(0, 160)}…`
3387
3421
  : outcome.answerContent;
@@ -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,6 +33,8 @@ export interface RunEvaluation {
31
33
  uiTheme: 'success' | 'warning' | 'error';
32
34
  }
33
35
  export declare function createLiveOutcome(): LiveOutcome;
36
+ /** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
37
+ export declare function isToolEvidenceStubAnswer(text: string): boolean;
34
38
  /** True when text looks like a real answer, not an executor/system placeholder. */
35
39
  export declare function isSubstantiveAgentAnswer(text: string): boolean;
36
40
  /** Pick the first substantive answer from response body and/or streamed text. */
@@ -17,25 +17,59 @@ 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',
27
29
  'glob',
28
30
  'dir',
29
31
  ]);
32
+ const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
33
+ /** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
34
+ export function isToolEvidenceStubAnswer(text) {
35
+ const trimmed = String(text || '').trim();
36
+ if (!trimmed)
37
+ return true;
38
+ if (BOOTSTRAP_PLACEHOLDER_RE.test(trimmed))
39
+ return true;
40
+ if (GENERIC_SUMMARY_RE.test(trimmed.replace(/\.\s*\d+\s+tool steps.*$/i, '').trim()))
41
+ return true;
42
+ const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean);
43
+ if (lines.length === 0)
44
+ return true;
45
+ const stubLine = /^\[(?:read_file|grep|list_directory|search_files|glob|tool_call)\]/i;
46
+ const xmlToolLine = /^<\/?(?:read_file|grep|list_directory|search_files|glob|tool_call|tool_result|path)\b[^>]*>/i;
47
+ const xmlToolBlockOnly = trimmed
48
+ .replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
49
+ .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, '')
50
+ .replace(/\s+/g, '')
51
+ .trim();
52
+ if (!xmlToolBlockOnly)
53
+ return true;
54
+ const filesOnly = /^Files inspected:/i;
55
+ const stubLines = lines.filter((line) => stubLine.test(line) || xmlToolLine.test(line) || filesOnly.test(line)).length;
56
+ if (stubLines === lines.length)
57
+ return true;
58
+ if (stubLines > 0 && stubLines / lines.length >= 0.6 && trimmed.length < 400)
59
+ return true;
60
+ return false;
61
+ }
30
62
  /** True when text looks like a real answer, not an executor/system placeholder. */
31
63
  export function isSubstantiveAgentAnswer(text) {
32
64
  const trimmed = String(text || '').trim();
33
- if (trimmed.length < 30)
65
+ if (trimmed.length < 80)
34
66
  return false;
35
67
  if (EXECUTOR_PLACEHOLDER_RE.test(trimmed))
36
68
  return false;
37
69
  if (/^v3 agent workflow completed\.?$/i.test(trimmed))
38
70
  return false;
71
+ if (isToolEvidenceStubAnswer(trimmed))
72
+ return false;
39
73
  return true;
40
74
  }
41
75
  /** Pick the first substantive answer from response body and/or streamed text. */
@@ -79,6 +113,7 @@ const READ_DISCOVERY_TOOLS = new Set([
79
113
  'grep',
80
114
  'list_directory',
81
115
  'glob',
116
+ 'search_files',
82
117
  'search_project',
83
118
  'list_dir',
84
119
  'dir',
@@ -149,6 +184,10 @@ export function evaluateExecutorSuccess(liveOutcome) {
149
184
  const answerText = normalizeAgentAnswerContent(liveOutcome.answerContent);
150
185
  const hasSubstantiveAnswer = isSubstantiveAgentAnswer(answerText);
151
186
  const hasDeepRead = liveOutcome.analysisReadToolsUsed > 0;
187
+ const meetsDeepAnalysisDepth = !liveOutcome.deepAnalysisRequired
188
+ || (liveOutcome.analysisToolsUsed >= 4
189
+ && liveOutcome.analysisReadToolsUsed >= 3
190
+ && answerText.length >= 700);
152
191
  if (hasFatalError && !hasSubstantiveAnswer) {
153
192
  return {
154
193
  executorSucceeded: false,
@@ -177,6 +216,13 @@ export function evaluateExecutorSuccess(liveOutcome) {
177
216
  uiTheme: 'warning',
178
217
  };
179
218
  }
219
+ if (!meetsDeepAnalysisDepth) {
220
+ return {
221
+ executorSucceeded: false,
222
+ statusHeadline: '⚠ Deep analysis was too shallow — retrying needs a project map, multiple file reads, and a structured report',
223
+ uiTheme: 'warning',
224
+ };
225
+ }
180
226
  return {
181
227
  executorSucceeded: true,
182
228
  statusHeadline: '✓ Analysis completed',
package/dist/utils/api.js CHANGED
@@ -11,7 +11,7 @@ import path from 'path';
11
11
  import WebSocket from 'ws';
12
12
  import { buildSemanticContext } from './context-ranker.js';
13
13
  import { getChangedFiles } from './workspace-cache.js';
14
- import { isSubstantiveAgentAnswer } from './agentRunOutcome.js';
14
+ import { isSubstantiveAgentAnswer, isToolEvidenceStubAnswer } from './agentRunOutcome.js';
15
15
  export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
16
16
  export const VIGTHORIA_SERVER_TEMPORARILY_UNAVAILABLE_MESSAGE = 'Vigthoria Server is temporarily not available. Please try again later. If the issue persists, please contact support.';
17
17
  export class CLIError extends Error {
@@ -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
- return sanitizeUserFacingPathText(completionEvent.summary);
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
- return this.sanitizeV3AgentResponseText(messageEvent.content);
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.
@@ -3387,6 +3391,9 @@ document.addEventListener('DOMContentLoaded', () => {
3387
3391
  .replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
3388
3392
  .replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
3389
3393
  .replace(/<tool_code>[\s\S]*?<\/tool_code>/gi, '')
3394
+ .replace(/<read_file>\s*<path>[\s\S]*?<\/path>\s*<\/read_file>/gi, '')
3395
+ .replace(/<(?:grep|list_directory|search_files|glob|tool_result)\b[\s\S]*?<\/(?:grep|list_directory|search_files|glob|tool_result)>/gi, '')
3396
+ .replace(/bootstrap discovery completed\s*[—-]\s*inspect the most relevant files next\.?/gi, '')
3390
3397
  .replace(/```json\s*\[\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*\]\s*```/gi, '')
3391
3398
  .replace(/```json\s*\{[\s\S]*?"tool"[\s\S]*?\}\s*```/gi, '')
3392
3399
  .replace(/^\s*(?:json\s*)?\[\s*\{[\s\S]*?"tool"[\s\S]*$/gim, '')
@@ -3402,22 +3409,45 @@ document.addEventListener('DOMContentLoaded', () => {
3402
3409
  synthesizeAnswerFromV3Events(events) {
3403
3410
  const toolResults = [];
3404
3411
  const filesRead = [];
3412
+ const readExcerpts = [];
3405
3413
  const filesWritten = [];
3406
3414
  const assistantFragments = [];
3415
+ const pendingReadPaths = new Map();
3416
+ let readIndex = 0;
3407
3417
  for (const event of events) {
3408
3418
  if (!event)
3409
3419
  continue;
3420
+ if (event.type === 'tool_call') {
3421
+ const name = String(event.name || event.tool || '').trim();
3422
+ if (name === 'read_file') {
3423
+ const target = event.arguments?.path || event.arguments?.file_path || event.target;
3424
+ if (target)
3425
+ pendingReadPaths.set(readIndex++, String(target));
3426
+ }
3427
+ }
3410
3428
  if (event.type === 'tool_result' && event.success && typeof event.output === 'string') {
3411
- const name = event.name || 'unknown_tool';
3412
- if (name === 'read_file' && typeof event.target === 'string') {
3413
- filesRead.push(sanitizeUserFacingPathText(event.target));
3429
+ const name = String(event.name || event.tool || 'unknown_tool').trim();
3430
+ const output = event.output.trim();
3431
+ if (name === 'read_file') {
3432
+ const target = typeof event.target === 'string'
3433
+ ? event.target
3434
+ : (event.arguments?.path || event.arguments?.file_path || filesRead[filesRead.length] || 'file');
3435
+ if (target)
3436
+ filesRead.push(sanitizeUserFacingPathText(String(target)));
3437
+ if (output.length > 120 && !/^"[^"]+"$/.test(output)) {
3438
+ const header = sanitizeUserFacingPathText(String(target || 'file'));
3439
+ const body = sanitizeUserFacingPathText(output.slice(0, 2500));
3440
+ readExcerpts.push(`${header}\n${body}${output.length > 2500 ? '\n…' : ''}`);
3441
+ }
3442
+ else if (output.length > 0) {
3443
+ toolResults.push(`[${name}] ${sanitizeUserFacingPathText(output.slice(0, 300))}`);
3444
+ }
3414
3445
  }
3415
3446
  else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
3416
3447
  filesWritten.push(sanitizeUserFacingPathText(event.target));
3417
3448
  }
3418
3449
  else {
3419
- // Keep last ~300 chars of output for context
3420
- const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
3450
+ const excerpt = output.length > 300 ? output.slice(-300) : output;
3421
3451
  toolResults.push(`[${name}] ${sanitizeUserFacingPathText(excerpt)}`);
3422
3452
  }
3423
3453
  }
@@ -3442,10 +3472,13 @@ document.addEventListener('DOMContentLoaded', () => {
3442
3472
  // Concatenate ALL assistant text fragments in order — keeps full
3443
3473
  // multi-turn reasoning instead of only the last fragment.
3444
3474
  const fullAssistantText = assistantFragments.join('\n\n').trim();
3445
- if (fullAssistantText.length > 20) {
3475
+ if (isSubstantiveAgentAnswer(fullAssistantText)) {
3446
3476
  return fullAssistantText;
3447
3477
  }
3448
- // Otherwise build a summary from tool evidence
3478
+ if (readExcerpts.length > 0) {
3479
+ return '';
3480
+ }
3481
+ // Otherwise build a summary from tool evidence — but never treat stubs as answers.
3449
3482
  const sections = [];
3450
3483
  if (filesRead.length > 0) {
3451
3484
  sections.push(`Files inspected: ${filesRead.join(', ')}`);
@@ -3456,9 +3489,8 @@ document.addEventListener('DOMContentLoaded', () => {
3456
3489
  if (toolResults.length > 0) {
3457
3490
  sections.push(toolResults.slice(-5).join('\n'));
3458
3491
  }
3459
- return sections.length > 0
3460
- ? sections.join('\n\n')
3461
- : '';
3492
+ const synthesized = sections.join('\n\n');
3493
+ return isToolEvidenceStubAnswer(synthesized) ? '' : synthesized;
3462
3494
  }
3463
3495
  sanitizeV3AgentEventForUser(event) {
3464
3496
  const sanitizeValue = (value) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.11.6",
3
+ "version": "1.11.8",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",