vigthoria-cli 1.11.3 → 1.11.5
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 +112 -12
- package/dist/index.js +141 -90
- package/dist/utils/agentRunOutcome.d.ts +8 -0
- package/dist/utils/agentRunOutcome.js +52 -4
- package/dist/utils/api.js +10 -3
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export declare class ChatCommand {
|
|
|
47
47
|
private retryPromptSignature;
|
|
48
48
|
private retryPromptStreak;
|
|
49
49
|
private v3SuppressThinkingStream;
|
|
50
|
+
private v3StreamedAnswerBuffer;
|
|
50
51
|
private lastAgentRunOutcome;
|
|
51
52
|
private isJwtExpirationError;
|
|
52
53
|
private isNetworkError;
|
|
@@ -149,6 +150,8 @@ export declare class ChatCommand {
|
|
|
149
150
|
private runOperatorDirectAnswer;
|
|
150
151
|
private runSimplePrompt;
|
|
151
152
|
private runAgentTurn;
|
|
153
|
+
private buildLocalLoopLiveOutcome;
|
|
154
|
+
private persistLocalLoopOutcome;
|
|
152
155
|
private runLocalAgentLoop;
|
|
153
156
|
private primeBypassedTargetFileContext;
|
|
154
157
|
private tryDirectSingleFileFlow;
|
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, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
16
|
+
import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, 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) {
|
|
@@ -66,6 +66,7 @@ export class ChatCommand {
|
|
|
66
66
|
retryPromptSignature = null;
|
|
67
67
|
retryPromptStreak = 0;
|
|
68
68
|
v3SuppressThinkingStream = false;
|
|
69
|
+
v3StreamedAnswerBuffer = '';
|
|
69
70
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
70
71
|
lastAgentRunOutcome = null;
|
|
71
72
|
isJwtExpirationError(error) {
|
|
@@ -733,6 +734,7 @@ export class ChatCommand {
|
|
|
733
734
|
spinner.stop();
|
|
734
735
|
}
|
|
735
736
|
process.stdout.write(safeText);
|
|
737
|
+
this.v3StreamedAnswerBuffer += safeText;
|
|
736
738
|
}
|
|
737
739
|
sanitizeV3VisibleStreamText(text) {
|
|
738
740
|
let output = String(text || '');
|
|
@@ -1940,6 +1942,43 @@ export class ChatCommand {
|
|
|
1940
1942
|
}
|
|
1941
1943
|
await this.runLocalAgentLoop(prompt);
|
|
1942
1944
|
}
|
|
1945
|
+
buildLocalLoopLiveOutcome(prompt) {
|
|
1946
|
+
const liveOutcome = createLiveOutcome();
|
|
1947
|
+
liveOutcome.requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
1948
|
+
liveOutcome.analysisToolsUsed = this.agentToolEvidence.discovery;
|
|
1949
|
+
liveOutcome.changedFileCount = this.agentToolEvidence.mutation;
|
|
1950
|
+
liveOutcome.workspaceHasOutput = this.agentToolEvidence.mutation > 0;
|
|
1951
|
+
if (this.agentToolEvidence.searchFailed > 0 && this.agentToolEvidence.discovery === 0) {
|
|
1952
|
+
liveOutcome.executorFailed = true;
|
|
1953
|
+
liveOutcome.executorError = 'Search tools failed before gathering workspace evidence';
|
|
1954
|
+
}
|
|
1955
|
+
return liveOutcome;
|
|
1956
|
+
}
|
|
1957
|
+
persistLocalLoopOutcome(prompt, evaluation, liveOutcome) {
|
|
1958
|
+
this.lastAgentRunOutcome = {
|
|
1959
|
+
prompt,
|
|
1960
|
+
taskId: null,
|
|
1961
|
+
contextId: null,
|
|
1962
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
1963
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
1964
|
+
failedTaskIds: [...liveOutcome.failedTaskIds],
|
|
1965
|
+
unfinishedTaskIds: [],
|
|
1966
|
+
qualityScore: null,
|
|
1967
|
+
qualityMissing: [],
|
|
1968
|
+
qualityBlockers: evaluation.executorSucceeded ? [] : [evaluation.statusHeadline],
|
|
1969
|
+
hasOutput: liveOutcome.workspaceHasOutput,
|
|
1970
|
+
answerContent: liveOutcome.answerContent || null,
|
|
1971
|
+
selfHealStatus: 'skipped',
|
|
1972
|
+
selfHealTool: null,
|
|
1973
|
+
plannerError: null,
|
|
1974
|
+
executorError: liveOutcome.executorError,
|
|
1975
|
+
clientToolErrors: [],
|
|
1976
|
+
transportErrors: [],
|
|
1977
|
+
workspacePath: this.currentProjectPath || null,
|
|
1978
|
+
workspaceSyncIssue: null,
|
|
1979
|
+
finishedAt: Date.now(),
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1943
1982
|
async runLocalAgentLoop(prompt) {
|
|
1944
1983
|
if (!this.tools) {
|
|
1945
1984
|
throw new Error('Agent tools are not initialized.');
|
|
@@ -1975,14 +2014,26 @@ export class ChatCommand {
|
|
|
1975
2014
|
const evidenceSummary = this.synthesizeEvidenceFromHistory();
|
|
1976
2015
|
if (evidenceSummary) {
|
|
1977
2016
|
const fallbackContent = this.sanitizeDirectModeOutput(evidenceSummary);
|
|
2017
|
+
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
2018
|
+
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2019
|
+
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
1978
2020
|
if (this.jsonOutput) {
|
|
2021
|
+
process.exitCode = evaluation.executorSucceeded ? 0 : 1;
|
|
1979
2022
|
console.log(JSON.stringify({
|
|
1980
|
-
success:
|
|
2023
|
+
success: evaluation.executorSucceeded,
|
|
1981
2024
|
mode: 'agent',
|
|
1982
2025
|
model: this.currentModel,
|
|
1983
|
-
partial:
|
|
2026
|
+
partial: !evaluation.executorSucceeded,
|
|
1984
2027
|
content: fallbackContent,
|
|
1985
|
-
|
|
2028
|
+
statusHeadline: evaluation.statusHeadline,
|
|
2029
|
+
metadata: {
|
|
2030
|
+
executionPath: 'local-agent-loop',
|
|
2031
|
+
recovered: true,
|
|
2032
|
+
outcomeTruth: {
|
|
2033
|
+
executorSucceeded: evaluation.executorSucceeded,
|
|
2034
|
+
uiTheme: evaluation.uiTheme,
|
|
2035
|
+
},
|
|
2036
|
+
},
|
|
1986
2037
|
}, null, 2));
|
|
1987
2038
|
}
|
|
1988
2039
|
else {
|
|
@@ -2078,15 +2129,26 @@ export class ChatCommand {
|
|
|
2078
2129
|
continue;
|
|
2079
2130
|
}
|
|
2080
2131
|
const finalContent = this.resolveDirectModeCompletion(prompt, visibleText);
|
|
2132
|
+
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
2133
|
+
const evaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2134
|
+
this.persistLocalLoopOutcome(prompt, evaluation, liveOutcome);
|
|
2081
2135
|
if (this.jsonOutput) {
|
|
2136
|
+
process.exitCode = evaluation.executorSucceeded ? 0 : 1;
|
|
2082
2137
|
console.log(JSON.stringify({
|
|
2083
|
-
success:
|
|
2138
|
+
success: evaluation.executorSucceeded,
|
|
2084
2139
|
mode: 'agent',
|
|
2085
2140
|
model: this.currentModel,
|
|
2086
|
-
partial:
|
|
2141
|
+
partial: !evaluation.executorSucceeded,
|
|
2087
2142
|
content: finalContent,
|
|
2143
|
+
statusHeadline: evaluation.statusHeadline,
|
|
2088
2144
|
metadata: {
|
|
2089
2145
|
executionPath: 'local-agent-loop',
|
|
2146
|
+
outcomeTruth: {
|
|
2147
|
+
executorSucceeded: evaluation.executorSucceeded,
|
|
2148
|
+
uiTheme: evaluation.uiTheme,
|
|
2149
|
+
analysisToolsUsed: liveOutcome.analysisToolsUsed,
|
|
2150
|
+
changedFileCount: liveOutcome.changedFileCount,
|
|
2151
|
+
},
|
|
2090
2152
|
},
|
|
2091
2153
|
}, null, 2));
|
|
2092
2154
|
}
|
|
@@ -2131,6 +2193,13 @@ export class ChatCommand {
|
|
|
2131
2193
|
return;
|
|
2132
2194
|
}
|
|
2133
2195
|
}
|
|
2196
|
+
const liveOutcome = this.buildLocalLoopLiveOutcome(prompt);
|
|
2197
|
+
const exhaustedEval = evaluateExecutorSuccess({
|
|
2198
|
+
...liveOutcome,
|
|
2199
|
+
executorFailed: true,
|
|
2200
|
+
executorError: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2201
|
+
});
|
|
2202
|
+
this.persistLocalLoopOutcome(prompt, exhaustedEval, liveOutcome);
|
|
2134
2203
|
if (this.jsonOutput) {
|
|
2135
2204
|
process.exitCode = 1;
|
|
2136
2205
|
console.log(JSON.stringify({
|
|
@@ -2140,8 +2209,13 @@ export class ChatCommand {
|
|
|
2140
2209
|
partial: true,
|
|
2141
2210
|
content: 'Task complete.',
|
|
2142
2211
|
error: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
2212
|
+
statusHeadline: exhaustedEval.statusHeadline,
|
|
2143
2213
|
metadata: {
|
|
2144
2214
|
executionPath: 'local-agent-loop',
|
|
2215
|
+
outcomeTruth: {
|
|
2216
|
+
executorSucceeded: false,
|
|
2217
|
+
uiTheme: exhaustedEval.uiTheme,
|
|
2218
|
+
},
|
|
2145
2219
|
},
|
|
2146
2220
|
}, null, 2));
|
|
2147
2221
|
}
|
|
@@ -2382,6 +2456,7 @@ export class ChatCommand {
|
|
|
2382
2456
|
this.v3ToolCallCount = 0;
|
|
2383
2457
|
this.v3LastActivity = Date.now();
|
|
2384
2458
|
this.v3StreamingStarted = false;
|
|
2459
|
+
this.v3StreamedAnswerBuffer = '';
|
|
2385
2460
|
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], !this.jsonOutput);
|
|
2386
2461
|
taskDisplay.start(0);
|
|
2387
2462
|
const spinner = this.jsonOutput ? null : createSpinner({
|
|
@@ -2566,9 +2641,11 @@ export class ChatCommand {
|
|
|
2566
2641
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2567
2642
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2568
2643
|
const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
2644
|
+
const answerContent = normalizeAgentAnswerContent(response.content, this.v3StreamedAnswerBuffer);
|
|
2569
2645
|
liveOutcome.changedFileCount = changedFileCount;
|
|
2570
2646
|
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
2571
|
-
liveOutcome.workspaceHasOutput = workspaceHasOutput;
|
|
2647
|
+
liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
|
|
2648
|
+
liveOutcome.answerContent = answerContent;
|
|
2572
2649
|
const success = previewGate?.required === true
|
|
2573
2650
|
? (previewGate?.passed === true || workspaceHasOutput)
|
|
2574
2651
|
: true;
|
|
@@ -2612,12 +2689,27 @@ export class ChatCommand {
|
|
|
2612
2689
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2613
2690
|
}
|
|
2614
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
|
+
}
|
|
2615
2700
|
else if (response.content) {
|
|
2616
2701
|
if (!this.directPromptMode) {
|
|
2617
2702
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
2618
2703
|
}
|
|
2619
2704
|
console.log(response.content);
|
|
2620
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
|
+
}
|
|
2621
2713
|
else {
|
|
2622
2714
|
if (!this.directPromptMode) {
|
|
2623
2715
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
@@ -2739,6 +2831,7 @@ export class ChatCommand {
|
|
|
2739
2831
|
qualityMissing: liveOutcome.qualityMissing,
|
|
2740
2832
|
qualityBlockers: liveOutcome.qualityBlockers,
|
|
2741
2833
|
hasOutput: workspaceHasOutput,
|
|
2834
|
+
answerContent: answerContent || null,
|
|
2742
2835
|
selfHealStatus,
|
|
2743
2836
|
selfHealTool,
|
|
2744
2837
|
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
@@ -2766,7 +2859,7 @@ export class ChatCommand {
|
|
|
2766
2859
|
taskId: response.taskId || null,
|
|
2767
2860
|
contextId: response.contextId || null,
|
|
2768
2861
|
partial: response.partial === true || (!executorSucceeded && liveOutcome.tasksSucceeded > 0),
|
|
2769
|
-
content: response.content || runEvaluation.statusHeadline,
|
|
2862
|
+
content: answerContent || response.content || runEvaluation.statusHeadline,
|
|
2770
2863
|
statusHeadline: runEvaluation.statusHeadline,
|
|
2771
2864
|
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2772
2865
|
tasksTotal: liveOutcome.tasksTotal,
|
|
@@ -2782,7 +2875,7 @@ export class ChatCommand {
|
|
|
2782
2875
|
},
|
|
2783
2876
|
}, null, 2));
|
|
2784
2877
|
}
|
|
2785
|
-
this.messages.push({ role: 'assistant', content: response.content || runEvaluation.statusHeadline });
|
|
2878
|
+
this.messages.push({ role: 'assistant', content: answerContent || response.content || runEvaluation.statusHeadline });
|
|
2786
2879
|
watcher?.stop();
|
|
2787
2880
|
return true;
|
|
2788
2881
|
}
|
|
@@ -2836,6 +2929,7 @@ export class ChatCommand {
|
|
|
2836
2929
|
qualityMissing: liveOutcome.qualityMissing,
|
|
2837
2930
|
qualityBlockers: liveOutcome.qualityBlockers,
|
|
2838
2931
|
hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
|
|
2932
|
+
answerContent: null,
|
|
2839
2933
|
selfHealStatus: 'skipped',
|
|
2840
2934
|
selfHealTool: null,
|
|
2841
2935
|
plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
|
|
@@ -3258,14 +3352,20 @@ export class ChatCommand {
|
|
|
3258
3352
|
: `${outcome.tasksSucceeded} task update${outcome.tasksSucceeded === 1 ? '' : 's'} reported`;
|
|
3259
3353
|
console.log(' ' + chalk.white(taskLine + '.'));
|
|
3260
3354
|
}
|
|
3355
|
+
else if (outcome.answerContent) {
|
|
3356
|
+
const preview = outcome.answerContent.length > 160
|
|
3357
|
+
? `${outcome.answerContent.slice(0, 160)}…`
|
|
3358
|
+
: outcome.answerContent;
|
|
3359
|
+
console.log(' ' + chalk.white(`Answer: ${preview}`));
|
|
3360
|
+
}
|
|
3261
3361
|
else if (changedFileCount > 0) {
|
|
3262
|
-
console.log(' ' + chalk.white(
|
|
3362
|
+
console.log(' ' + chalk.white('Workspace changes were produced and are ready for review.'));
|
|
3263
3363
|
}
|
|
3264
|
-
else if (outcome.hasOutput) {
|
|
3364
|
+
else if (outcome.hasOutput && executorSucceeded && changedFileCount === 0) {
|
|
3265
3365
|
console.log(' ' + chalk.white('The assistant returned a final answer for your request.'));
|
|
3266
3366
|
}
|
|
3267
3367
|
else {
|
|
3268
|
-
console.log(' ' + chalk.white('No final workspace output was confirmed.'));
|
|
3368
|
+
console.log(' ' + chalk.white('No final answer or workspace output was confirmed.'));
|
|
3269
3369
|
}
|
|
3270
3370
|
if (outcome.selfHealStatus && outcome.selfHealStatus !== 'skipped') {
|
|
3271
3371
|
console.log(' ' + chalk.white(`Self-check status: ${outcome.selfHealStatus}.`));
|
package/dist/index.js
CHANGED
|
@@ -75,8 +75,41 @@ function getPackageMetadataPaths() {
|
|
|
75
75
|
path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'vigthoria-cli', 'package.json'),
|
|
76
76
|
];
|
|
77
77
|
}
|
|
78
|
+
function resolveRunningPackageJsonPath() {
|
|
79
|
+
const candidates = [];
|
|
80
|
+
const scriptArg = process.argv[1];
|
|
81
|
+
if (scriptArg) {
|
|
82
|
+
try {
|
|
83
|
+
const resolvedScript = fs.realpathSync(scriptArg);
|
|
84
|
+
candidates.push(path.join(path.dirname(resolvedScript), '..', 'package.json'));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
candidates.push(path.join(path.dirname(scriptArg), '..', 'package.json'));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
candidates.push(path.join(__dirname, '..', 'package.json'));
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
try {
|
|
93
|
+
if (!fs.existsSync(candidate))
|
|
94
|
+
continue;
|
|
95
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
96
|
+
if (pkg?.name === 'vigthoria-cli')
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// try next candidate
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
78
105
|
function getVersion() {
|
|
79
106
|
try {
|
|
107
|
+
const runningPackageJson = resolveRunningPackageJsonPath();
|
|
108
|
+
if (runningPackageJson) {
|
|
109
|
+
const pkg = JSON.parse(fs.readFileSync(runningPackageJson, 'utf8'));
|
|
110
|
+
if (pkg.version)
|
|
111
|
+
return pkg.version;
|
|
112
|
+
}
|
|
80
113
|
for (const p of getPackageMetadataPaths()) {
|
|
81
114
|
if (fs.existsSync(p)) {
|
|
82
115
|
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
@@ -149,6 +182,30 @@ function compareVersions(v1, v2) {
|
|
|
149
182
|
}
|
|
150
183
|
return 0;
|
|
151
184
|
}
|
|
185
|
+
function maxVersion(...versions) {
|
|
186
|
+
let best = null;
|
|
187
|
+
for (const version of versions) {
|
|
188
|
+
if (!version)
|
|
189
|
+
continue;
|
|
190
|
+
if (!best || compareVersions(version, best) > 0)
|
|
191
|
+
best = version;
|
|
192
|
+
}
|
|
193
|
+
return best;
|
|
194
|
+
}
|
|
195
|
+
async function fetchNpmLatestVersion() {
|
|
196
|
+
const { execSync } = await import('child_process');
|
|
197
|
+
try {
|
|
198
|
+
const latest = execSync('npm view vigthoria-cli version', {
|
|
199
|
+
encoding: 'utf8',
|
|
200
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
201
|
+
windowsHide: true,
|
|
202
|
+
}).trim();
|
|
203
|
+
return latest || null;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
152
209
|
const VERSION = getVersion();
|
|
153
210
|
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
|
|
154
211
|
function resolveManifestEntry(manifest, channel) {
|
|
@@ -1452,7 +1509,6 @@ Examples:
|
|
|
1452
1509
|
.option('--channel <name>', 'Release channel to use from manifest (default: stable)', 'stable')
|
|
1453
1510
|
.option('--allow-downgrade', 'Allow installing an older version from custom update source')
|
|
1454
1511
|
.action(async (options) => {
|
|
1455
|
-
const { execSync } = await import('child_process');
|
|
1456
1512
|
const updateTarget = typeof options.from === 'string' ? options.from.trim() : '';
|
|
1457
1513
|
const manifestUrl = typeof options.manifest === 'string' ? options.manifest.trim() : VIGTHORIA_DEFAULT_MANIFEST_URL.trim();
|
|
1458
1514
|
const channel = typeof options.channel === 'string' ? options.channel.trim() : 'stable';
|
|
@@ -1484,11 +1540,13 @@ Examples:
|
|
|
1484
1540
|
return;
|
|
1485
1541
|
}
|
|
1486
1542
|
}
|
|
1543
|
+
if (isOfflineMode()) {
|
|
1544
|
+
console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping update check.'));
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const currentVersion = VERSION;
|
|
1548
|
+
let manifestEntry = null;
|
|
1487
1549
|
if (manifestUrl) {
|
|
1488
|
-
if (isOfflineMode()) {
|
|
1489
|
-
console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping manifest update.'));
|
|
1490
|
-
return;
|
|
1491
|
-
}
|
|
1492
1550
|
try {
|
|
1493
1551
|
console.log(chalk.cyan(`Checking manifest channel ${channel}...`));
|
|
1494
1552
|
const response = await axios.get(manifestUrl, {
|
|
@@ -1496,103 +1554,96 @@ Examples:
|
|
|
1496
1554
|
maxRedirects: 5,
|
|
1497
1555
|
validateStatus: (status) => status >= 200 && status < 300,
|
|
1498
1556
|
});
|
|
1499
|
-
|
|
1500
|
-
if (!
|
|
1501
|
-
console.
|
|
1502
|
-
|
|
1503
|
-
process.exitCode = 1;
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
|
-
const currentVersion = VERSION;
|
|
1507
|
-
const comparison = compareVersions(entry.version, currentVersion);
|
|
1508
|
-
if (!allowDowngrade && comparison <= 0) {
|
|
1509
|
-
console.log(chalk.green(`You are running the latest version for channel ${channel} (${currentVersion})`));
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
console.log(chalk.yellow(`Update available from manifest: ${currentVersion} -> ${entry.version} (${channel})`));
|
|
1513
|
-
if (entry.notes) {
|
|
1514
|
-
console.log(chalk.gray(` ${entry.notes}`));
|
|
1515
|
-
}
|
|
1516
|
-
if (options.check) {
|
|
1517
|
-
console.log(chalk.gray(`Install with: vigthoria update --manifest ${manifestUrl} --channel ${channel}`));
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
const tmpFile = path.join(os.tmpdir(), `vigthoria-update-${Date.now()}.tgz`);
|
|
1521
|
-
try {
|
|
1522
|
-
console.log(chalk.cyan('Downloading release package...'));
|
|
1523
|
-
await downloadFile(entry.url, tmpFile);
|
|
1524
|
-
if (entry.sha256) {
|
|
1525
|
-
const expected = entry.sha256.toLowerCase();
|
|
1526
|
-
const actual = sha256File(tmpFile).toLowerCase();
|
|
1527
|
-
if (actual !== expected) {
|
|
1528
|
-
console.error(chalk.red('Release checksum verification failed'));
|
|
1529
|
-
console.error(chalk.red(`Expected: ${expected}`));
|
|
1530
|
-
console.error(chalk.red(`Actual: ${actual}`));
|
|
1531
|
-
process.exitCode = 1;
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
console.log(chalk.green('Checksum verification passed'));
|
|
1535
|
-
}
|
|
1536
|
-
else {
|
|
1537
|
-
console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
|
|
1538
|
-
console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
|
|
1539
|
-
console.log(chalk.gray(' Set --manifest to a trusted source, or supply sha256 in the manifest entry.'));
|
|
1540
|
-
}
|
|
1541
|
-
console.log(chalk.cyan('Installing update...'));
|
|
1542
|
-
await installGlobalPackageWithNpm(tmpFile);
|
|
1543
|
-
console.log(chalk.green(`Updated to version ${entry.version}`));
|
|
1544
|
-
console.log(chalk.gray('Please restart the CLI to use the new version'));
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
finally {
|
|
1548
|
-
if (fs.existsSync(tmpFile)) {
|
|
1549
|
-
try {
|
|
1550
|
-
fs.unlinkSync(tmpFile);
|
|
1551
|
-
}
|
|
1552
|
-
catch { }
|
|
1553
|
-
}
|
|
1557
|
+
manifestEntry = resolveManifestEntry(response.data || {}, channel);
|
|
1558
|
+
if (!manifestEntry?.version || !manifestEntry?.url) {
|
|
1559
|
+
console.log(chalk.yellow(`Manifest has no installable entry for channel ${channel}; will check npm.`));
|
|
1560
|
+
manifestEntry = null;
|
|
1554
1561
|
}
|
|
1555
1562
|
}
|
|
1556
1563
|
catch (error) {
|
|
1557
|
-
console.
|
|
1558
|
-
console.log(chalk.gray('
|
|
1564
|
+
console.log(chalk.yellow(`Manifest check failed: ${error.message}`));
|
|
1565
|
+
console.log(chalk.gray('Continuing with npm registry check...'));
|
|
1559
1566
|
}
|
|
1560
1567
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1568
|
+
console.log(chalk.cyan('Checking npm registry...'));
|
|
1569
|
+
const npmVersion = await fetchNpmLatestVersion();
|
|
1570
|
+
if (!npmVersion) {
|
|
1571
|
+
console.log(chalk.yellow('Could not read latest version from npm registry.'));
|
|
1572
|
+
}
|
|
1573
|
+
const effectiveLatest = maxVersion(manifestEntry?.version, npmVersion);
|
|
1574
|
+
if (!effectiveLatest) {
|
|
1575
|
+
console.error(chalk.red('Unable to determine the latest CLI version from manifest or npm.'));
|
|
1576
|
+
process.exitCode = 1;
|
|
1563
1577
|
return;
|
|
1564
1578
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1579
|
+
if (manifestEntry?.version
|
|
1580
|
+
&& npmVersion
|
|
1581
|
+
&& compareVersions(npmVersion, manifestEntry.version) > 0) {
|
|
1582
|
+
console.log(chalk.yellow(`Release manifest (${manifestEntry.version}) is behind npm (${npmVersion}); npm will be used when needed.`));
|
|
1583
|
+
}
|
|
1584
|
+
if (!allowDowngrade && compareVersions(effectiveLatest, currentVersion) <= 0) {
|
|
1585
|
+
console.log(chalk.green(`You are running the latest version (${currentVersion})`));
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
console.log(chalk.yellow(`Update available: ${currentVersion} -> ${effectiveLatest}`));
|
|
1589
|
+
if (manifestEntry?.notes && compareVersions(manifestEntry.version, effectiveLatest) >= 0) {
|
|
1590
|
+
console.log(chalk.gray(` ${manifestEntry.notes}`));
|
|
1591
|
+
}
|
|
1592
|
+
if (options.check) {
|
|
1593
|
+
console.log(chalk.gray('Run `vigthoria update` to install the update'));
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
const manifestIsAuthoritative = Boolean(manifestEntry
|
|
1597
|
+
&& manifestEntry.url
|
|
1598
|
+
&& compareVersions(manifestEntry.version, effectiveLatest) >= 0
|
|
1599
|
+
&& compareVersions(manifestEntry.version, currentVersion) > 0);
|
|
1600
|
+
if (manifestIsAuthoritative && manifestEntry) {
|
|
1601
|
+
const tmpFile = path.join(os.tmpdir(), `vigthoria-update-${Date.now()}.tgz`);
|
|
1602
|
+
try {
|
|
1603
|
+
console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
|
|
1604
|
+
await downloadFile(manifestEntry.url, tmpFile);
|
|
1605
|
+
if (manifestEntry.sha256) {
|
|
1606
|
+
const expected = manifestEntry.sha256.toLowerCase();
|
|
1607
|
+
const actual = sha256File(tmpFile).toLowerCase();
|
|
1608
|
+
if (actual !== expected) {
|
|
1609
|
+
console.error(chalk.red('Release checksum verification failed'));
|
|
1610
|
+
console.error(chalk.red(`Expected: ${expected}`));
|
|
1611
|
+
console.error(chalk.red(`Actual: ${actual}`));
|
|
1612
|
+
process.exitCode = 1;
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
console.log(chalk.green('Checksum verification passed'));
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
|
|
1619
|
+
console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
|
|
1620
|
+
}
|
|
1621
|
+
console.log(chalk.cyan('Installing update...'));
|
|
1622
|
+
await installGlobalPackageWithNpm(tmpFile);
|
|
1623
|
+
console.log(chalk.green(`Updated to version ${manifestEntry.version}`));
|
|
1624
|
+
console.log(chalk.gray('Please restart the CLI to use the new version'));
|
|
1578
1625
|
return;
|
|
1579
1626
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1627
|
+
finally {
|
|
1628
|
+
if (fs.existsSync(tmpFile)) {
|
|
1629
|
+
try {
|
|
1630
|
+
fs.unlinkSync(tmpFile);
|
|
1631
|
+
}
|
|
1632
|
+
catch { }
|
|
1633
|
+
}
|
|
1584
1634
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1635
|
+
}
|
|
1636
|
+
const npmSpec = npmVersion && compareVersions(npmVersion, currentVersion) > 0
|
|
1637
|
+
? `vigthoria-cli@${npmVersion}`
|
|
1638
|
+
: 'vigthoria-cli@latest';
|
|
1639
|
+
try {
|
|
1640
|
+
console.log(chalk.cyan(`Installing update from npm registry (${npmSpec})...`));
|
|
1641
|
+
await installGlobalPackageWithNpm(npmSpec);
|
|
1642
|
+
console.log(chalk.green(`Updated to version ${effectiveLatest}`));
|
|
1588
1643
|
console.log(chalk.gray('Please restart the CLI to use the new version'));
|
|
1589
1644
|
}
|
|
1590
1645
|
catch (error) {
|
|
1591
|
-
console.error(chalk.red('Failed to
|
|
1592
|
-
if (options.check) {
|
|
1593
|
-
console.log(chalk.gray(`npm registry check failed; fallback install target is ${gitPackageSpec}`));
|
|
1594
|
-
return;
|
|
1595
|
-
}
|
|
1646
|
+
console.error(chalk.red('Failed to install via npm registry:'), error.message);
|
|
1596
1647
|
try {
|
|
1597
1648
|
console.log(chalk.cyan('Attempting git package fallback...'));
|
|
1598
1649
|
await installGlobalPackageWithNpm(gitPackageSpec);
|
|
@@ -1601,7 +1652,7 @@ Examples:
|
|
|
1601
1652
|
}
|
|
1602
1653
|
catch (fallbackError) {
|
|
1603
1654
|
console.error(chalk.red('Fallback update also failed:'), fallbackError.message);
|
|
1604
|
-
console.log(chalk.gray(
|
|
1655
|
+
console.log(chalk.gray(`Try manually: npm install -g ${npmSpec}`));
|
|
1605
1656
|
console.log(chalk.gray(`Or: npm install -g ${gitPackageSpec}`));
|
|
1606
1657
|
process.exitCode = 1;
|
|
1607
1658
|
}
|
|
@@ -14,6 +14,10 @@ export interface LiveOutcome {
|
|
|
14
14
|
failedTaskIds: Set<string>;
|
|
15
15
|
completedTaskIds: Set<string>;
|
|
16
16
|
analysisToolsUsed: number;
|
|
17
|
+
/** read_file / grep — not list_directory-only browsing */
|
|
18
|
+
analysisReadToolsUsed: number;
|
|
19
|
+
/** Final user-facing answer text (stream + response body) */
|
|
20
|
+
answerContent?: string;
|
|
17
21
|
streamAborted?: boolean;
|
|
18
22
|
}
|
|
19
23
|
export interface TaskSummaryEvent {
|
|
@@ -27,6 +31,10 @@ export interface RunEvaluation {
|
|
|
27
31
|
uiTheme: 'success' | 'warning' | 'error';
|
|
28
32
|
}
|
|
29
33
|
export declare function createLiveOutcome(): LiveOutcome;
|
|
34
|
+
/** True when text looks like a real answer, not an executor/system placeholder. */
|
|
35
|
+
export declare function isSubstantiveAgentAnswer(text: string): boolean;
|
|
36
|
+
/** Pick the first substantive answer from response body and/or streamed text. */
|
|
37
|
+
export declare function normalizeAgentAnswerContent(...sources: Array<string | undefined | null>): string;
|
|
30
38
|
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
31
39
|
export declare function handleTaskEvent(summary: TaskSummaryEvent, liveOutcome: LiveOutcome): void;
|
|
32
40
|
/** Track read/discovery tool usage for analysis-turn accountability. */
|
|
@@ -15,9 +15,38 @@ export function createLiveOutcome() {
|
|
|
15
15
|
failedTaskIds: new Set(),
|
|
16
16
|
completedTaskIds: new Set(),
|
|
17
17
|
analysisToolsUsed: 0,
|
|
18
|
+
analysisReadToolsUsed: 0,
|
|
19
|
+
answerContent: '',
|
|
18
20
|
streamAborted: false,
|
|
19
21
|
};
|
|
20
22
|
}
|
|
23
|
+
const EXECUTOR_PLACEHOLDER_RE = /read-only analysis turn|run at least one discovery tool|reviewing attached files and workspace findings/i;
|
|
24
|
+
const LIST_ONLY_DISCOVERY_TOOLS = new Set([
|
|
25
|
+
'list_directory',
|
|
26
|
+
'list_dir',
|
|
27
|
+
'glob',
|
|
28
|
+
'dir',
|
|
29
|
+
]);
|
|
30
|
+
/** True when text looks like a real answer, not an executor/system placeholder. */
|
|
31
|
+
export function isSubstantiveAgentAnswer(text) {
|
|
32
|
+
const trimmed = String(text || '').trim();
|
|
33
|
+
if (trimmed.length < 30)
|
|
34
|
+
return false;
|
|
35
|
+
if (EXECUTOR_PLACEHOLDER_RE.test(trimmed))
|
|
36
|
+
return false;
|
|
37
|
+
if (/^v3 agent workflow completed\.?$/i.test(trimmed))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** Pick the first substantive answer from response body and/or streamed text. */
|
|
42
|
+
export function normalizeAgentAnswerContent(...sources) {
|
|
43
|
+
for (const source of sources) {
|
|
44
|
+
const text = String(source || '').trim();
|
|
45
|
+
if (isSubstantiveAgentAnswer(text))
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
21
50
|
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
22
51
|
export function handleTaskEvent(summary, liveOutcome) {
|
|
23
52
|
const tid = summary.task_id || summary.id;
|
|
@@ -60,6 +89,9 @@ export function noteAnalysisToolUse(toolName, liveOutcome) {
|
|
|
60
89
|
const normalized = String(toolName || '').trim().toLowerCase();
|
|
61
90
|
if (READ_DISCOVERY_TOOLS.has(normalized)) {
|
|
62
91
|
liveOutcome.analysisToolsUsed += 1;
|
|
92
|
+
if (!LIST_ONLY_DISCOVERY_TOOLS.has(normalized)) {
|
|
93
|
+
liveOutcome.analysisReadToolsUsed += 1;
|
|
94
|
+
}
|
|
63
95
|
}
|
|
64
96
|
}
|
|
65
97
|
/**
|
|
@@ -114,21 +146,37 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
114
146
|
}
|
|
115
147
|
// Analysis / verification turns with no planned task graph
|
|
116
148
|
if (!liveOutcome.requiresWorkspaceChanges) {
|
|
117
|
-
const
|
|
118
|
-
|
|
149
|
+
const answerText = normalizeAgentAnswerContent(liveOutcome.answerContent);
|
|
150
|
+
const hasSubstantiveAnswer = isSubstantiveAgentAnswer(answerText);
|
|
151
|
+
const hasDeepRead = liveOutcome.analysisReadToolsUsed > 0;
|
|
152
|
+
if (hasFatalError && !hasSubstantiveAnswer) {
|
|
119
153
|
return {
|
|
120
154
|
executorSucceeded: false,
|
|
121
|
-
statusHeadline: '✕ Analysis failed — no
|
|
155
|
+
statusHeadline: '✕ Analysis failed — no answer produced',
|
|
122
156
|
uiTheme: 'error',
|
|
123
157
|
};
|
|
124
158
|
}
|
|
125
|
-
if (!
|
|
159
|
+
if (!hasSubstantiveAnswer) {
|
|
160
|
+
if (liveOutcome.analysisToolsUsed > 0) {
|
|
161
|
+
return {
|
|
162
|
+
executorSucceeded: false,
|
|
163
|
+
statusHeadline: '⚠ Analysis browsed the workspace but did not answer your question — try /retry',
|
|
164
|
+
uiTheme: 'warning',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
126
167
|
return {
|
|
127
168
|
executorSucceeded: false,
|
|
128
169
|
statusHeadline: '⚠ Warning: No read tools confirmed — answer may be incomplete',
|
|
129
170
|
uiTheme: 'warning',
|
|
130
171
|
};
|
|
131
172
|
}
|
|
173
|
+
if (!hasDeepRead) {
|
|
174
|
+
return {
|
|
175
|
+
executorSucceeded: false,
|
|
176
|
+
statusHeadline: '⚠ Analysis answer was not verified against source files — try /retry',
|
|
177
|
+
uiTheme: 'warning',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
132
180
|
return {
|
|
133
181
|
executorSucceeded: true,
|
|
134
182
|
statusHeadline: '✓ Analysis completed',
|
package/dist/utils/api.js
CHANGED
|
@@ -11,6 +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
15
|
export const VIGTHORIA_HUB_CREDITS_URL = 'https://hub.vigthoria.io/credits';
|
|
15
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.';
|
|
16
17
|
export class CLIError extends Error {
|
|
@@ -3308,13 +3309,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3308
3309
|
formatV3AgentResponse(data) {
|
|
3309
3310
|
const result = data?.result || {};
|
|
3310
3311
|
if (typeof result === 'string') {
|
|
3311
|
-
|
|
3312
|
+
const text = this.sanitizeV3AgentResponseText(result);
|
|
3313
|
+
if (isSubstantiveAgentAnswer(text))
|
|
3314
|
+
return text;
|
|
3312
3315
|
}
|
|
3313
3316
|
if (typeof result?.summary === 'string' && result.summary.trim()) {
|
|
3314
|
-
|
|
3317
|
+
const text = this.sanitizeV3AgentResponseText(result.summary);
|
|
3318
|
+
if (isSubstantiveAgentAnswer(text))
|
|
3319
|
+
return text;
|
|
3315
3320
|
}
|
|
3316
3321
|
if (typeof result?.message === 'string' && result.message.trim()) {
|
|
3317
|
-
|
|
3322
|
+
const text = this.sanitizeV3AgentResponseText(result.message);
|
|
3323
|
+
if (isSubstantiveAgentAnswer(text))
|
|
3324
|
+
return text;
|
|
3318
3325
|
}
|
|
3319
3326
|
if (Array.isArray(data?.events)) {
|
|
3320
3327
|
const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string' && event.summary.trim());
|