vigthoria-cli 1.10.55 → 1.11.2
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.js
CHANGED
|
@@ -13,6 +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
17
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
17
18
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
18
19
|
if (!rawValue) {
|
|
@@ -353,7 +354,12 @@ export class ChatCommand {
|
|
|
353
354
|
* question — these should use analysis_only workflow, not full_autonomy.
|
|
354
355
|
*/
|
|
355
356
|
isAnalysisLookupPrompt(prompt) {
|
|
356
|
-
|
|
357
|
+
const trimmed = prompt.trim();
|
|
358
|
+
if (/^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(trimmed)) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
// Mid-conversation accountability / lookup questions (Gideon regression)
|
|
362
|
+
return /\b(where\s+(?:is|are)|what\s+(?:is|are|have|did|was|were)|how\s+(?:do|does|is|are)|what\s+(?:have\s+)?you\s+(?:done|changed|built)|what\s+(?:is|was)\s+(?:your|the)\s+answer)\b/i.test(trimmed);
|
|
357
363
|
}
|
|
358
364
|
extractExplicitLocalPath(prompt) {
|
|
359
365
|
// Try to extract Windows paths (C:\ D:\ etc.)
|
|
@@ -2362,16 +2368,11 @@ export class ChatCommand {
|
|
|
2362
2368
|
}).start();
|
|
2363
2369
|
// Live run telemetry, used both for the final summary block and for /retry, /continue.
|
|
2364
2370
|
const liveOutcome = {
|
|
2365
|
-
|
|
2366
|
-
tasksTotal: 0,
|
|
2367
|
-
failedTaskIds: new Set(),
|
|
2371
|
+
...createLiveOutcome(),
|
|
2368
2372
|
unfinishedTaskIds: new Set(),
|
|
2369
2373
|
qualityScore: null,
|
|
2370
2374
|
qualityMissing: [],
|
|
2371
2375
|
qualityBlockers: [],
|
|
2372
|
-
plannerError: null,
|
|
2373
|
-
executorError: null,
|
|
2374
|
-
executorFailed: false,
|
|
2375
2376
|
};
|
|
2376
2377
|
const parsePlannerSummary = (raw) => {
|
|
2377
2378
|
if (!raw || typeof raw !== 'string')
|
|
@@ -2487,20 +2488,27 @@ export class ChatCommand {
|
|
|
2487
2488
|
}
|
|
2488
2489
|
else if (event.type === 'executor_complete') {
|
|
2489
2490
|
const summary = event.summary || {};
|
|
2490
|
-
|
|
2491
|
-
if (summary.status === 'failed') {
|
|
2492
|
-
if (tid)
|
|
2493
|
-
liveOutcome.failedTaskIds.add(String(tid));
|
|
2494
|
-
liveOutcome.executorFailed = true;
|
|
2491
|
+
handleTaskEvent(summary, liveOutcome);
|
|
2492
|
+
if (summary.status === 'failed' || summary.status === 'stalled_error') {
|
|
2495
2493
|
const err = typeof summary.error === 'string' ? summary.error.trim() : '';
|
|
2496
2494
|
if (err && !liveOutcome.executorError)
|
|
2497
2495
|
liveOutcome.executorError = err;
|
|
2498
2496
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2497
|
+
}
|
|
2498
|
+
else if (event.type === 'tool_call' || event.type === 'tool_result') {
|
|
2499
|
+
const toolName = String(event.name || event.tool || '').trim();
|
|
2500
|
+
if (toolName)
|
|
2501
|
+
noteAnalysisToolUse(toolName, liveOutcome);
|
|
2502
|
+
}
|
|
2503
|
+
else if (event.type === 'complete') {
|
|
2504
|
+
const tt = Number(event.tasks_total);
|
|
2505
|
+
if (Number.isFinite(tt) && tt > 0) {
|
|
2506
|
+
liveOutcome.tasksTotal = tt;
|
|
2507
|
+
}
|
|
2508
|
+
if (Number(event.discovery_tools_used) > 0) {
|
|
2509
|
+
liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
|
|
2503
2510
|
}
|
|
2511
|
+
taskDisplay.complete(1);
|
|
2504
2512
|
}
|
|
2505
2513
|
else if (event.type === 'executor_error') {
|
|
2506
2514
|
const msg = typeof event.error === 'string' ? event.error : '';
|
|
@@ -2520,9 +2528,6 @@ export class ChatCommand {
|
|
|
2520
2528
|
parsePlannerSummary(msg);
|
|
2521
2529
|
}
|
|
2522
2530
|
}
|
|
2523
|
-
else if (event.type === 'complete') {
|
|
2524
|
-
taskDisplay.complete(1);
|
|
2525
|
-
}
|
|
2526
2531
|
if (spinner)
|
|
2527
2532
|
this.updateV3AgentSpinner(spinner, event);
|
|
2528
2533
|
},
|
|
@@ -2539,6 +2544,9 @@ export class ChatCommand {
|
|
|
2539
2544
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2540
2545
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2541
2546
|
const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
2547
|
+
liveOutcome.changedFileCount = changedFileCount;
|
|
2548
|
+
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
2549
|
+
liveOutcome.workspaceHasOutput = workspaceHasOutput;
|
|
2542
2550
|
const success = previewGate?.required === true
|
|
2543
2551
|
? (previewGate?.passed === true || workspaceHasOutput)
|
|
2544
2552
|
: true;
|
|
@@ -2576,20 +2584,7 @@ export class ChatCommand {
|
|
|
2576
2584
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
2577
2585
|
console.log(chalk.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
|
|
2578
2586
|
}
|
|
2579
|
-
if (this.
|
|
2580
|
-
console.log(JSON.stringify({
|
|
2581
|
-
success,
|
|
2582
|
-
mode: 'agent',
|
|
2583
|
-
model: routingPolicy.selectedModel,
|
|
2584
|
-
routingPolicy,
|
|
2585
|
-
taskId: response.taskId || null,
|
|
2586
|
-
contextId: response.contextId || null,
|
|
2587
|
-
partial: response.partial === true,
|
|
2588
|
-
content: response.content || 'V3 agent workflow completed.',
|
|
2589
|
-
metadata: response.metadata || {},
|
|
2590
|
-
}, null, 2));
|
|
2591
|
-
}
|
|
2592
|
-
else if (this.v3StreamingStarted) {
|
|
2587
|
+
if (this.v3StreamingStarted) {
|
|
2593
2588
|
// Content was already streamed to stdout in real-time; skip duplicate print.
|
|
2594
2589
|
if (!this.jsonOutput) {
|
|
2595
2590
|
console.log(chalk.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'}`));
|
|
@@ -2634,10 +2629,8 @@ export class ChatCommand {
|
|
|
2634
2629
|
// executor errors and the spinner needs to clearly say "failed", not "✓".
|
|
2635
2630
|
let selfHealStatus = null;
|
|
2636
2631
|
let selfHealTool = null;
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
&& !liveOutcome.executorError
|
|
2640
|
-
&& (requiresWorkspaceChanges ? (workspaceHasOutput && changedFileCount > 0) : true);
|
|
2632
|
+
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2633
|
+
const executorSucceeded = runEvaluation.executorSucceeded;
|
|
2641
2634
|
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
|
|
2642
2635
|
const clientToolErrors = this.api.getClientToolErrors();
|
|
2643
2636
|
const transportErrors = this.api.getLastChatTransportErrors();
|
|
@@ -2709,6 +2702,9 @@ export class ChatCommand {
|
|
|
2709
2702
|
}
|
|
2710
2703
|
taskDisplay.finalize();
|
|
2711
2704
|
// ────────────────────────────────────────────────────────────────
|
|
2705
|
+
if (!executorSucceeded) {
|
|
2706
|
+
process.exitCode = 1;
|
|
2707
|
+
}
|
|
2712
2708
|
this.lastAgentRunOutcome = {
|
|
2713
2709
|
prompt,
|
|
2714
2710
|
taskId: response.taskId || null,
|
|
@@ -2734,9 +2730,37 @@ export class ChatCommand {
|
|
|
2734
2730
|
finishedAt: Date.now(),
|
|
2735
2731
|
};
|
|
2736
2732
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
2737
|
-
this.printAgentRunSummary(this.lastAgentRunOutcome,
|
|
2733
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
|
|
2738
2734
|
}
|
|
2739
|
-
this.
|
|
2735
|
+
if (this.jsonOutput) {
|
|
2736
|
+
if (!executorSucceeded) {
|
|
2737
|
+
process.exitCode = 1;
|
|
2738
|
+
}
|
|
2739
|
+
console.log(JSON.stringify({
|
|
2740
|
+
success: executorSucceeded,
|
|
2741
|
+
mode: 'agent',
|
|
2742
|
+
model: routingPolicy.selectedModel,
|
|
2743
|
+
routingPolicy,
|
|
2744
|
+
taskId: response.taskId || null,
|
|
2745
|
+
contextId: response.contextId || null,
|
|
2746
|
+
partial: response.partial === true || (!executorSucceeded && liveOutcome.tasksSucceeded > 0),
|
|
2747
|
+
content: response.content || runEvaluation.statusHeadline,
|
|
2748
|
+
statusHeadline: runEvaluation.statusHeadline,
|
|
2749
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2750
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2751
|
+
metadata: {
|
|
2752
|
+
...(response.metadata || {}),
|
|
2753
|
+
outcomeTruth: {
|
|
2754
|
+
executorSucceeded,
|
|
2755
|
+
uiTheme: runEvaluation.uiTheme,
|
|
2756
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2757
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2758
|
+
},
|
|
2759
|
+
previewGate,
|
|
2760
|
+
},
|
|
2761
|
+
}, null, 2));
|
|
2762
|
+
}
|
|
2763
|
+
this.messages.push({ role: 'assistant', content: response.content || runEvaluation.statusHeadline });
|
|
2740
2764
|
watcher?.stop();
|
|
2741
2765
|
return true;
|
|
2742
2766
|
}
|
|
@@ -2765,6 +2789,10 @@ export class ChatCommand {
|
|
|
2765
2789
|
: 'Agent mode is unavailable right now. Please retry shortly or run vigthoria login if the issue persists.';
|
|
2766
2790
|
this.logger.error(errorMessage);
|
|
2767
2791
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
2792
|
+
liveOutcome.streamAborted = true;
|
|
2793
|
+
if (!liveOutcome.executorError) {
|
|
2794
|
+
liveOutcome.executorError = safeDetail || 'Connection aborted';
|
|
2795
|
+
}
|
|
2768
2796
|
// Resolve any half-rendered TaskDisplay spinners before the prompt
|
|
2769
2797
|
// comes back, otherwise the user sees `⟳ Execute tasks` next to `>`.
|
|
2770
2798
|
try {
|
|
@@ -2796,20 +2824,34 @@ export class ChatCommand {
|
|
|
2796
2824
|
workspaceSyncIssue: null,
|
|
2797
2825
|
finishedAt: Date.now(),
|
|
2798
2826
|
};
|
|
2827
|
+
if (!this.jsonOutput) {
|
|
2828
|
+
process.exitCode = 1;
|
|
2829
|
+
}
|
|
2799
2830
|
if (this.jsonOutput) {
|
|
2800
2831
|
process.exitCode = 1;
|
|
2801
2832
|
console.log(JSON.stringify({
|
|
2802
2833
|
success: false,
|
|
2803
2834
|
mode: 'agent',
|
|
2804
2835
|
model: routingPolicy.selectedModel,
|
|
2805
|
-
partial:
|
|
2836
|
+
partial: liveOutcome.tasksSucceeded > 0 && liveOutcome.tasksTotal > 0,
|
|
2806
2837
|
content: '',
|
|
2807
2838
|
error: errorMessage,
|
|
2839
|
+
statusHeadline: evaluateExecutorSuccess(liveOutcome).statusHeadline,
|
|
2840
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2841
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2808
2842
|
metadata: { executionPath: 'v3-agent' },
|
|
2809
2843
|
}, null, 2));
|
|
2810
2844
|
}
|
|
2811
2845
|
else if (!this.directPromptMode) {
|
|
2812
|
-
|
|
2846
|
+
const failedEval = evaluateExecutorSuccess({
|
|
2847
|
+
...liveOutcome,
|
|
2848
|
+
executorFailed: true,
|
|
2849
|
+
executorError: liveOutcome.executorError || safeDetail,
|
|
2850
|
+
changedFileCount: 0,
|
|
2851
|
+
requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(prompt),
|
|
2852
|
+
workspaceHasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
|
|
2853
|
+
});
|
|
2854
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
|
|
2813
2855
|
}
|
|
2814
2856
|
return true;
|
|
2815
2857
|
}
|
|
@@ -3106,7 +3148,8 @@ export class ChatCommand {
|
|
|
3106
3148
|
* - User can answer "What do I type next?" without reading 200 scrollback lines.
|
|
3107
3149
|
* - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
|
|
3108
3150
|
*/
|
|
3109
|
-
printAgentRunSummary(outcome,
|
|
3151
|
+
printAgentRunSummary(outcome, evaluation, changedFileCount) {
|
|
3152
|
+
const executorSucceeded = evaluation.executorSucceeded;
|
|
3110
3153
|
const bar = chalk.gray('─'.repeat(63));
|
|
3111
3154
|
const ok = chalk.green('✓');
|
|
3112
3155
|
const warn = chalk.yellow('⚠');
|
|
@@ -3116,15 +3159,9 @@ export class ChatCommand {
|
|
|
3116
3159
|
const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
|
|
3117
3160
|
console.log('');
|
|
3118
3161
|
console.log(bar);
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
}
|
|
3122
|
-
else if (executorSucceeded) {
|
|
3123
|
-
console.log(`${warn} ${chalk.bold('Agent run finished with warnings')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3124
|
-
}
|
|
3125
|
-
else {
|
|
3126
|
-
console.log(`${bad} ${chalk.bold('Agent run did not complete')}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3127
|
-
}
|
|
3162
|
+
const headlineIcon = evaluation.uiTheme === 'success' ? ok : evaluation.uiTheme === 'warning' ? warn : bad;
|
|
3163
|
+
const headlineText = evaluation.statusHeadline.replace(/^[✓⚠✕]\s*/, '');
|
|
3164
|
+
console.log(`${headlineIcon} ${chalk.bold(headlineText)}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3128
3165
|
if (hasTaskInfo) {
|
|
3129
3166
|
const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
|
|
3130
3167
|
console.log(chalk.gray(` Tasks completed: ${succ}`));
|
|
@@ -3293,12 +3330,18 @@ export class ChatCommand {
|
|
|
3293
3330
|
return;
|
|
3294
3331
|
}
|
|
3295
3332
|
const o = this.lastAgentRunOutcome;
|
|
3296
|
-
const
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3333
|
+
const statusEval = evaluateExecutorSuccess({
|
|
3334
|
+
...createLiveOutcome(),
|
|
3335
|
+
executorFailed: o.failedTaskIds.length > 0,
|
|
3336
|
+
plannerError: o.plannerError,
|
|
3337
|
+
executorError: o.executorError,
|
|
3338
|
+
tasksTotal: o.tasksTotal,
|
|
3339
|
+
tasksSucceeded: o.tasksSucceeded,
|
|
3340
|
+
requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(o.prompt),
|
|
3341
|
+
workspaceHasOutput: o.hasOutput,
|
|
3342
|
+
failedTaskIds: new Set(o.failedTaskIds),
|
|
3343
|
+
});
|
|
3344
|
+
this.printAgentRunSummary(o, statusEval, 0);
|
|
3302
3345
|
}
|
|
3303
3346
|
showContext() {
|
|
3304
3347
|
if (!this.currentSession) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task-and-quality-validated success evaluation for V3 agent runs.
|
|
3
|
+
* Decouples CLI verdict from raw file-change counts (1.11.0+).
|
|
4
|
+
*/
|
|
5
|
+
export interface LiveOutcome {
|
|
6
|
+
executorFailed: boolean;
|
|
7
|
+
plannerError: string | null;
|
|
8
|
+
executorError: string | null;
|
|
9
|
+
tasksTotal: number;
|
|
10
|
+
tasksSucceeded: number;
|
|
11
|
+
changedFileCount: number;
|
|
12
|
+
requiresWorkspaceChanges: boolean;
|
|
13
|
+
workspaceHasOutput: boolean;
|
|
14
|
+
failedTaskIds: Set<string>;
|
|
15
|
+
completedTaskIds: Set<string>;
|
|
16
|
+
analysisToolsUsed: number;
|
|
17
|
+
streamAborted?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface TaskSummaryEvent {
|
|
20
|
+
status?: string;
|
|
21
|
+
task_id?: string;
|
|
22
|
+
id?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RunEvaluation {
|
|
25
|
+
executorSucceeded: boolean;
|
|
26
|
+
statusHeadline: string;
|
|
27
|
+
uiTheme: 'success' | 'warning' | 'error';
|
|
28
|
+
}
|
|
29
|
+
export declare function createLiveOutcome(): LiveOutcome;
|
|
30
|
+
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
31
|
+
export declare function handleTaskEvent(summary: TaskSummaryEvent, liveOutcome: LiveOutcome): void;
|
|
32
|
+
/** Track read/discovery tool usage for analysis-turn accountability. */
|
|
33
|
+
export declare function noteAnalysisToolUse(toolName: string, liveOutcome: LiveOutcome): void;
|
|
34
|
+
/**
|
|
35
|
+
* Compute the real CLI success verdict.
|
|
36
|
+
* When tasks were planned, ALL must succeed — file changes alone never imply victory.
|
|
37
|
+
*/
|
|
38
|
+
export declare function evaluateExecutorSuccess(liveOutcome: LiveOutcome): RunEvaluation;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task-and-quality-validated success evaluation for V3 agent runs.
|
|
3
|
+
* Decouples CLI verdict from raw file-change counts (1.11.0+).
|
|
4
|
+
*/
|
|
5
|
+
export function createLiveOutcome() {
|
|
6
|
+
return {
|
|
7
|
+
executorFailed: false,
|
|
8
|
+
plannerError: null,
|
|
9
|
+
executorError: null,
|
|
10
|
+
tasksTotal: 0,
|
|
11
|
+
tasksSucceeded: 0,
|
|
12
|
+
changedFileCount: 0,
|
|
13
|
+
requiresWorkspaceChanges: false,
|
|
14
|
+
workspaceHasOutput: false,
|
|
15
|
+
failedTaskIds: new Set(),
|
|
16
|
+
completedTaskIds: new Set(),
|
|
17
|
+
analysisToolsUsed: 0,
|
|
18
|
+
streamAborted: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
22
|
+
export function handleTaskEvent(summary, liveOutcome) {
|
|
23
|
+
const tid = summary.task_id || summary.id;
|
|
24
|
+
const status = String(summary.status || '').toLowerCase();
|
|
25
|
+
if (!liveOutcome.completedTaskIds) {
|
|
26
|
+
liveOutcome.completedTaskIds = new Set();
|
|
27
|
+
}
|
|
28
|
+
if (status === 'completed' || status === 'success') {
|
|
29
|
+
if (tid) {
|
|
30
|
+
const id = String(tid);
|
|
31
|
+
if (liveOutcome.completedTaskIds.has(id))
|
|
32
|
+
return;
|
|
33
|
+
liveOutcome.completedTaskIds.add(id);
|
|
34
|
+
liveOutcome.failedTaskIds.delete(id);
|
|
35
|
+
}
|
|
36
|
+
liveOutcome.tasksSucceeded += 1;
|
|
37
|
+
}
|
|
38
|
+
else if (status === 'failed' || status === 'blocked' || status === 'stalled_error') {
|
|
39
|
+
if (tid) {
|
|
40
|
+
liveOutcome.failedTaskIds.add(String(tid));
|
|
41
|
+
}
|
|
42
|
+
liveOutcome.executorFailed = true;
|
|
43
|
+
if (status === 'stalled_error') {
|
|
44
|
+
liveOutcome.streamAborted = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const READ_DISCOVERY_TOOLS = new Set([
|
|
49
|
+
'read_file',
|
|
50
|
+
'grep',
|
|
51
|
+
'list_directory',
|
|
52
|
+
'glob',
|
|
53
|
+
'search_project',
|
|
54
|
+
'list_dir',
|
|
55
|
+
'dir',
|
|
56
|
+
'read',
|
|
57
|
+
]);
|
|
58
|
+
/** Track read/discovery tool usage for analysis-turn accountability. */
|
|
59
|
+
export function noteAnalysisToolUse(toolName, liveOutcome) {
|
|
60
|
+
const normalized = String(toolName || '').trim().toLowerCase();
|
|
61
|
+
if (READ_DISCOVERY_TOOLS.has(normalized)) {
|
|
62
|
+
liveOutcome.analysisToolsUsed += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compute the real CLI success verdict.
|
|
67
|
+
* When tasks were planned, ALL must succeed — file changes alone never imply victory.
|
|
68
|
+
*/
|
|
69
|
+
export function evaluateExecutorSuccess(liveOutcome) {
|
|
70
|
+
const hasFatalError = liveOutcome.executorFailed
|
|
71
|
+
|| Boolean(liveOutcome.plannerError)
|
|
72
|
+
|| Boolean(liveOutcome.executorError)
|
|
73
|
+
|| Boolean(liveOutcome.streamAborted);
|
|
74
|
+
if (liveOutcome.streamAborted && liveOutcome.tasksTotal > 0) {
|
|
75
|
+
const partial = liveOutcome.tasksSucceeded > 0;
|
|
76
|
+
return {
|
|
77
|
+
executorSucceeded: false,
|
|
78
|
+
statusHeadline: partial
|
|
79
|
+
? `⚠ ${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed — connection aborted`
|
|
80
|
+
: `✕ Connection aborted — 0/${liveOutcome.tasksTotal} tasks completed`,
|
|
81
|
+
uiTheme: 'warning',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (hasFatalError && liveOutcome.tasksTotal === 0) {
|
|
85
|
+
return {
|
|
86
|
+
executorSucceeded: false,
|
|
87
|
+
statusHeadline: '✕ Execution Error',
|
|
88
|
+
uiTheme: 'error',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (liveOutcome.tasksTotal > 0) {
|
|
92
|
+
const allTasksPassed = liveOutcome.tasksSucceeded === liveOutcome.tasksTotal;
|
|
93
|
+
const partialTasksPassed = liveOutcome.tasksSucceeded > 0;
|
|
94
|
+
const failedCount = liveOutcome.failedTaskIds.size;
|
|
95
|
+
if (allTasksPassed && !hasFatalError) {
|
|
96
|
+
return {
|
|
97
|
+
executorSucceeded: true,
|
|
98
|
+
statusHeadline: `✓ Agent run finished (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed)`,
|
|
99
|
+
uiTheme: 'success',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (partialTasksPassed) {
|
|
103
|
+
return {
|
|
104
|
+
executorSucceeded: false,
|
|
105
|
+
statusHeadline: `⚠ Warning: Partial Run (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed${failedCount ? `, ${failedCount} failed` : ''})`,
|
|
106
|
+
uiTheme: 'warning',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
executorSucceeded: false,
|
|
111
|
+
statusHeadline: `✕ Failed: 0/${liveOutcome.tasksTotal} tasks completed${liveOutcome.changedFileCount > 0 ? ' (stub writes or stalls detected)' : ''}`,
|
|
112
|
+
uiTheme: 'error',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Analysis / verification turns with no planned task graph
|
|
116
|
+
if (!liveOutcome.requiresWorkspaceChanges) {
|
|
117
|
+
const analysisOk = liveOutcome.analysisToolsUsed > 0 || liveOutcome.workspaceHasOutput;
|
|
118
|
+
if (!analysisOk && hasFatalError) {
|
|
119
|
+
return {
|
|
120
|
+
executorSucceeded: false,
|
|
121
|
+
statusHeadline: '✕ Analysis failed — no workspace evidence gathered',
|
|
122
|
+
uiTheme: 'error',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (!analysisOk) {
|
|
126
|
+
return {
|
|
127
|
+
executorSucceeded: false,
|
|
128
|
+
statusHeadline: '⚠ Warning: No read tools confirmed — answer may be incomplete',
|
|
129
|
+
uiTheme: 'warning',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
executorSucceeded: true,
|
|
134
|
+
statusHeadline: '✓ Analysis completed',
|
|
135
|
+
uiTheme: 'success',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const legacySuccess = liveOutcome.workspaceHasOutput && liveOutcome.changedFileCount > 0;
|
|
139
|
+
return {
|
|
140
|
+
executorSucceeded: legacySuccess && !hasFatalError,
|
|
141
|
+
statusHeadline: legacySuccess && !hasFatalError
|
|
142
|
+
? '✓ Agent run finished'
|
|
143
|
+
: '✕ No validated workspace output',
|
|
144
|
+
uiTheme: legacySuccess && !hasFatalError ? 'success' : 'error',
|
|
145
|
+
};
|
|
146
|
+
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -334,6 +334,8 @@ export declare class APIClient {
|
|
|
334
334
|
* 5. Drop readmeExcerpt
|
|
335
335
|
*/
|
|
336
336
|
private compactV3Context;
|
|
337
|
+
/** Remove duplicate functionIndex entries that poison model context after compaction. */
|
|
338
|
+
private dedupeBrainPayload;
|
|
337
339
|
buildMinimalV3AgentContext(context?: Record<string, any>): string;
|
|
338
340
|
private extractEmergencyAppName;
|
|
339
341
|
private materializeEmergencySaaSWorkspace;
|
package/dist/utils/api.js
CHANGED
|
@@ -1513,6 +1513,12 @@ export class APIClient {
|
|
|
1513
1513
|
*/
|
|
1514
1514
|
compactV3Context(payload) {
|
|
1515
1515
|
const LIMIT = APIClient.V3_CONTEXT_CHAR_LIMIT;
|
|
1516
|
+
if (payload.vigthoriaBrain) {
|
|
1517
|
+
payload.vigthoriaBrain = this.dedupeBrainPayload(payload.vigthoriaBrain);
|
|
1518
|
+
}
|
|
1519
|
+
if (payload.vigthoria_brain) {
|
|
1520
|
+
payload.vigthoria_brain = payload.vigthoriaBrain || this.dedupeBrainPayload(payload.vigthoria_brain);
|
|
1521
|
+
}
|
|
1516
1522
|
let json = JSON.stringify(payload);
|
|
1517
1523
|
if (json.length <= LIMIT)
|
|
1518
1524
|
return json;
|
|
@@ -1586,6 +1592,51 @@ export class APIClient {
|
|
|
1586
1592
|
}
|
|
1587
1593
|
return json;
|
|
1588
1594
|
}
|
|
1595
|
+
/** Remove duplicate functionIndex entries that poison model context after compaction. */
|
|
1596
|
+
dedupeBrainPayload(brain) {
|
|
1597
|
+
if (!brain || typeof brain !== 'object') {
|
|
1598
|
+
return brain;
|
|
1599
|
+
}
|
|
1600
|
+
const copy = { ...brain };
|
|
1601
|
+
const dedupeIndexEntries = (entries) => {
|
|
1602
|
+
const seen = new Set();
|
|
1603
|
+
const unique = [];
|
|
1604
|
+
for (const entry of entries) {
|
|
1605
|
+
if (!entry || typeof entry !== 'object')
|
|
1606
|
+
continue;
|
|
1607
|
+
const file = String(entry.file || '')
|
|
1608
|
+
.replace(/\\/g, '/')
|
|
1609
|
+
.replace(/^home\/user\//i, '')
|
|
1610
|
+
.toLowerCase();
|
|
1611
|
+
const name = String(entry.name || '');
|
|
1612
|
+
const line = String(entry.line ?? '');
|
|
1613
|
+
const key = `${name}::${file}::${line}`;
|
|
1614
|
+
if (seen.has(key))
|
|
1615
|
+
continue;
|
|
1616
|
+
seen.add(key);
|
|
1617
|
+
unique.push(entry);
|
|
1618
|
+
}
|
|
1619
|
+
return unique;
|
|
1620
|
+
};
|
|
1621
|
+
if (copy.functionIndex && typeof copy.functionIndex === 'object') {
|
|
1622
|
+
const deduped = {};
|
|
1623
|
+
for (const [symbol, entries] of Object.entries(copy.functionIndex)) {
|
|
1624
|
+
deduped[symbol] = Array.isArray(entries) ? dedupeIndexEntries(entries) : [];
|
|
1625
|
+
}
|
|
1626
|
+
copy.functionIndex = deduped;
|
|
1627
|
+
}
|
|
1628
|
+
if (copy.files && typeof copy.files === 'object') {
|
|
1629
|
+
const normalized = {};
|
|
1630
|
+
for (const [filePath, meta] of Object.entries(copy.files)) {
|
|
1631
|
+
const norm = String(filePath).replace(/\\/g, '/').replace(/^home\/user\//i, '');
|
|
1632
|
+
if (!(norm in normalized)) {
|
|
1633
|
+
normalized[norm] = meta;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
copy.files = normalized;
|
|
1637
|
+
}
|
|
1638
|
+
return copy;
|
|
1639
|
+
}
|
|
1589
1640
|
buildMinimalV3AgentContext(context = {}) {
|
|
1590
1641
|
const resolvedContext = this.ensureExecutionContext(context);
|
|
1591
1642
|
const targetPath = this.resolveAgentTargetPath(resolvedContext)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigthoria-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"start": "node dist/index.js",
|
|
37
37
|
"dev": "ts-node src/index.ts",
|
|
38
38
|
"test": "npm run test:cli",
|
|
39
|
-
"test:
|
|
39
|
+
"test:agent:outcome": "npm run build && node scripts/test-agent-run-outcome.js",
|
|
40
|
+
"test:cli": "npm run build && npm run precheck:services && node scripts/test-cli-suite.js && npm run test:agent:outcome",
|
|
40
41
|
"test:regression": "npm run build && node scripts/test-regression-1.6.22.js",
|
|
41
42
|
"test:agent:smoke": "npm run build && node scripts/test-agent-smoke.js",
|
|
42
43
|
"test:agent:routing": "npm run build && node scripts/test-agent-routing-policy.js",
|