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.
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.js +61 -45
- package/dist/utils/agentRunOutcome.d.ts +2 -0
- package/dist/utils/agentRunOutcome.js +23 -1
- package/dist/utils/api.js +36 -11
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -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);
|
package/dist/commands/chat.js
CHANGED
|
@@ -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.
|
|
815
|
+
const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
811
816
|
if (spinner.isSpinning)
|
|
812
817
|
spinner.stop();
|
|
813
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
850
|
+
this.writeAgentActivityLine(chalk.red(` ${lines.join('\n ')}\n`));
|
|
843
851
|
}
|
|
844
852
|
else if (success && output && output.length > 0) {
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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
|
-
|
|
3413
|
-
|
|
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
|
-
|
|
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
|
|
3468
|
+
if (isSubstantiveAgentAnswer(fullAssistantText)) {
|
|
3446
3469
|
return fullAssistantText;
|
|
3447
3470
|
}
|
|
3448
|
-
|
|
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
|
-
|
|
3460
|
-
|
|
3461
|
-
: '';
|
|
3485
|
+
const synthesized = sections.join('\n\n');
|
|
3486
|
+
return isToolEvidenceStubAnswer(synthesized) ? '' : synthesized;
|
|
3462
3487
|
}
|
|
3463
3488
|
sanitizeV3AgentEventForUser(event) {
|
|
3464
3489
|
const sanitizeValue = (value) => {
|