vigthoria-cli 1.11.6 → 1.11.7

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.
@@ -117,6 +117,7 @@ export declare class ChatCommand {
117
117
  private consumeV3StreamPayload;
118
118
  private writeV3StreamText;
119
119
  private sanitizeV3VisibleStreamText;
120
+ private writeAgentActivityLine;
120
121
  private updateV3AgentSpinner;
121
122
  private updateOperatorSpinner;
122
123
  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) {
@@ -787,6 +787,11 @@ export class ChatCommand {
787
787
  .replace(/\n{3,}/g, '\n\n');
788
788
  return output;
789
789
  }
790
+ writeAgentActivityLine(text) {
791
+ if (this.jsonOutput)
792
+ return;
793
+ process.stdout.write(text);
794
+ }
790
795
  updateV3AgentSpinner(spinner, event) {
791
796
  if (this.isRawV3StreamPayload(event)) {
792
797
  this.consumeV3StreamPayload(spinner, event).catch((error) => {
@@ -807,20 +812,23 @@ export class ChatCommand {
807
812
  const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
808
813
  const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
809
814
  const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
810
- const stepLabel = chalk.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
815
+ const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
811
816
  if (spinner.isSpinning)
812
817
  spinner.stop();
813
- process.stderr.write(stepLabel + '\n');
818
+ this.writeAgentActivityLine(stepLabel + '\n');
814
819
  // Show extra detail for key tools
815
820
  const args = event.arguments || {};
816
821
  const toolName = event.name || event.tool || '';
817
822
  if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
818
823
  const len = args.content.length;
819
- process.stderr.write(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
824
+ this.writeAgentActivityLine(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
820
825
  }
821
826
  else if (toolName === 'bash' && typeof args.command === 'string') {
822
827
  const command = this.sanitizeServerPath(args.command);
823
- process.stderr.write(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
828
+ this.writeAgentActivityLine(chalk.gray(` $ ${command.slice(0, 120)}${command.length > 120 ? '…' : ''}\n`));
829
+ }
830
+ else if (toolName === 'read_file') {
831
+ this.writeAgentActivityLine(chalk.gray(` reading ${sanitizedTarget || 'file'}\n`));
824
832
  }
825
833
  spinner.start();
826
834
  spinner.text = `Running ${toolDesc}...`;
@@ -832,18 +840,28 @@ export class ChatCommand {
832
840
  const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
833
841
  if (spinner.isSpinning)
834
842
  spinner.stop();
835
- process.stderr.write(`${indicator} ${toolName}\n`);
843
+ this.writeAgentActivityLine(`${indicator} ${toolName}\n`);
836
844
  // Show output for failures, or brief summary for successes — sanitize server paths
837
845
  const rawOutput = typeof event.output === 'string' ? event.output.trim() : '';
838
846
  const output = this.sanitizeServerPath(rawOutput);
839
847
  if (!success && output) {
840
848
  const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
841
849
  const lines = sanitizedError.split('\n').slice(0, 4);
842
- process.stderr.write(chalk.red(` ${lines.join('\n ')}\n`));
850
+ this.writeAgentActivityLine(chalk.red(` ${lines.join('\n ')}\n`));
843
851
  }
844
852
  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`));
853
+ if (toolName === 'read_file' || toolName === 'grep' || toolName === 'search_files' || toolName === 'list_directory') {
854
+ const lineCount = output.split('\n').length;
855
+ const preview = output.split('\n').slice(0, 3).join('\n');
856
+ this.writeAgentActivityLine(chalk.gray(` ${lineCount} line${lineCount === 1 ? '' : 's'} returned\n`));
857
+ if (preview && preview.length > 20 && !/^\(no matches\)$/i.test(preview.trim())) {
858
+ this.writeAgentActivityLine(chalk.gray(`${preview.split('\n').map((l) => ` ${l}`).join('\n')}\n`));
859
+ }
860
+ }
861
+ else {
862
+ const brief = output.split('\n')[0].slice(0, 120);
863
+ this.writeAgentActivityLine(chalk.gray(` ${brief}${output.length > 120 ? '…' : ''}\n`));
864
+ }
847
865
  }
848
866
  spinner.start();
849
867
  spinner.text = 'Next step...';
@@ -858,7 +876,10 @@ export class ChatCommand {
858
876
  const iterText = this.sanitizeServerPath(event.content || '');
859
877
  if (spinner.isSpinning)
860
878
  spinner.stop();
861
- process.stderr.write(chalk.cyan(`\n── ${iterText || `Iteration ${this.v3IterationCount}`} ──\n`));
879
+ const display = iterText && !/read-only analysis turn|run at least one discovery tool/i.test(iterText)
880
+ ? iterText
881
+ : `Step ${this.v3IterationCount}: inspecting workspace…`;
882
+ this.writeAgentActivityLine(chalk.cyan(`\n── ${display} ──\n`));
862
883
  spinner.start();
863
884
  spinner.text = 'Analyzing...';
864
885
  return;
@@ -2705,39 +2726,6 @@ export class ChatCommand {
2705
2726
  if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
2706
2727
  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
2728
  }
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
2729
  if (!this.jsonOutput && previewGate?.required) {
2742
2730
  if (previewGate.passed) {
2743
2731
  console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
@@ -2765,14 +2753,42 @@ export class ChatCommand {
2765
2753
  // executor errors and the spinner needs to clearly say "failed", not "✓".
2766
2754
  let selfHealStatus = null;
2767
2755
  let selfHealTool = null;
2756
+ if (isToolEvidenceStubAnswer(answerContent)) {
2757
+ liveOutcome.answerContent = '';
2758
+ }
2768
2759
  const runEvaluation = evaluateExecutorSuccess(liveOutcome);
2769
2760
  let executorSucceeded = runEvaluation.executorSucceeded;
2770
- if (!executorSucceeded && !requiresWorkspaceChanges && !this.jsonOutput) {
2761
+ let finalAnswer = answerContent;
2762
+ const needsAnalysisRescue = !requiresWorkspaceChanges && !this.jsonOutput
2763
+ && (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
2764
+ if (needsAnalysisRescue) {
2771
2765
  const rescued = await this.tryAnalysisLocalRescue(prompt, workspacePath);
2772
2766
  if (rescued) {
2773
2767
  watcher?.stop();
2774
2768
  return true;
2775
2769
  }
2770
+ executorSucceeded = false;
2771
+ }
2772
+ if (!this.jsonOutput && !this.directPromptMode) {
2773
+ if (this.v3StreamingStarted && isSubstantiveAgentAnswer(this.v3StreamedAnswerBuffer)) {
2774
+ console.log('');
2775
+ console.log(chalk.bold('Answer:'));
2776
+ console.log(this.v3StreamedAnswerBuffer.trim());
2777
+ }
2778
+ else if (isSubstantiveAgentAnswer(finalAnswer)) {
2779
+ console.log('');
2780
+ console.log(chalk.bold('Answer:'));
2781
+ console.log(finalAnswer);
2782
+ }
2783
+ else if (!requiresWorkspaceChanges) {
2784
+ console.log('');
2785
+ console.log(chalk.yellow('No written analysis was produced for your question.'));
2786
+ 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").'));
2787
+ }
2788
+ else if (finalAnswer) {
2789
+ console.log('');
2790
+ console.log(finalAnswer);
2791
+ }
2776
2792
  }
2777
2793
  if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
2778
2794
  const clientToolErrors = this.api.getClientToolErrors();
@@ -3381,7 +3397,7 @@ export class ChatCommand {
3381
3397
  : `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
3382
3398
  console.log(' ' + chalk.white(taskLine + '.'));
3383
3399
  }
3384
- else if (outcome.answerContent) {
3400
+ else if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
3385
3401
  const preview = outcome.answerContent.length > 160
3386
3402
  ? `${outcome.answerContent.slice(0, 160)}…`
3387
3403
  : outcome.answerContent;
@@ -31,6 +31,8 @@ export interface RunEvaluation {
31
31
  uiTheme: 'success' | 'warning' | 'error';
32
32
  }
33
33
  export declare function createLiveOutcome(): LiveOutcome;
34
+ /** Tool-trace stubs like `[read_file] "index.html"` — not a user-facing answer. */
35
+ export declare function isToolEvidenceStubAnswer(text: string): boolean;
34
36
  /** True when text looks like a real answer, not an executor/system placeholder. */
35
37
  export declare function isSubstantiveAgentAnswer(text: string): boolean;
36
38
  /** Pick the first substantive answer from response body and/or streamed text. */
@@ -27,15 +27,37 @@ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
27
27
  'glob',
28
28
  'dir',
29
29
  ]);
30
+ const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
31
+ /** Tool-trace stubs like `[read_file] "index.html"` — not a user-facing answer. */
32
+ export function isToolEvidenceStubAnswer(text) {
33
+ const trimmed = String(text || '').trim();
34
+ if (!trimmed)
35
+ return true;
36
+ if (GENERIC_SUMMARY_RE.test(trimmed.replace(/\.\s*\d+\s+tool steps.*$/i, '').trim()))
37
+ return true;
38
+ const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean);
39
+ if (lines.length === 0)
40
+ return true;
41
+ const stubLine = /^\[(?:read_file|grep|list_directory|search_files|glob|tool_call)\]/i;
42
+ const filesOnly = /^Files inspected:/i;
43
+ const stubLines = lines.filter((line) => stubLine.test(line) || filesOnly.test(line)).length;
44
+ if (stubLines === lines.length)
45
+ return true;
46
+ if (stubLines > 0 && stubLines / lines.length >= 0.6 && trimmed.length < 400)
47
+ return true;
48
+ return false;
49
+ }
30
50
  /** True when text looks like a real answer, not an executor/system placeholder. */
31
51
  export function isSubstantiveAgentAnswer(text) {
32
52
  const trimmed = String(text || '').trim();
33
- if (trimmed.length < 30)
53
+ if (trimmed.length < 80)
34
54
  return false;
35
55
  if (EXECUTOR_PLACEHOLDER_RE.test(trimmed))
36
56
  return false;
37
57
  if (/^v3 agent workflow completed\.?$/i.test(trimmed))
38
58
  return false;
59
+ if (isToolEvidenceStubAnswer(trimmed))
60
+ return false;
39
61
  return true;
40
62
  }
41
63
  /** Pick the first substantive answer from response body and/or streamed text. */
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 {
@@ -3402,22 +3402,45 @@ document.addEventListener('DOMContentLoaded', () => {
3402
3402
  synthesizeAnswerFromV3Events(events) {
3403
3403
  const toolResults = [];
3404
3404
  const filesRead = [];
3405
+ const readExcerpts = [];
3405
3406
  const filesWritten = [];
3406
3407
  const assistantFragments = [];
3408
+ const pendingReadPaths = new Map();
3409
+ let readIndex = 0;
3407
3410
  for (const event of events) {
3408
3411
  if (!event)
3409
3412
  continue;
3413
+ if (event.type === 'tool_call') {
3414
+ const name = String(event.name || event.tool || '').trim();
3415
+ if (name === 'read_file') {
3416
+ const target = event.arguments?.path || event.arguments?.file_path || event.target;
3417
+ if (target)
3418
+ pendingReadPaths.set(readIndex++, String(target));
3419
+ }
3420
+ }
3410
3421
  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));
3422
+ const name = String(event.name || event.tool || 'unknown_tool').trim();
3423
+ const output = event.output.trim();
3424
+ if (name === 'read_file') {
3425
+ const target = typeof event.target === 'string'
3426
+ ? event.target
3427
+ : (event.arguments?.path || event.arguments?.file_path || filesRead[filesRead.length] || 'file');
3428
+ if (target)
3429
+ filesRead.push(sanitizeUserFacingPathText(String(target)));
3430
+ if (output.length > 120 && !/^"[^"]+"$/.test(output)) {
3431
+ const header = sanitizeUserFacingPathText(String(target || 'file'));
3432
+ const body = sanitizeUserFacingPathText(output.slice(0, 2500));
3433
+ readExcerpts.push(`${header}\n${body}${output.length > 2500 ? '\n…' : ''}`);
3434
+ }
3435
+ else if (output.length > 0) {
3436
+ toolResults.push(`[${name}] ${sanitizeUserFacingPathText(output.slice(0, 300))}`);
3437
+ }
3414
3438
  }
3415
3439
  else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
3416
3440
  filesWritten.push(sanitizeUserFacingPathText(event.target));
3417
3441
  }
3418
3442
  else {
3419
- // Keep last ~300 chars of output for context
3420
- const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
3443
+ const excerpt = output.length > 300 ? output.slice(-300) : output;
3421
3444
  toolResults.push(`[${name}] ${sanitizeUserFacingPathText(excerpt)}`);
3422
3445
  }
3423
3446
  }
@@ -3442,10 +3465,13 @@ document.addEventListener('DOMContentLoaded', () => {
3442
3465
  // Concatenate ALL assistant text fragments in order — keeps full
3443
3466
  // multi-turn reasoning instead of only the last fragment.
3444
3467
  const fullAssistantText = assistantFragments.join('\n\n').trim();
3445
- if (fullAssistantText.length > 20) {
3468
+ if (isSubstantiveAgentAnswer(fullAssistantText)) {
3446
3469
  return fullAssistantText;
3447
3470
  }
3448
- // Otherwise build a summary from tool evidence
3471
+ if (readExcerpts.length > 0) {
3472
+ return '';
3473
+ }
3474
+ // Otherwise build a summary from tool evidence — but never treat stubs as answers.
3449
3475
  const sections = [];
3450
3476
  if (filesRead.length > 0) {
3451
3477
  sections.push(`Files inspected: ${filesRead.join(', ')}`);
@@ -3456,9 +3482,8 @@ document.addEventListener('DOMContentLoaded', () => {
3456
3482
  if (toolResults.length > 0) {
3457
3483
  sections.push(toolResults.slice(-5).join('\n'));
3458
3484
  }
3459
- return sections.length > 0
3460
- ? sections.join('\n\n')
3461
- : '';
3485
+ const synthesized = sections.join('\n\n');
3486
+ return isToolEvidenceStubAnswer(synthesized) ? '' : synthesized;
3462
3487
  }
3463
3488
  sanitizeV3AgentEventForUser(event) {
3464
3489
  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.7",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",