vigthoria-cli 1.11.5 → 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 +3 -0
- package/dist/commands/chat.js +91 -46
- package/dist/utils/agentRunOutcome.d.ts +2 -0
- package/dist/utils/agentRunOutcome.js +23 -1
- package/dist/utils/api.js +78 -20
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ export declare class ChatCommand {
|
|
|
63
63
|
private getDefaultChatModel;
|
|
64
64
|
private resolveInitialModel;
|
|
65
65
|
private isLegacyAgentFallbackAllowed;
|
|
66
|
+
private shouldRescueFailedAnalysis;
|
|
67
|
+
private tryAnalysisLocalRescue;
|
|
66
68
|
private resolveAgentExecutionPolicy;
|
|
67
69
|
private getMessagesForModel;
|
|
68
70
|
private getActivePersonaMode;
|
|
@@ -115,6 +117,7 @@ export declare class ChatCommand {
|
|
|
115
117
|
private consumeV3StreamPayload;
|
|
116
118
|
private writeV3StreamText;
|
|
117
119
|
private sanitizeV3VisibleStreamText;
|
|
120
|
+
private writeAgentActivityLine;
|
|
118
121
|
private updateV3AgentSpinner;
|
|
119
122
|
private updateOperatorSpinner;
|
|
120
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) {
|
|
@@ -259,6 +259,28 @@ export class ChatCommand {
|
|
|
259
259
|
// Operators can still opt in during emergency local debugging.
|
|
260
260
|
return process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '1';
|
|
261
261
|
}
|
|
262
|
+
shouldRescueFailedAnalysis() {
|
|
263
|
+
return process.env.VIGTHORIA_DISABLE_ANALYSIS_RESCUE !== '1';
|
|
264
|
+
}
|
|
265
|
+
async tryAnalysisLocalRescue(prompt, workspacePath) {
|
|
266
|
+
if (!this.shouldRescueFailedAnalysis() || !this.tools) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
const clientToolErrors = this.api.getClientToolErrors();
|
|
270
|
+
if (clientToolErrors.some((err) => /could not accept the result|client tool bridge failed/i.test(err))) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log(chalk.yellow('V3 analysis did not produce a grounded answer — retrying with local workspace tools...'));
|
|
275
|
+
console.log(chalk.gray(`Workspace: ${workspacePath}`));
|
|
276
|
+
console.log('');
|
|
277
|
+
const last = this.messages[this.messages.length - 1];
|
|
278
|
+
if (last?.role === 'assistant') {
|
|
279
|
+
this.messages.pop();
|
|
280
|
+
}
|
|
281
|
+
await this.runLocalAgentLoop(prompt);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
262
284
|
resolveAgentExecutionPolicy(prompt) {
|
|
263
285
|
const explicitModel = this.modelExplicitlySelected;
|
|
264
286
|
const heavyTask = this.config.isComplexTask(prompt);
|
|
@@ -765,6 +787,11 @@ export class ChatCommand {
|
|
|
765
787
|
.replace(/\n{3,}/g, '\n\n');
|
|
766
788
|
return output;
|
|
767
789
|
}
|
|
790
|
+
writeAgentActivityLine(text) {
|
|
791
|
+
if (this.jsonOutput)
|
|
792
|
+
return;
|
|
793
|
+
process.stdout.write(text);
|
|
794
|
+
}
|
|
768
795
|
updateV3AgentSpinner(spinner, event) {
|
|
769
796
|
if (this.isRawV3StreamPayload(event)) {
|
|
770
797
|
this.consumeV3StreamPayload(spinner, event).catch((error) => {
|
|
@@ -785,20 +812,23 @@ export class ChatCommand {
|
|
|
785
812
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
786
813
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
787
814
|
const shortTarget = sanitizedTarget ? ` → ${sanitizedTarget.replace(/\\/g, '/').split('/').slice(-2).join('/')}` : '';
|
|
788
|
-
const stepLabel = chalk.cyan(` [${this.
|
|
815
|
+
const stepLabel = chalk.cyan(` [${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
789
816
|
if (spinner.isSpinning)
|
|
790
817
|
spinner.stop();
|
|
791
|
-
|
|
818
|
+
this.writeAgentActivityLine(stepLabel + '\n');
|
|
792
819
|
// Show extra detail for key tools
|
|
793
820
|
const args = event.arguments || {};
|
|
794
821
|
const toolName = event.name || event.tool || '';
|
|
795
822
|
if ((toolName === 'write_file' || toolName === 'edit_file') && typeof args.content === 'string') {
|
|
796
823
|
const len = args.content.length;
|
|
797
|
-
|
|
824
|
+
this.writeAgentActivityLine(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
|
|
798
825
|
}
|
|
799
826
|
else if (toolName === 'bash' && typeof args.command === 'string') {
|
|
800
827
|
const command = this.sanitizeServerPath(args.command);
|
|
801
|
-
|
|
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`));
|
|
802
832
|
}
|
|
803
833
|
spinner.start();
|
|
804
834
|
spinner.text = `Running ${toolDesc}...`;
|
|
@@ -810,18 +840,28 @@ export class ChatCommand {
|
|
|
810
840
|
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
811
841
|
if (spinner.isSpinning)
|
|
812
842
|
spinner.stop();
|
|
813
|
-
|
|
843
|
+
this.writeAgentActivityLine(`${indicator} ${toolName}\n`);
|
|
814
844
|
// Show output for failures, or brief summary for successes — sanitize server paths
|
|
815
845
|
const rawOutput = typeof event.output === 'string' ? event.output.trim() : '';
|
|
816
846
|
const output = this.sanitizeServerPath(rawOutput);
|
|
817
847
|
if (!success && output) {
|
|
818
848
|
const sanitizedError = this.sanitizeServerPath(typeof event.error === 'string' ? event.error : output);
|
|
819
849
|
const lines = sanitizedError.split('\n').slice(0, 4);
|
|
820
|
-
|
|
850
|
+
this.writeAgentActivityLine(chalk.red(` ${lines.join('\n ')}\n`));
|
|
821
851
|
}
|
|
822
852
|
else if (success && output && output.length > 0) {
|
|
823
|
-
|
|
824
|
-
|
|
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
|
+
}
|
|
825
865
|
}
|
|
826
866
|
spinner.start();
|
|
827
867
|
spinner.text = 'Next step...';
|
|
@@ -836,7 +876,10 @@ export class ChatCommand {
|
|
|
836
876
|
const iterText = this.sanitizeServerPath(event.content || '');
|
|
837
877
|
if (spinner.isSpinning)
|
|
838
878
|
spinner.stop();
|
|
839
|
-
|
|
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`));
|
|
840
883
|
spinner.start();
|
|
841
884
|
spinner.text = 'Analyzing...';
|
|
842
885
|
return;
|
|
@@ -2683,39 +2726,6 @@ export class ChatCommand {
|
|
|
2683
2726
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
2684
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}` : '.'}`));
|
|
2685
2728
|
}
|
|
2686
|
-
if (this.v3StreamingStarted) {
|
|
2687
|
-
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
2688
|
-
if (!this.jsonOutput) {
|
|
2689
|
-
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
else if (answerContent) {
|
|
2693
|
-
if (!this.directPromptMode) {
|
|
2694
|
-
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2695
|
-
}
|
|
2696
|
-
console.log('');
|
|
2697
|
-
console.log(chalk.bold('Answer:'));
|
|
2698
|
-
console.log(answerContent);
|
|
2699
|
-
}
|
|
2700
|
-
else if (response.content) {
|
|
2701
|
-
if (!this.directPromptMode) {
|
|
2702
|
-
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2703
|
-
}
|
|
2704
|
-
console.log(response.content);
|
|
2705
|
-
}
|
|
2706
|
-
else if (!requiresWorkspaceChanges) {
|
|
2707
|
-
if (!this.directPromptMode) {
|
|
2708
|
-
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2709
|
-
}
|
|
2710
|
-
console.log(chalk.yellow('The agent finished without producing an answer to your question.'));
|
|
2711
|
-
console.log(chalk.gray('Try /retry or ask again with more detail (e.g. file name or feature).'));
|
|
2712
|
-
}
|
|
2713
|
-
else {
|
|
2714
|
-
if (!this.directPromptMode) {
|
|
2715
|
-
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2716
|
-
}
|
|
2717
|
-
console.log('V3 agent workflow completed.');
|
|
2718
|
-
}
|
|
2719
2729
|
if (!this.jsonOutput && previewGate?.required) {
|
|
2720
2730
|
if (previewGate.passed) {
|
|
2721
2731
|
console.log(chalk.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
@@ -2743,8 +2753,43 @@ export class ChatCommand {
|
|
|
2743
2753
|
// executor errors and the spinner needs to clearly say "failed", not "✓".
|
|
2744
2754
|
let selfHealStatus = null;
|
|
2745
2755
|
let selfHealTool = null;
|
|
2756
|
+
if (isToolEvidenceStubAnswer(answerContent)) {
|
|
2757
|
+
liveOutcome.answerContent = '';
|
|
2758
|
+
}
|
|
2746
2759
|
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2747
|
-
|
|
2760
|
+
let executorSucceeded = runEvaluation.executorSucceeded;
|
|
2761
|
+
let finalAnswer = answerContent;
|
|
2762
|
+
const needsAnalysisRescue = !requiresWorkspaceChanges && !this.jsonOutput
|
|
2763
|
+
&& (!executorSucceeded || !isSubstantiveAgentAnswer(finalAnswer));
|
|
2764
|
+
if (needsAnalysisRescue) {
|
|
2765
|
+
const rescued = await this.tryAnalysisLocalRescue(prompt, workspacePath);
|
|
2766
|
+
if (rescued) {
|
|
2767
|
+
watcher?.stop();
|
|
2768
|
+
return true;
|
|
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
|
+
}
|
|
2792
|
+
}
|
|
2748
2793
|
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
|
|
2749
2794
|
const clientToolErrors = this.api.getClientToolErrors();
|
|
2750
2795
|
const transportErrors = this.api.getLastChatTransportErrors();
|
|
@@ -2839,7 +2884,7 @@ export class ChatCommand {
|
|
|
2839
2884
|
clientToolErrors: this.api.getClientToolErrors(),
|
|
2840
2885
|
transportErrors: this.api.getLastChatTransportErrors(),
|
|
2841
2886
|
workspacePath: workspacePath || null,
|
|
2842
|
-
workspaceSyncIssue: (!executorSucceeded && workspaceHasOutput && changedFileCount === 0)
|
|
2887
|
+
workspaceSyncIssue: (!executorSucceeded && requiresWorkspaceChanges && workspaceHasOutput && changedFileCount === 0)
|
|
2843
2888
|
? 'Local workspace has files but the agent did not confirm tracked changes.'
|
|
2844
2889
|
: null,
|
|
2845
2890
|
finishedAt: Date.now(),
|
|
@@ -3352,7 +3397,7 @@ export class ChatCommand {
|
|
|
3352
3397
|
: `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
|
|
3353
3398
|
console.log(' ' + chalk.white(taskLine + '.'));
|
|
3354
3399
|
}
|
|
3355
|
-
else if (outcome.answerContent) {
|
|
3400
|
+
else if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
|
|
3356
3401
|
const preview = outcome.answerContent.length > 160
|
|
3357
3402
|
? `${outcome.answerContent.slice(0, 160)}…`
|
|
3358
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 {
|
|
@@ -2745,9 +2745,10 @@ menu {
|
|
|
2745
2745
|
.map((entry) => `${entry.isDirectory() ? '[DIR] ' : ''}${entry.name}`);
|
|
2746
2746
|
return { success: true, output: entries.join('\n') || '(empty)' };
|
|
2747
2747
|
}
|
|
2748
|
-
if (name === 'glob' || name === 'search_files') {
|
|
2748
|
+
if (name === 'glob' || name === 'search_files' || name === 'grep') {
|
|
2749
2749
|
const pattern = String(args.pattern || args.query || '').toLowerCase();
|
|
2750
2750
|
const files = [];
|
|
2751
|
+
const matches = [];
|
|
2751
2752
|
const walk = (dir) => {
|
|
2752
2753
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2753
2754
|
if (['node_modules', '.git'].includes(entry.name))
|
|
@@ -2758,18 +2759,30 @@ menu {
|
|
|
2758
2759
|
continue;
|
|
2759
2760
|
}
|
|
2760
2761
|
const relative = path.relative(rootPath, absolute).replace(/\\/g, '/');
|
|
2761
|
-
if (
|
|
2762
|
-
|
|
2763
|
-
}
|
|
2764
|
-
else if (name === 'search_files') {
|
|
2765
|
-
const content = fs.readFileSync(absolute, 'utf8');
|
|
2766
|
-
if (content.toLowerCase().includes(pattern))
|
|
2762
|
+
if (name === 'glob' && pattern) {
|
|
2763
|
+
if (!pattern || relative.toLowerCase().includes(pattern.replace(/\*/g, ''))) {
|
|
2767
2764
|
files.push(relative);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
else if (pattern) {
|
|
2768
|
+
try {
|
|
2769
|
+
const content = fs.readFileSync(absolute, 'utf8');
|
|
2770
|
+
if (relative.toLowerCase().includes(pattern.replace(/\*/g, '')) || content.toLowerCase().includes(pattern)) {
|
|
2771
|
+
matches.push(`${relative}: ${content.split('\n').find((line) => line.toLowerCase().includes(pattern))?.trim() || '(match)'}`);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
catch {
|
|
2775
|
+
// skip unreadable/binary files
|
|
2776
|
+
}
|
|
2768
2777
|
}
|
|
2769
2778
|
}
|
|
2770
2779
|
};
|
|
2771
|
-
|
|
2772
|
-
|
|
2780
|
+
const searchRoot = fs.statSync(target.absolutePath).isDirectory() ? target.absolutePath : rootPath;
|
|
2781
|
+
walk(searchRoot);
|
|
2782
|
+
if (name === 'glob') {
|
|
2783
|
+
return { success: true, output: files.slice(0, 200).join('\n') || '(no matches)' };
|
|
2784
|
+
}
|
|
2785
|
+
return { success: true, output: matches.slice(0, 100).join('\n') || '(no matches)' };
|
|
2773
2786
|
}
|
|
2774
2787
|
if (name === 'syntax_check') {
|
|
2775
2788
|
const content = fs.readFileSync(target.absolutePath, 'utf8');
|
|
@@ -2805,8 +2818,27 @@ menu {
|
|
|
2805
2818
|
if (!contextId || !callId)
|
|
2806
2819
|
return;
|
|
2807
2820
|
const backendUrl = responseUrl ? new URL(responseUrl).origin : (context.mcpContextBackendUrl || null);
|
|
2821
|
+
const emitStream = (streamEvent) => {
|
|
2822
|
+
if (typeof context.onStreamEvent === 'function') {
|
|
2823
|
+
try {
|
|
2824
|
+
context.onStreamEvent(streamEvent);
|
|
2825
|
+
}
|
|
2826
|
+
catch {
|
|
2827
|
+
// UI callbacks must never break the client tool bridge.
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
emitStream({ type: 'tool_call', name: toolName, tool: toolName, arguments: event.arguments || {} });
|
|
2808
2832
|
try {
|
|
2809
2833
|
const result = await this.executeV3ClientToolRequest(event, context);
|
|
2834
|
+
emitStream({
|
|
2835
|
+
type: 'tool_result',
|
|
2836
|
+
name: toolName,
|
|
2837
|
+
tool: toolName,
|
|
2838
|
+
success: result.success,
|
|
2839
|
+
output: result.output,
|
|
2840
|
+
error: result.error || '',
|
|
2841
|
+
});
|
|
2810
2842
|
try {
|
|
2811
2843
|
await this.submitClientToolResult(contextId, callId, result, backendUrl);
|
|
2812
2844
|
}
|
|
@@ -3370,22 +3402,45 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3370
3402
|
synthesizeAnswerFromV3Events(events) {
|
|
3371
3403
|
const toolResults = [];
|
|
3372
3404
|
const filesRead = [];
|
|
3405
|
+
const readExcerpts = [];
|
|
3373
3406
|
const filesWritten = [];
|
|
3374
3407
|
const assistantFragments = [];
|
|
3408
|
+
const pendingReadPaths = new Map();
|
|
3409
|
+
let readIndex = 0;
|
|
3375
3410
|
for (const event of events) {
|
|
3376
3411
|
if (!event)
|
|
3377
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
|
+
}
|
|
3378
3421
|
if (event.type === 'tool_result' && event.success && typeof event.output === 'string') {
|
|
3379
|
-
const name = event.name || 'unknown_tool';
|
|
3380
|
-
|
|
3381
|
-
|
|
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
|
+
}
|
|
3382
3438
|
}
|
|
3383
3439
|
else if ((name === 'write_file' || name === 'create_file') && typeof event.target === 'string') {
|
|
3384
3440
|
filesWritten.push(sanitizeUserFacingPathText(event.target));
|
|
3385
3441
|
}
|
|
3386
3442
|
else {
|
|
3387
|
-
|
|
3388
|
-
const excerpt = event.output.length > 300 ? event.output.slice(-300) : event.output;
|
|
3443
|
+
const excerpt = output.length > 300 ? output.slice(-300) : output;
|
|
3389
3444
|
toolResults.push(`[${name}] ${sanitizeUserFacingPathText(excerpt)}`);
|
|
3390
3445
|
}
|
|
3391
3446
|
}
|
|
@@ -3410,10 +3465,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3410
3465
|
// Concatenate ALL assistant text fragments in order — keeps full
|
|
3411
3466
|
// multi-turn reasoning instead of only the last fragment.
|
|
3412
3467
|
const fullAssistantText = assistantFragments.join('\n\n').trim();
|
|
3413
|
-
if (fullAssistantText
|
|
3468
|
+
if (isSubstantiveAgentAnswer(fullAssistantText)) {
|
|
3414
3469
|
return fullAssistantText;
|
|
3415
3470
|
}
|
|
3416
|
-
|
|
3471
|
+
if (readExcerpts.length > 0) {
|
|
3472
|
+
return '';
|
|
3473
|
+
}
|
|
3474
|
+
// Otherwise build a summary from tool evidence — but never treat stubs as answers.
|
|
3417
3475
|
const sections = [];
|
|
3418
3476
|
if (filesRead.length > 0) {
|
|
3419
3477
|
sections.push(`Files inspected: ${filesRead.join(', ')}`);
|
|
@@ -3424,9 +3482,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3424
3482
|
if (toolResults.length > 0) {
|
|
3425
3483
|
sections.push(toolResults.slice(-5).join('\n'));
|
|
3426
3484
|
}
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
: '';
|
|
3485
|
+
const synthesized = sections.join('\n\n');
|
|
3486
|
+
return isToolEvidenceStubAnswer(synthesized) ? '' : synthesized;
|
|
3430
3487
|
}
|
|
3431
3488
|
sanitizeV3AgentEventForUser(event) {
|
|
3432
3489
|
const sanitizeValue = (value) => {
|
|
@@ -3646,6 +3703,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3646
3703
|
// Exit stream early on complete — agent is done; server-side teardown
|
|
3647
3704
|
// can hold the connection open for many seconds otherwise.
|
|
3648
3705
|
if (event.type === 'complete') {
|
|
3706
|
+
await Promise.allSettled([...this.pendingV3ClientToolTasks]);
|
|
3649
3707
|
reader.cancel().catch(() => { });
|
|
3650
3708
|
return {
|
|
3651
3709
|
task_id: events.find((entry) => entry && entry.task_id)?.task_id || null,
|