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.
- package/dist/commands/chat.d.ts +2 -0
- package/dist/commands/chat.js +82 -48
- package/dist/utils/agentRunOutcome.d.ts +4 -0
- package/dist/utils/agentRunOutcome.js +47 -1
- package/dist/utils/api.js +45 -13
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -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);
|
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) {
|
|
@@ -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.
|
|
822
|
+
const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
811
823
|
if (spinner.isSpinning)
|
|
812
824
|
spinner.stop();
|
|
813
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
857
|
+
this.writeAgentActivityLine(chalk.red(` ${lines.join('\n ')}\n`));
|
|
843
858
|
}
|
|
844
859
|
else if (success && output && output.length > 0) {
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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 <
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
3413
|
-
|
|
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
|
-
|
|
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
|
|
3475
|
+
if (isSubstantiveAgentAnswer(fullAssistantText)) {
|
|
3446
3476
|
return fullAssistantText;
|
|
3447
3477
|
}
|
|
3448
|
-
|
|
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
|
-
|
|
3460
|
-
|
|
3461
|
-
: '';
|
|
3492
|
+
const synthesized = sections.join('\n\n');
|
|
3493
|
+
return isToolEvidenceStubAnswer(synthesized) ? '' : synthesized;
|
|
3462
3494
|
}
|
|
3463
3495
|
sanitizeV3AgentEventForUser(event) {
|
|
3464
3496
|
const sanitizeValue = (value) => {
|