minovative-mind-cli 2.6.3 → 2.6.4
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/services/agent-tools.js +13 -2
- package/dist/services/agent.d.ts +3 -13
- package/dist/services/agent.js +82 -34
- package/dist/services/ai.d.ts +4 -0
- package/dist/services/ai.js +36 -12
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1413,12 +1413,23 @@ const currentTasksByAgent = new Map();
|
|
|
1413
1413
|
* @returns A promise resolving to a {@link ToolResult}.
|
|
1414
1414
|
*/
|
|
1415
1415
|
export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
1416
|
+
// Normalize common LLM tool alias hallucinations to registered tool names
|
|
1417
|
+
let normalizedToolName = toolName;
|
|
1418
|
+
if (normalizedToolName === 'search' || normalizedToolName === 'web_search') {
|
|
1419
|
+
normalizedToolName = (args.query || args.searchQuery || args.url) ? 'perform_web_search' : 'grep_search';
|
|
1420
|
+
}
|
|
1421
|
+
else if (normalizedToolName === 'grep' || normalizedToolName === 'search_codebase') {
|
|
1422
|
+
normalizedToolName = 'grep_search';
|
|
1423
|
+
}
|
|
1424
|
+
else if (normalizedToolName === 'finish_investigation') {
|
|
1425
|
+
normalizedToolName = 'finish_task';
|
|
1426
|
+
}
|
|
1416
1427
|
// ─── Multi-Workspace Path Resolution ─────────────────────────────
|
|
1417
1428
|
// Intercept @alias/ prefixed paths and swap workspaceRoot + relative path
|
|
1418
1429
|
// before dispatching to the underlying tool functions (which remain unchanged).
|
|
1419
|
-
const { effectiveRoot, resolvedArgs } = resolveWorkspaceArgs(workspaceRoot,
|
|
1430
|
+
const { effectiveRoot, resolvedArgs } = resolveWorkspaceArgs(workspaceRoot, normalizedToolName, args);
|
|
1420
1431
|
let result;
|
|
1421
|
-
switch (
|
|
1432
|
+
switch (normalizedToolName) {
|
|
1422
1433
|
case 'perform_web_search': {
|
|
1423
1434
|
const { createWebSearchAgentSession } = await import('./ai.js');
|
|
1424
1435
|
const webSession = createWebSearchAgentSession();
|
package/dist/services/agent.d.ts
CHANGED
|
@@ -112,24 +112,14 @@ export declare function executeSingleTurn(workspaceRoot: string, userInput: stri
|
|
|
112
112
|
} | void>;
|
|
113
113
|
/**
|
|
114
114
|
* Raw chat history entry count threshold to trigger chat history summarization.
|
|
115
|
-
*
|
|
116
|
-
*/
|
|
117
|
-
export declare const HISTORY_SUMMARIZATION_THRESHOLD = 12;
|
|
118
|
-
/**
|
|
119
|
-
* Checks and summarizes older chat history entries if the active session's history
|
|
120
|
-
* exceeds HISTORY_SUMMARIZATION_THRESHOLD entries.
|
|
121
|
-
*
|
|
122
|
-
* Preserves the most recent 4 entries (2 turns) intact for conversational continuity,
|
|
123
|
-
* replacing all preceding turns with a single summarized pair in the chat history.
|
|
124
|
-
*
|
|
125
|
-
* @param chat - The active ProxyChatSession instance.
|
|
126
|
-
* @returns True if history was summarized and updated; false otherwise.
|
|
115
|
+
* 50 entries = 25 conversational user/model turns.
|
|
127
116
|
*/
|
|
117
|
+
export declare const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
128
118
|
/**
|
|
129
119
|
* Summarizes older chat history entries if the active session's history
|
|
130
120
|
* exceeds {@link HISTORY_SUMMARIZATION_THRESHOLD} entries.
|
|
131
121
|
*
|
|
132
|
-
* Preserves the most recent
|
|
122
|
+
* Preserves the most recent 20 entries (10 turns) intact for conversational continuity,
|
|
133
123
|
* replacing all preceding turns with a single summarized pair in the chat history.
|
|
134
124
|
*
|
|
135
125
|
* @param chat - The active ProxyChatSession instance.
|
package/dist/services/agent.js
CHANGED
|
@@ -152,7 +152,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
152
152
|
process.stdout.write('\n\n\n\x1b[3A');
|
|
153
153
|
console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
|
|
154
154
|
while (true) {
|
|
155
|
-
if (chat.
|
|
155
|
+
if (chat.getFullHistory().length === 0 && chatSessionState.title !== '') {
|
|
156
156
|
chatSessionState.id = crypto.randomUUID();
|
|
157
157
|
chatSessionState.title = '';
|
|
158
158
|
chatSessionState.totalTokens = 0;
|
|
@@ -466,19 +466,47 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
466
466
|
const orchestrator = new Orchestrator(workspaceRoot, chatSessionState.id, inputHandler);
|
|
467
467
|
const handledByOrchestrator = await orchestrator.runOrchestration(finalInput, dynamicSystemInstruction, ac.signal);
|
|
468
468
|
if (typeof handledByOrchestrator === 'string') {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
469
|
+
const isStopped = handledByOrchestrator.includes('Generation stopped') ||
|
|
470
|
+
handledByOrchestrator.includes('[Generation stopped');
|
|
471
|
+
if (isStopped) {
|
|
472
|
+
const currentChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
473
|
+
const modifiedFiles = currentChanges
|
|
474
|
+
.filter((c) => c.action === 'modify' || c.action === 'create' || c.action === 'delete')
|
|
475
|
+
.map((c) => `${c.action}: ${path.relative(workspaceRoot, c.filePath)}`);
|
|
476
|
+
let summaryMsg = '';
|
|
477
|
+
if (modifiedFiles.length > 0) {
|
|
478
|
+
summaryMsg = `Generation was stopped by the user before completion. Partial code changes made before stopping:\n- ${modifiedFiles.join('\n- ')}`;
|
|
479
|
+
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion.'));
|
|
480
|
+
p.log.info(pc.cyan('Partial code changes created/modified before stopping:'));
|
|
481
|
+
modifiedFiles.forEach((file) => p.log.message(` ${pc.dim('•')} ${file}`));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
summaryMsg = 'Generation was stopped by the user before completion. No code files were modified before stopping.';
|
|
485
|
+
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion (no files were modified).'));
|
|
486
|
+
}
|
|
487
|
+
chat.addTurn({
|
|
488
|
+
role: 'user',
|
|
489
|
+
parts: [{ text: `[USER INTERRUPT] User issued a "stop" command during execution: "${userInput}"` }],
|
|
490
|
+
}, {
|
|
491
|
+
role: 'model',
|
|
492
|
+
parts: [
|
|
493
|
+
{
|
|
494
|
+
text: `[GENERATION INTERRUPTED BY USER] I acknowledge that generation was manually stopped by the user before task completion.\n${summaryMsg}`,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// Inject the task context into the main proxy chat session so the agent remembers what happened
|
|
501
|
+
// and so the full history state gets saved properly on disk.
|
|
502
|
+
chat.addTurn({
|
|
503
|
+
role: 'user',
|
|
504
|
+
parts: [{ text: userInput }],
|
|
505
|
+
}, {
|
|
506
|
+
role: 'model',
|
|
507
|
+
parts: [{ text: handledByOrchestrator }],
|
|
508
|
+
});
|
|
509
|
+
// Print the summary text just like single-agent mode
|
|
482
510
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(Orchestrator)`)}\n`);
|
|
483
511
|
const cleanText = handledByOrchestrator.replace(/\n([ \t]*\n){2,}/g, '\n\n');
|
|
484
512
|
console.log(marked.parse(cleanText));
|
|
@@ -519,7 +547,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
519
547
|
changeLogger.markComplete();
|
|
520
548
|
changeLogger.commitChangeSet();
|
|
521
549
|
// Auto-save chat history for the orchestrator turn
|
|
522
|
-
const history = chat.
|
|
550
|
+
const history = chat.getFullHistory();
|
|
523
551
|
if (history.length > 0) {
|
|
524
552
|
if (!chatSessionState.title) {
|
|
525
553
|
chatSessionState.title = 'Generating title...';
|
|
@@ -628,8 +656,38 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
628
656
|
const turnEndTime = Date.now();
|
|
629
657
|
const turnDuration = ((turnEndTime - turnStartTime) / 1000).toFixed(1);
|
|
630
658
|
p.log.info(`${pc.dim('Generated in')} ${pc.cyan(turnDuration + 's')}`);
|
|
631
|
-
//
|
|
632
|
-
|
|
659
|
+
// Check if generation was stopped by user
|
|
660
|
+
const isStopped = finalText.includes('Generation stopped') ||
|
|
661
|
+
finalText.includes('[Generation stopped');
|
|
662
|
+
if (isStopped) {
|
|
663
|
+
const currentChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
664
|
+
const modifiedFiles = currentChanges
|
|
665
|
+
.filter((c) => c.action === 'modify' || c.action === 'create' || c.action === 'delete')
|
|
666
|
+
.map((c) => `${c.action}: ${path.relative(workspaceRoot, c.filePath)}`);
|
|
667
|
+
let summaryMsg = '';
|
|
668
|
+
if (modifiedFiles.length > 0) {
|
|
669
|
+
summaryMsg = `Generation was stopped by the user before completion. Partial code changes made before stopping:\n- ${modifiedFiles.join('\n- ')}`;
|
|
670
|
+
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion.'));
|
|
671
|
+
p.log.info(pc.cyan('Partial code changes created/modified before stopping:'));
|
|
672
|
+
modifiedFiles.forEach((file) => p.log.message(` ${pc.dim('•')} ${file}`));
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
summaryMsg = 'Generation was stopped by the user before completion. No code files were modified before stopping.';
|
|
676
|
+
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion (no files were modified).'));
|
|
677
|
+
}
|
|
678
|
+
chat.addTurn({
|
|
679
|
+
role: 'user',
|
|
680
|
+
parts: [{ text: `[USER INTERRUPT] User issued a "stop" command during execution: "${userInput}"` }],
|
|
681
|
+
}, {
|
|
682
|
+
role: 'model',
|
|
683
|
+
parts: [
|
|
684
|
+
{
|
|
685
|
+
text: `[GENERATION INTERRUPTED BY USER] I acknowledge that generation was manually stopped by the user before task completion.\n${summaryMsg}`,
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
633
691
|
changeLogger.markComplete();
|
|
634
692
|
}
|
|
635
693
|
if (usage) {
|
|
@@ -647,7 +705,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
647
705
|
chatSessionState.totalCreditsUsed += usage.creditsUsed || 0;
|
|
648
706
|
}
|
|
649
707
|
// Auto-save chat history
|
|
650
|
-
const history = chat.
|
|
708
|
+
const history = chat.getFullHistory();
|
|
651
709
|
if (history.length > 0) {
|
|
652
710
|
if (!chatSessionState.title) {
|
|
653
711
|
chatSessionState.title = 'Generating title...';
|
|
@@ -884,24 +942,14 @@ async function compressContextFiles(workspaceRoot, contextResult) {
|
|
|
884
942
|
}
|
|
885
943
|
/**
|
|
886
944
|
* Raw chat history entry count threshold to trigger chat history summarization.
|
|
887
|
-
*
|
|
888
|
-
*/
|
|
889
|
-
export const HISTORY_SUMMARIZATION_THRESHOLD = 12;
|
|
890
|
-
/**
|
|
891
|
-
* Checks and summarizes older chat history entries if the active session's history
|
|
892
|
-
* exceeds HISTORY_SUMMARIZATION_THRESHOLD entries.
|
|
893
|
-
*
|
|
894
|
-
* Preserves the most recent 4 entries (2 turns) intact for conversational continuity,
|
|
895
|
-
* replacing all preceding turns with a single summarized pair in the chat history.
|
|
896
|
-
*
|
|
897
|
-
* @param chat - The active ProxyChatSession instance.
|
|
898
|
-
* @returns True if history was summarized and updated; false otherwise.
|
|
945
|
+
* 50 entries = 25 conversational user/model turns.
|
|
899
946
|
*/
|
|
947
|
+
export const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
900
948
|
/**
|
|
901
949
|
* Summarizes older chat history entries if the active session's history
|
|
902
950
|
* exceeds {@link HISTORY_SUMMARIZATION_THRESHOLD} entries.
|
|
903
951
|
*
|
|
904
|
-
* Preserves the most recent
|
|
952
|
+
* Preserves the most recent 20 entries (10 turns) intact for conversational continuity,
|
|
905
953
|
* replacing all preceding turns with a single summarized pair in the chat history.
|
|
906
954
|
*
|
|
907
955
|
* @param chat - The active ProxyChatSession instance.
|
|
@@ -913,8 +961,8 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
913
961
|
return false;
|
|
914
962
|
}
|
|
915
963
|
try {
|
|
916
|
-
// Keep the most recent
|
|
917
|
-
const recentCount =
|
|
964
|
+
// Keep the most recent 20 entries (10 full user/model turns) intact
|
|
965
|
+
const recentCount = 20;
|
|
918
966
|
const olderHistory = history.slice(0, history.length - recentCount);
|
|
919
967
|
const recentHistory = history.slice(history.length - recentCount);
|
|
920
968
|
debugLog(`Summarizing ${olderHistory.length} older chat history entries...`);
|
|
@@ -930,7 +978,7 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
930
978
|
parts: [{ text: 'Understood. I have reviewed and absorbed the summary of our preceding conversation.' }],
|
|
931
979
|
},
|
|
932
980
|
];
|
|
933
|
-
chat.
|
|
981
|
+
chat.loadCompressedHistory([...summaryContent, ...recentHistory]);
|
|
934
982
|
debugLog(`Chat history compressed successfully from ${history.length} to ${summaryContent.length + recentHistory.length} entries.`);
|
|
935
983
|
return true;
|
|
936
984
|
}
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare function setGlobalActiveModel(model: string): void;
|
|
|
5
5
|
export declare function getGlobalActiveModel(): string;
|
|
6
6
|
export declare class ProxyChatSession {
|
|
7
7
|
private history;
|
|
8
|
+
private fullHistory;
|
|
8
9
|
private modelName;
|
|
9
10
|
private systemInstruction;
|
|
10
11
|
private tools;
|
|
@@ -17,7 +18,10 @@ export declare class ProxyChatSession {
|
|
|
17
18
|
getModel(): string;
|
|
18
19
|
clearHistory(): void;
|
|
19
20
|
getRawHistory(): Content[];
|
|
21
|
+
getFullHistory(): Content[];
|
|
20
22
|
loadRawHistory(history: Content[]): void;
|
|
23
|
+
loadCompressedHistory(history: Content[]): void;
|
|
24
|
+
addTurn(userContent: Content, modelContent: Content): void;
|
|
21
25
|
/**
|
|
22
26
|
* Retrieves the most recent conversation history as a formatted string.
|
|
23
27
|
* Useful for passing conversation context to stateless background agents.
|
package/dist/services/ai.js
CHANGED
|
@@ -69,6 +69,7 @@ function truncatePartText(text) {
|
|
|
69
69
|
}
|
|
70
70
|
export class ProxyChatSession {
|
|
71
71
|
history = [];
|
|
72
|
+
fullHistory = [];
|
|
72
73
|
modelName;
|
|
73
74
|
systemInstruction;
|
|
74
75
|
tools;
|
|
@@ -95,25 +96,37 @@ export class ProxyChatSession {
|
|
|
95
96
|
}
|
|
96
97
|
clearHistory() {
|
|
97
98
|
this.history = [];
|
|
99
|
+
this.fullHistory = [];
|
|
98
100
|
}
|
|
99
101
|
getRawHistory() {
|
|
100
102
|
return this.history;
|
|
101
103
|
}
|
|
104
|
+
getFullHistory() {
|
|
105
|
+
return this.fullHistory;
|
|
106
|
+
}
|
|
102
107
|
loadRawHistory(history) {
|
|
108
|
+
this.history = [...history];
|
|
109
|
+
this.fullHistory = JSON.parse(JSON.stringify(history));
|
|
110
|
+
}
|
|
111
|
+
loadCompressedHistory(history) {
|
|
103
112
|
this.history = history;
|
|
104
113
|
}
|
|
114
|
+
addTurn(userContent, modelContent) {
|
|
115
|
+
this.history.push(userContent);
|
|
116
|
+
this.history.push(modelContent);
|
|
117
|
+
this.fullHistory.push(JSON.parse(JSON.stringify(userContent)));
|
|
118
|
+
this.fullHistory.push(JSON.parse(JSON.stringify(modelContent)));
|
|
119
|
+
}
|
|
105
120
|
/**
|
|
106
121
|
* Retrieves the most recent conversation history as a formatted string.
|
|
107
122
|
* Useful for passing conversation context to stateless background agents.
|
|
108
123
|
* @param turns Number of back-and-forth turns (user + model pair = 1 turn) to retrieve.
|
|
109
124
|
*/
|
|
110
125
|
getRecentHistory(turns = 2) {
|
|
111
|
-
if (this.history.length === 0)
|
|
126
|
+
if (this.fullHistory.length === 0 && this.history.length === 0)
|
|
112
127
|
return '';
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// We'll just grab the last N * 2 items.
|
|
116
|
-
const recentItems = this.history.slice(-(turns * 2));
|
|
128
|
+
const sourceHistory = this.fullHistory.length > 0 ? this.fullHistory : this.history;
|
|
129
|
+
const recentItems = sourceHistory.slice(-(turns * 2));
|
|
117
130
|
let formattedHistory = '';
|
|
118
131
|
for (const item of recentItems) {
|
|
119
132
|
const role = item.role === 'user' ? 'User' : 'Assistant';
|
|
@@ -134,6 +147,10 @@ export class ProxyChatSession {
|
|
|
134
147
|
this.history.pop();
|
|
135
148
|
this.history.pop();
|
|
136
149
|
}
|
|
150
|
+
if (this.fullHistory.length >= 2) {
|
|
151
|
+
this.fullHistory.pop();
|
|
152
|
+
this.fullHistory.pop();
|
|
153
|
+
}
|
|
137
154
|
}
|
|
138
155
|
sessionId;
|
|
139
156
|
workspaceRoot;
|
|
@@ -148,12 +165,15 @@ export class ProxyChatSession {
|
|
|
148
165
|
*/
|
|
149
166
|
async pruneHistory() {
|
|
150
167
|
if (this.history.length > MAX_HISTORY_ENTRIES) {
|
|
151
|
-
// Always keep pairs aligned (user/model), so trim from the front
|
|
152
168
|
const excess = this.history.length - MAX_HISTORY_ENTRIES;
|
|
153
|
-
// Round up to the nearest even number to keep user/model pairs intact
|
|
154
169
|
const trimCount = excess % 2 === 0 ? excess : excess + 1;
|
|
155
|
-
const pruned = this.history.slice(0, trimCount);
|
|
156
170
|
this.history = this.history.slice(trimCount);
|
|
171
|
+
}
|
|
172
|
+
if (this.fullHistory.length > MAX_HISTORY_ENTRIES) {
|
|
173
|
+
const excess = this.fullHistory.length - MAX_HISTORY_ENTRIES;
|
|
174
|
+
const trimCount = excess % 2 === 0 ? excess : excess + 1;
|
|
175
|
+
const pruned = this.fullHistory.slice(0, trimCount);
|
|
176
|
+
this.fullHistory = this.fullHistory.slice(trimCount);
|
|
157
177
|
if (this.sessionId && this.workspaceRoot) {
|
|
158
178
|
const archiveFile = `archives/archived_history_${this.sessionId}.json`;
|
|
159
179
|
const existingArchive = readCache(this.workspaceRoot, archiveFile) || [];
|
|
@@ -199,10 +219,12 @@ export class ProxyChatSession {
|
|
|
199
219
|
if (additionalText) {
|
|
200
220
|
newParts.push({ text: truncatePartText(additionalText) });
|
|
201
221
|
}
|
|
202
|
-
|
|
222
|
+
const userEntry = {
|
|
203
223
|
role: 'user',
|
|
204
224
|
parts: newParts,
|
|
205
|
-
}
|
|
225
|
+
};
|
|
226
|
+
this.history.push(userEntry);
|
|
227
|
+
this.fullHistory.push(JSON.parse(JSON.stringify(userEntry)));
|
|
206
228
|
// Prune old history before sending to keep payload bounded
|
|
207
229
|
await this.pruneHistory();
|
|
208
230
|
const effectiveGenerationConfig = { ...this.generationConfig };
|
|
@@ -286,10 +308,12 @@ export class ProxyChatSession {
|
|
|
286
308
|
});
|
|
287
309
|
}
|
|
288
310
|
if (modelParts.length > 0) {
|
|
289
|
-
|
|
311
|
+
const modelEntry = {
|
|
290
312
|
role: 'model',
|
|
291
313
|
parts: modelParts,
|
|
292
|
-
}
|
|
314
|
+
};
|
|
315
|
+
this.history.push(modelEntry);
|
|
316
|
+
this.fullHistory.push(JSON.parse(JSON.stringify(modelEntry)));
|
|
293
317
|
// We must prune again if we just pushed new history
|
|
294
318
|
await this.pruneHistory();
|
|
295
319
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED