vigthoria-cli 1.10.55 → 1.11.0
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 +59 -34
- package/dist/utils/agentRunOutcome.d.ts +36 -0
- package/dist/utils/agentRunOutcome.js +123 -0
- package/dist/utils/api.d.ts +2 -0
- package/dist/utils/api.js +51 -0
- package/package.json +1 -1
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 { 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.)
|
|
@@ -2372,6 +2378,10 @@ export class ChatCommand {
|
|
|
2372
2378
|
plannerError: null,
|
|
2373
2379
|
executorError: null,
|
|
2374
2380
|
executorFailed: false,
|
|
2381
|
+
analysisToolsUsed: 0,
|
|
2382
|
+
changedFileCount: 0,
|
|
2383
|
+
requiresWorkspaceChanges: false,
|
|
2384
|
+
workspaceHasOutput: false,
|
|
2375
2385
|
};
|
|
2376
2386
|
const parsePlannerSummary = (raw) => {
|
|
2377
2387
|
if (!raw || typeof raw !== 'string')
|
|
@@ -2487,20 +2497,23 @@ export class ChatCommand {
|
|
|
2487
2497
|
}
|
|
2488
2498
|
else if (event.type === 'executor_complete') {
|
|
2489
2499
|
const summary = event.summary || {};
|
|
2490
|
-
|
|
2500
|
+
handleTaskEvent(summary, liveOutcome);
|
|
2491
2501
|
if (summary.status === 'failed') {
|
|
2492
|
-
if (tid)
|
|
2493
|
-
liveOutcome.failedTaskIds.add(String(tid));
|
|
2494
|
-
liveOutcome.executorFailed = true;
|
|
2495
2502
|
const err = typeof summary.error === 'string' ? summary.error.trim() : '';
|
|
2496
2503
|
if (err && !liveOutcome.executorError)
|
|
2497
2504
|
liveOutcome.executorError = err;
|
|
2498
2505
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2506
|
+
}
|
|
2507
|
+
else if (event.type === 'tool_call' || event.type === 'tool_result') {
|
|
2508
|
+
const toolName = String(event.name || event.tool || '').trim();
|
|
2509
|
+
if (toolName)
|
|
2510
|
+
noteAnalysisToolUse(toolName, liveOutcome);
|
|
2511
|
+
}
|
|
2512
|
+
else if (event.type === 'complete') {
|
|
2513
|
+
if (Number(event.discovery_tools_used) > 0) {
|
|
2514
|
+
liveOutcome.analysisToolsUsed = Math.max(liveOutcome.analysisToolsUsed, Number(event.discovery_tools_used));
|
|
2503
2515
|
}
|
|
2516
|
+
taskDisplay.complete(1);
|
|
2504
2517
|
}
|
|
2505
2518
|
else if (event.type === 'executor_error') {
|
|
2506
2519
|
const msg = typeof event.error === 'string' ? event.error : '';
|
|
@@ -2520,9 +2533,6 @@ export class ChatCommand {
|
|
|
2520
2533
|
parsePlannerSummary(msg);
|
|
2521
2534
|
}
|
|
2522
2535
|
}
|
|
2523
|
-
else if (event.type === 'complete') {
|
|
2524
|
-
taskDisplay.complete(1);
|
|
2525
|
-
}
|
|
2526
2536
|
if (spinner)
|
|
2527
2537
|
this.updateV3AgentSpinner(spinner, event);
|
|
2528
2538
|
},
|
|
@@ -2539,6 +2549,9 @@ export class ChatCommand {
|
|
|
2539
2549
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
2540
2550
|
const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
|
|
2541
2551
|
const requiresWorkspaceChanges = this.taskRequiresWorkspaceChanges(prompt);
|
|
2552
|
+
liveOutcome.changedFileCount = changedFileCount;
|
|
2553
|
+
liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
|
|
2554
|
+
liveOutcome.workspaceHasOutput = workspaceHasOutput;
|
|
2542
2555
|
const success = previewGate?.required === true
|
|
2543
2556
|
? (previewGate?.passed === true || workspaceHasOutput)
|
|
2544
2557
|
: true;
|
|
@@ -2634,10 +2647,8 @@ export class ChatCommand {
|
|
|
2634
2647
|
// executor errors and the spinner needs to clearly say "failed", not "✓".
|
|
2635
2648
|
let selfHealStatus = null;
|
|
2636
2649
|
let selfHealTool = null;
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
&& !liveOutcome.executorError
|
|
2640
|
-
&& (requiresWorkspaceChanges ? (workspaceHasOutput && changedFileCount > 0) : true);
|
|
2650
|
+
const runEvaluation = evaluateExecutorSuccess(liveOutcome);
|
|
2651
|
+
const executorSucceeded = runEvaluation.executorSucceeded;
|
|
2641
2652
|
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
|
|
2642
2653
|
const clientToolErrors = this.api.getClientToolErrors();
|
|
2643
2654
|
const transportErrors = this.api.getLastChatTransportErrors();
|
|
@@ -2734,7 +2745,7 @@ export class ChatCommand {
|
|
|
2734
2745
|
finishedAt: Date.now(),
|
|
2735
2746
|
};
|
|
2736
2747
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
2737
|
-
this.printAgentRunSummary(this.lastAgentRunOutcome,
|
|
2748
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, runEvaluation, changedFileCount);
|
|
2738
2749
|
}
|
|
2739
2750
|
this.messages.push({ role: 'assistant', content: response.content || 'V3 agent workflow completed.' });
|
|
2740
2751
|
watcher?.stop();
|
|
@@ -2809,7 +2820,19 @@ export class ChatCommand {
|
|
|
2809
2820
|
}, null, 2));
|
|
2810
2821
|
}
|
|
2811
2822
|
else if (!this.directPromptMode) {
|
|
2812
|
-
|
|
2823
|
+
const failedEval = evaluateExecutorSuccess({
|
|
2824
|
+
executorFailed: true,
|
|
2825
|
+
plannerError: liveOutcome.plannerError,
|
|
2826
|
+
executorError: liveOutcome.executorError || safeDetail,
|
|
2827
|
+
tasksTotal: liveOutcome.tasksTotal,
|
|
2828
|
+
tasksSucceeded: liveOutcome.tasksSucceeded,
|
|
2829
|
+
changedFileCount: 0,
|
|
2830
|
+
requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(prompt),
|
|
2831
|
+
workspaceHasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
|
|
2832
|
+
failedTaskIds: liveOutcome.failedTaskIds,
|
|
2833
|
+
analysisToolsUsed: liveOutcome.analysisToolsUsed,
|
|
2834
|
+
});
|
|
2835
|
+
this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
|
|
2813
2836
|
}
|
|
2814
2837
|
return true;
|
|
2815
2838
|
}
|
|
@@ -3106,7 +3129,8 @@ export class ChatCommand {
|
|
|
3106
3129
|
* - User can answer "What do I type next?" without reading 200 scrollback lines.
|
|
3107
3130
|
* - Never leave the spinners (`⟳`, `–`) ambiguous when the prompt returns.
|
|
3108
3131
|
*/
|
|
3109
|
-
printAgentRunSummary(outcome,
|
|
3132
|
+
printAgentRunSummary(outcome, evaluation, changedFileCount) {
|
|
3133
|
+
const executorSucceeded = evaluation.executorSucceeded;
|
|
3110
3134
|
const bar = chalk.gray('─'.repeat(63));
|
|
3111
3135
|
const ok = chalk.green('✓');
|
|
3112
3136
|
const warn = chalk.yellow('⚠');
|
|
@@ -3116,15 +3140,9 @@ export class ChatCommand {
|
|
|
3116
3140
|
const hasTaskInfo = outcome.tasksTotal > 0 || failedList.length > 0 || unfinishedList.length > 0;
|
|
3117
3141
|
console.log('');
|
|
3118
3142
|
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
|
-
}
|
|
3143
|
+
const headlineIcon = evaluation.uiTheme === 'success' ? ok : evaluation.uiTheme === 'warning' ? warn : bad;
|
|
3144
|
+
const headlineText = evaluation.statusHeadline.replace(/^[✓⚠✕]\s*/, '');
|
|
3145
|
+
console.log(`${headlineIcon} ${chalk.bold(headlineText)}${changedFileCount ? chalk.gray(` — ${changedFileCount} file${changedFileCount === 1 ? '' : 's'} changed`) : ''}`);
|
|
3128
3146
|
if (hasTaskInfo) {
|
|
3129
3147
|
const succ = outcome.tasksTotal > 0 ? `${outcome.tasksSucceeded}/${outcome.tasksTotal}` : `${outcome.tasksSucceeded}`;
|
|
3130
3148
|
console.log(chalk.gray(` Tasks completed: ${succ}`));
|
|
@@ -3293,12 +3311,19 @@ export class ChatCommand {
|
|
|
3293
3311
|
return;
|
|
3294
3312
|
}
|
|
3295
3313
|
const o = this.lastAgentRunOutcome;
|
|
3296
|
-
const
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3314
|
+
const statusEval = evaluateExecutorSuccess({
|
|
3315
|
+
executorFailed: o.failedTaskIds.length > 0,
|
|
3316
|
+
plannerError: o.plannerError,
|
|
3317
|
+
executorError: o.executorError,
|
|
3318
|
+
tasksTotal: o.tasksTotal,
|
|
3319
|
+
tasksSucceeded: o.tasksSucceeded,
|
|
3320
|
+
changedFileCount: 0,
|
|
3321
|
+
requiresWorkspaceChanges: this.taskRequiresWorkspaceChanges(o.prompt),
|
|
3322
|
+
workspaceHasOutput: o.hasOutput,
|
|
3323
|
+
failedTaskIds: new Set(o.failedTaskIds),
|
|
3324
|
+
analysisToolsUsed: 0,
|
|
3325
|
+
});
|
|
3326
|
+
this.printAgentRunSummary(o, statusEval, 0);
|
|
3302
3327
|
}
|
|
3303
3328
|
showContext() {
|
|
3304
3329
|
if (!this.currentSession) {
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
analysisToolsUsed: number;
|
|
16
|
+
}
|
|
17
|
+
export interface TaskSummaryEvent {
|
|
18
|
+
status?: string;
|
|
19
|
+
task_id?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RunEvaluation {
|
|
23
|
+
executorSucceeded: boolean;
|
|
24
|
+
statusHeadline: string;
|
|
25
|
+
uiTheme: 'success' | 'warning' | 'error';
|
|
26
|
+
}
|
|
27
|
+
export declare function createLiveOutcome(): LiveOutcome;
|
|
28
|
+
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
29
|
+
export declare function handleTaskEvent(summary: TaskSummaryEvent, liveOutcome: LiveOutcome): void;
|
|
30
|
+
/** Track read/discovery tool usage for analysis-turn accountability. */
|
|
31
|
+
export declare function noteAnalysisToolUse(toolName: string, liveOutcome: LiveOutcome): void;
|
|
32
|
+
/**
|
|
33
|
+
* Compute the real CLI success verdict.
|
|
34
|
+
* When tasks were planned, ALL must succeed — file changes alone never imply victory.
|
|
35
|
+
*/
|
|
36
|
+
export declare function evaluateExecutorSuccess(liveOutcome: LiveOutcome): RunEvaluation;
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
analysisToolsUsed: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Process executor_complete / task summary events from the V3 SSE stream. */
|
|
20
|
+
export function handleTaskEvent(summary, liveOutcome) {
|
|
21
|
+
const tid = summary.task_id || summary.id;
|
|
22
|
+
const status = String(summary.status || '').toLowerCase();
|
|
23
|
+
if (status === 'completed' || status === 'success') {
|
|
24
|
+
if (tid) {
|
|
25
|
+
liveOutcome.failedTaskIds.delete(String(tid));
|
|
26
|
+
}
|
|
27
|
+
liveOutcome.tasksSucceeded += 1;
|
|
28
|
+
}
|
|
29
|
+
else if (status === 'failed' || status === 'blocked') {
|
|
30
|
+
if (tid) {
|
|
31
|
+
liveOutcome.failedTaskIds.add(String(tid));
|
|
32
|
+
}
|
|
33
|
+
liveOutcome.executorFailed = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const READ_DISCOVERY_TOOLS = new Set([
|
|
37
|
+
'read_file',
|
|
38
|
+
'grep',
|
|
39
|
+
'list_directory',
|
|
40
|
+
'glob',
|
|
41
|
+
'search_project',
|
|
42
|
+
'list_dir',
|
|
43
|
+
'dir',
|
|
44
|
+
'read',
|
|
45
|
+
]);
|
|
46
|
+
/** Track read/discovery tool usage for analysis-turn accountability. */
|
|
47
|
+
export function noteAnalysisToolUse(toolName, liveOutcome) {
|
|
48
|
+
const normalized = String(toolName || '').trim().toLowerCase();
|
|
49
|
+
if (READ_DISCOVERY_TOOLS.has(normalized)) {
|
|
50
|
+
liveOutcome.analysisToolsUsed += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Compute the real CLI success verdict.
|
|
55
|
+
* When tasks were planned, ALL must succeed — file changes alone never imply victory.
|
|
56
|
+
*/
|
|
57
|
+
export function evaluateExecutorSuccess(liveOutcome) {
|
|
58
|
+
const hasFatalError = liveOutcome.executorFailed
|
|
59
|
+
|| Boolean(liveOutcome.plannerError)
|
|
60
|
+
|| Boolean(liveOutcome.executorError);
|
|
61
|
+
if (hasFatalError && liveOutcome.tasksTotal === 0) {
|
|
62
|
+
return {
|
|
63
|
+
executorSucceeded: false,
|
|
64
|
+
statusHeadline: '✕ Execution Error',
|
|
65
|
+
uiTheme: 'error',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (liveOutcome.tasksTotal > 0) {
|
|
69
|
+
const allTasksPassed = liveOutcome.tasksSucceeded === liveOutcome.tasksTotal;
|
|
70
|
+
const partialTasksPassed = liveOutcome.tasksSucceeded > 0;
|
|
71
|
+
const failedCount = liveOutcome.failedTaskIds.size;
|
|
72
|
+
if (allTasksPassed && !hasFatalError) {
|
|
73
|
+
return {
|
|
74
|
+
executorSucceeded: true,
|
|
75
|
+
statusHeadline: `✓ Agent run finished (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed)`,
|
|
76
|
+
uiTheme: 'success',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (partialTasksPassed) {
|
|
80
|
+
return {
|
|
81
|
+
executorSucceeded: false,
|
|
82
|
+
statusHeadline: `⚠ Warning: Partial Run (${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed${failedCount ? `, ${failedCount} failed` : ''})`,
|
|
83
|
+
uiTheme: 'warning',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
executorSucceeded: false,
|
|
88
|
+
statusHeadline: `✕ Failed: 0/${liveOutcome.tasksTotal} tasks completed${liveOutcome.changedFileCount > 0 ? ' (stub writes or stalls detected)' : ''}`,
|
|
89
|
+
uiTheme: 'error',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Analysis / verification turns with no planned task graph
|
|
93
|
+
if (!liveOutcome.requiresWorkspaceChanges) {
|
|
94
|
+
const analysisOk = liveOutcome.analysisToolsUsed > 0 || liveOutcome.workspaceHasOutput;
|
|
95
|
+
if (!analysisOk && hasFatalError) {
|
|
96
|
+
return {
|
|
97
|
+
executorSucceeded: false,
|
|
98
|
+
statusHeadline: '✕ Analysis failed — no workspace evidence gathered',
|
|
99
|
+
uiTheme: 'error',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (!analysisOk) {
|
|
103
|
+
return {
|
|
104
|
+
executorSucceeded: false,
|
|
105
|
+
statusHeadline: '⚠ Warning: No read tools confirmed — answer may be incomplete',
|
|
106
|
+
uiTheme: 'warning',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
executorSucceeded: true,
|
|
111
|
+
statusHeadline: '✓ Analysis completed',
|
|
112
|
+
uiTheme: 'success',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const legacySuccess = liveOutcome.workspaceHasOutput && liveOutcome.changedFileCount > 0;
|
|
116
|
+
return {
|
|
117
|
+
executorSucceeded: legacySuccess && !hasFatalError,
|
|
118
|
+
statusHeadline: legacySuccess && !hasFatalError
|
|
119
|
+
? '✓ Agent run finished'
|
|
120
|
+
: '✕ No validated workspace output',
|
|
121
|
+
uiTheme: legacySuccess && !hasFatalError ? 'success' : 'error',
|
|
122
|
+
};
|
|
123
|
+
}
|
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)
|