minovative-mind-cli 2.14.1 → 2.14.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/README.md +35 -54
- package/dist/services/agent/slashCommands.js +35 -1
- package/dist/services/agent/toolLoop.js +3 -1
- package/dist/services/agent-tools.d.ts +18 -0
- package/dist/services/agent-tools.js +220 -43
- package/dist/services/agent.js +6 -3
- package/dist/services/ai.d.ts +3 -0
- package/dist/services/ai.js +63 -19
- package/dist/services/contextAgent.d.ts +10 -4
- package/dist/services/contextAgent.js +33 -9
- package/dist/services/orchestration/investigationAgent.js +31 -7
- package/dist/services/orchestration/investigationCache.js +19 -9
- package/dist/services/orchestration/readCache.d.ts +1 -0
- package/dist/services/orchestration/readCache.js +5 -2
- package/dist/services/orchestration/scopedTools.js +37 -10
- package/dist/services/orchestration/subAgent.js +16 -2
- package/dist/services/proxyClient.d.ts +6 -0
- package/dist/services/proxyClient.js +24 -10
- package/dist/services/userProfileService.d.ts +14 -0
- package/dist/services/userProfileService.js +105 -3
- package/dist/utils/analysisRunner.d.ts +120 -8
- package/dist/utils/analysisRunner.js +946 -125
- package/dist/utils/contextPrompts.d.ts +39 -0
- package/dist/utils/contextPrompts.js +81 -9
- package/dist/utils/contextRanker.d.ts +216 -0
- package/dist/utils/contextRanker.js +603 -0
- package/dist/utils/dependencyTracer/modules/graph.d.ts +4 -1
- package/dist/utils/dependencyTracer/modules/graph.js +11 -0
- package/dist/utils/dependencyTracer/modules/types.d.ts +10 -0
- package/dist/utils/dependencyTracer.d.ts +25 -0
- package/dist/utils/dependencyTracer.js +34 -0
- package/dist/utils/systemPrompts.d.ts +1 -1
- package/dist/utils/systemPrompts.js +13 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -557,8 +557,8 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
557
557
|
else if (call.name === 'find_recent_changes') {
|
|
558
558
|
logMsg = ` [Context Agent] Looking for recently modified files`;
|
|
559
559
|
}
|
|
560
|
-
else if (call.name === 'run_analysis_script') {
|
|
561
|
-
logMsg = ` [Context Agent] Running analysis script (${args.language})`;
|
|
560
|
+
else if (call.name === 'run_analysis_script' || call.name === 'run_debug_script') {
|
|
561
|
+
logMsg = ` [Context Agent] Running analysis script (${args.language || 'auto'})`;
|
|
562
562
|
}
|
|
563
563
|
if (onProgress) {
|
|
564
564
|
onProgress(logMsg.trim());
|
|
@@ -745,18 +745,42 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
745
745
|
},
|
|
746
746
|
};
|
|
747
747
|
}
|
|
748
|
-
else if (call.name === 'run_analysis_script') {
|
|
749
|
-
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, {
|
|
748
|
+
else if (call.name === 'run_analysis_script' || call.name === 'run_debug_script') {
|
|
749
|
+
const analysisResult = await runEphemeralScript(workspaceRoot, args.language || 'auto', args.code, {
|
|
750
750
|
abortSignal,
|
|
751
751
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
752
|
+
let rawOutput = '';
|
|
753
|
+
if (analysisResult.exitCode !== 0) {
|
|
754
|
+
rawOutput += `Script failed with exit code ${analysisResult.exitCode}.\n`;
|
|
755
|
+
}
|
|
756
|
+
if (analysisResult.structuredResult !== undefined) {
|
|
757
|
+
const formattedStructured = typeof analysisResult.structuredResult === 'string'
|
|
758
|
+
? analysisResult.structuredResult
|
|
759
|
+
: JSON.stringify(analysisResult.structuredResult, null, 2);
|
|
760
|
+
rawOutput += `[STRUCTURED RESULT]\n${formattedStructured}\n`;
|
|
761
|
+
}
|
|
762
|
+
if (analysisResult.stdout) {
|
|
763
|
+
rawOutput += `[STDOUT]\n${analysisResult.stdout}\n`;
|
|
764
|
+
}
|
|
765
|
+
if (analysisResult.stderr) {
|
|
766
|
+
rawOutput += `[STDERR]\n${analysisResult.stderr}\n`;
|
|
767
|
+
}
|
|
768
|
+
if (!rawOutput.trim()) {
|
|
769
|
+
rawOutput = '(script produced no output)';
|
|
770
|
+
}
|
|
771
|
+
const output = boundToolOutput(rawOutput.trim());
|
|
756
772
|
return {
|
|
757
773
|
functionResponse: {
|
|
758
774
|
name: call.name,
|
|
759
|
-
response: {
|
|
775
|
+
response: {
|
|
776
|
+
output,
|
|
777
|
+
...(analysisResult.structuredResult !== undefined
|
|
778
|
+
? { structuredResult: analysisResult.structuredResult }
|
|
779
|
+
: {}),
|
|
780
|
+
...(analysisResult.exitCode !== 0
|
|
781
|
+
? { error: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}` }
|
|
782
|
+
: {}),
|
|
783
|
+
},
|
|
760
784
|
},
|
|
761
785
|
};
|
|
762
786
|
}
|
|
@@ -329,19 +329,43 @@ export class InvestigationAgentRunner {
|
|
|
329
329
|
},
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
|
-
else if (call.name === 'run_analysis_script') {
|
|
332
|
+
else if (call.name === 'run_analysis_script' || call.name === 'run_debug_script') {
|
|
333
333
|
if (onProgress)
|
|
334
|
-
onProgress(`${logPrefix} Running analysis script`);
|
|
335
|
-
const analysisResult = await runEphemeralScript(this.workspaceRoot, args.language, args.code, {
|
|
334
|
+
onProgress(`${logPrefix} Running analysis script (${args.language || 'auto'})`);
|
|
335
|
+
const analysisResult = await runEphemeralScript(this.workspaceRoot, args.language || 'auto', args.code, {
|
|
336
336
|
abortSignal,
|
|
337
337
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
let rawOutput = '';
|
|
339
|
+
if (analysisResult.exitCode !== 0) {
|
|
340
|
+
rawOutput += `Script failed with exit code ${analysisResult.exitCode}.\n`;
|
|
341
|
+
}
|
|
342
|
+
if (analysisResult.structuredResult !== undefined) {
|
|
343
|
+
const formattedStructured = typeof analysisResult.structuredResult === 'string'
|
|
344
|
+
? analysisResult.structuredResult
|
|
345
|
+
: JSON.stringify(analysisResult.structuredResult, null, 2);
|
|
346
|
+
rawOutput += `[STRUCTURED RESULT]\n${formattedStructured}\n`;
|
|
347
|
+
}
|
|
348
|
+
if (analysisResult.stdout) {
|
|
349
|
+
rawOutput += `[STDOUT]\n${analysisResult.stdout}\n`;
|
|
350
|
+
}
|
|
351
|
+
if (analysisResult.stderr) {
|
|
352
|
+
rawOutput += `[STDERR]\n${analysisResult.stderr}\n`;
|
|
353
|
+
}
|
|
354
|
+
if (!rawOutput.trim()) {
|
|
355
|
+
rawOutput = '(script produced no output)';
|
|
356
|
+
}
|
|
341
357
|
return {
|
|
342
358
|
functionResponse: {
|
|
343
359
|
name: call.name,
|
|
344
|
-
response: {
|
|
360
|
+
response: {
|
|
361
|
+
output: rawOutput.trim(),
|
|
362
|
+
...(analysisResult.structuredResult !== undefined
|
|
363
|
+
? { structuredResult: analysisResult.structuredResult }
|
|
364
|
+
: {}),
|
|
365
|
+
...(analysisResult.exitCode !== 0
|
|
366
|
+
? { error: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}` }
|
|
367
|
+
: {}),
|
|
368
|
+
},
|
|
345
369
|
},
|
|
346
370
|
};
|
|
347
371
|
}
|
|
@@ -332,20 +332,21 @@ function tokenJaccardSimilarity(a, b) {
|
|
|
332
332
|
export async function generateWorkspaceFingerprint(workspaceRoot, relevantFiles) {
|
|
333
333
|
const fileStats = [];
|
|
334
334
|
for (const file of relevantFiles) {
|
|
335
|
+
const normFile = file.replace(/\\/g, '/').toLowerCase();
|
|
335
336
|
try {
|
|
336
|
-
const fullPath = path.join(workspaceRoot, file);
|
|
337
|
+
const fullPath = path.isAbsolute(file) ? file : path.join(workspaceRoot, file);
|
|
337
338
|
const stat = await fs.stat(fullPath);
|
|
338
|
-
fileStats.push(`${
|
|
339
|
+
fileStats.push(`${normFile}:${stat.mtimeMs}:${stat.size}`);
|
|
339
340
|
}
|
|
340
341
|
catch (e) {
|
|
341
342
|
if (e.code === 'ENOENT') {
|
|
342
|
-
fileStats.push(`${
|
|
343
|
+
fileStats.push(`${normFile}:deleted`);
|
|
343
344
|
}
|
|
344
345
|
else if (e.code === 'EACCES' || e.code === 'EPERM') {
|
|
345
|
-
fileStats.push(`${
|
|
346
|
+
fileStats.push(`${normFile}:inaccessible`);
|
|
346
347
|
}
|
|
347
348
|
else {
|
|
348
|
-
fileStats.push(`${
|
|
349
|
+
fileStats.push(`${normFile}:error`);
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
}
|
|
@@ -356,15 +357,17 @@ export async function generateWorkspaceFingerprint(workspaceRoot, relevantFiles)
|
|
|
356
357
|
* Enforces LRU eviction to keep cache store below MAX_CACHE_SIZE_BYTES (5MB).
|
|
357
358
|
*/
|
|
358
359
|
export function pruneCacheStore(store) {
|
|
360
|
+
if (!store || !store.entries)
|
|
361
|
+
return;
|
|
359
362
|
let storeJson = JSON.stringify(store);
|
|
360
363
|
while (Buffer.byteLength(storeJson, 'utf8') > MAX_CACHE_SIZE_BYTES) {
|
|
361
364
|
const keys = Object.keys(store.entries);
|
|
362
365
|
if (keys.length === 0)
|
|
363
366
|
break;
|
|
364
367
|
let oldestKey = keys[0];
|
|
365
|
-
let oldestTime = store.entries[oldestKey].lastAccessedAt || store.entries[oldestKey].createdAt;
|
|
368
|
+
let oldestTime = store.entries[oldestKey].lastAccessedAt || store.entries[oldestKey].createdAt || 0;
|
|
366
369
|
for (let i = 1; i < keys.length; i++) {
|
|
367
|
-
const entryTime = store.entries[keys[i]].lastAccessedAt || store.entries[keys[i]].createdAt;
|
|
370
|
+
const entryTime = store.entries[keys[i]].lastAccessedAt || store.entries[keys[i]].createdAt || 0;
|
|
368
371
|
if (entryTime < oldestTime) {
|
|
369
372
|
oldestKey = keys[i];
|
|
370
373
|
oldestTime = entryTime;
|
|
@@ -612,7 +615,14 @@ export async function invalidateFilesFromInvestigationCache(workspaceRoot, chang
|
|
|
612
615
|
const entry = store.entries[hash];
|
|
613
616
|
const dependsOnChange = entry.relevantFiles.some((f) => {
|
|
614
617
|
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
615
|
-
|
|
618
|
+
const baseF = path.basename(normF);
|
|
619
|
+
return normalizedChanged.some((c) => {
|
|
620
|
+
const baseC = path.basename(c);
|
|
621
|
+
return (normF === c ||
|
|
622
|
+
normF.endsWith('/' + c) ||
|
|
623
|
+
c.endsWith('/' + normF) ||
|
|
624
|
+
(baseF === baseC && (normF.includes(c) || c.includes(normF))));
|
|
625
|
+
});
|
|
616
626
|
});
|
|
617
627
|
if (dependsOnChange) {
|
|
618
628
|
delete store.entries[hash];
|
|
@@ -620,7 +630,7 @@ export async function invalidateFilesFromInvestigationCache(workspaceRoot, chang
|
|
|
620
630
|
}
|
|
621
631
|
}
|
|
622
632
|
if (invalidated > 0) {
|
|
623
|
-
writeCache(workspaceRoot, CACHE_FILE, store);
|
|
633
|
+
await writeCache(workspaceRoot, CACHE_FILE, store);
|
|
624
634
|
debugLog(`[DEBUG] Investigation cache: invalidated ${invalidated} entries referencing changed files`);
|
|
625
635
|
}
|
|
626
636
|
}
|
|
@@ -73,6 +73,7 @@ export declare class ReadCache {
|
|
|
73
73
|
/**
|
|
74
74
|
* Invalidates and evicts all cache entries associated with a file path,
|
|
75
75
|
* including full-file reads and any ranged or targeted sub-reads.
|
|
76
|
+
* Uses case-insensitive and normalized path comparison.
|
|
76
77
|
*
|
|
77
78
|
* @param filePath - The file path that was modified or deleted.
|
|
78
79
|
*/
|
|
@@ -115,16 +115,19 @@ export class ReadCache {
|
|
|
115
115
|
/**
|
|
116
116
|
* Invalidates and evicts all cache entries associated with a file path,
|
|
117
117
|
* including full-file reads and any ranged or targeted sub-reads.
|
|
118
|
+
* Uses case-insensitive and normalized path comparison.
|
|
118
119
|
*
|
|
119
120
|
* @param filePath - The file path that was modified or deleted.
|
|
120
121
|
*/
|
|
121
122
|
invalidate(filePath) {
|
|
122
123
|
if (!filePath)
|
|
123
124
|
return;
|
|
124
|
-
const
|
|
125
|
+
const normalizedTarget = filePath.replace(/\\/g, '/').toLowerCase();
|
|
126
|
+
const prefix = `${normalizedTarget}::`;
|
|
125
127
|
const keysToRemove = [];
|
|
126
128
|
for (const key of this.cache.keys()) {
|
|
127
|
-
|
|
129
|
+
const normKey = key.replace(/\\/g, '/').toLowerCase();
|
|
130
|
+
if (normKey === normalizedTarget || normKey.startsWith(prefix)) {
|
|
128
131
|
keysToRemove.push(key);
|
|
129
132
|
}
|
|
130
133
|
}
|
|
@@ -215,18 +215,37 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
215
215
|
inlineData: result.inlineData,
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
|
-
// If a file was modified/written/deleted/renamed, invalidate readCache entries for affected file(s)
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
221
|
-
if (
|
|
218
|
+
// If a file was modified/written/deleted/renamed, invalidate readCache and investigationCache entries for affected file(s)
|
|
219
|
+
if (name === 'write_file' || name === 'modify_file' || name === 'delete_file') {
|
|
220
|
+
if (args?.filePath) {
|
|
221
|
+
if (readCache) {
|
|
222
222
|
readCache.invalidate(args.filePath);
|
|
223
223
|
}
|
|
224
|
+
import('./investigationCache.js')
|
|
225
|
+
.then(({ invalidateFilesFromInvestigationCache }) => {
|
|
226
|
+
invalidateFilesFromInvestigationCache(workspaceRoot, [args.filePath]).catch(() => { });
|
|
227
|
+
})
|
|
228
|
+
.catch(() => { });
|
|
224
229
|
}
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
}
|
|
231
|
+
else if (name === 'rename_file') {
|
|
232
|
+
const affected = [];
|
|
233
|
+
if (args?.sourcePath) {
|
|
234
|
+
if (readCache)
|
|
227
235
|
readCache.invalidate(args.sourcePath);
|
|
228
|
-
|
|
236
|
+
affected.push(args.sourcePath);
|
|
237
|
+
}
|
|
238
|
+
if (args?.targetPath) {
|
|
239
|
+
if (readCache)
|
|
229
240
|
readCache.invalidate(args.targetPath);
|
|
241
|
+
affected.push(args.targetPath);
|
|
242
|
+
}
|
|
243
|
+
if (affected.length > 0) {
|
|
244
|
+
import('./investigationCache.js')
|
|
245
|
+
.then(({ invalidateFilesFromInvestigationCache }) => {
|
|
246
|
+
invalidateFilesFromInvestigationCache(workspaceRoot, affected).catch(() => { });
|
|
247
|
+
})
|
|
248
|
+
.catch(() => { });
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
// If we got a file lock and had context from a previous writer, we should
|
|
@@ -307,10 +326,18 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
307
326
|
targetDesc = String(args.filePath);
|
|
308
327
|
actionDesc = 'Deleted';
|
|
309
328
|
}
|
|
310
|
-
else if (name === 'run_debug_script') {
|
|
329
|
+
else if (name === 'run_debug_script' || name === 'run_analysis_script' || name === 'debug_script' || name === 'analysis_script') {
|
|
311
330
|
targetDesc = String(args.language ?? 'node');
|
|
312
|
-
actionDesc = 'Ran debug script';
|
|
313
|
-
|
|
331
|
+
actionDesc = name.includes('analysis') ? 'Ran analysis script' : 'Ran debug script';
|
|
332
|
+
if (typeof result === 'object' && result?.error) {
|
|
333
|
+
resultSummary = result.error;
|
|
334
|
+
}
|
|
335
|
+
else if (typeof result === 'object' && result?.structuredResult !== undefined) {
|
|
336
|
+
resultSummary = 'Completed script (structured result)';
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
resultSummary = 'Completed script';
|
|
340
|
+
}
|
|
314
341
|
}
|
|
315
342
|
else if (name === 'run_fuzz_probe') {
|
|
316
343
|
targetDesc = String(args.language ?? 'node');
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements the lifecycle, health monitoring, and tool-loop execution for a single
|
|
5
5
|
* parallelized sub-agent.
|
|
6
6
|
*/
|
|
7
|
-
import { ProxyChatSession, getGlobalActiveModel, getModelThinkingLevel } from '../ai.js';
|
|
7
|
+
import { ProxyChatSession, getGlobalActiveModel, getModelThinkingLevel, HISTORICAL_TOOL_OUTPUT_THRESHOLD } from '../ai.js';
|
|
8
8
|
import { GEMINI_MODELS, MAX_OUTPUT_TOKENS } from '../../utils/config.js';
|
|
9
9
|
import { ReadCache } from './readCache.js';
|
|
10
10
|
import { executeScopedTool, getScopedToolDeclarations } from './scopedTools.js';
|
|
@@ -99,6 +99,15 @@ export function createCompactToolReference(toolName, output, args) {
|
|
|
99
99
|
const file = args?.filePath ? `${args.filePath}` : 'file';
|
|
100
100
|
return `[Wrote ${file} - tool execution completed]`;
|
|
101
101
|
}
|
|
102
|
+
if (toolName === 'delete_file') {
|
|
103
|
+
const file = args?.filePath ? `${args.filePath}` : 'file';
|
|
104
|
+
return `[Deleted ${file} - tool execution completed]`;
|
|
105
|
+
}
|
|
106
|
+
if (toolName === 'rename_file') {
|
|
107
|
+
const src = args?.sourcePath || 'source';
|
|
108
|
+
const tgt = args?.targetPath || 'target';
|
|
109
|
+
return `[Renamed ${src} to ${tgt} - tool execution completed]`;
|
|
110
|
+
}
|
|
102
111
|
if (toolName === 'find_dependencies') {
|
|
103
112
|
const file = args?.filePath ? `for ${args.filePath}` : '';
|
|
104
113
|
return `[Dependency trace ${file} completed]`.replace(/\s+/g, ' ');
|
|
@@ -237,7 +246,7 @@ export class SubAgentRunner {
|
|
|
237
246
|
}
|
|
238
247
|
}
|
|
239
248
|
// Also trigger proxy chat session pruning to collapse any remaining long fields
|
|
240
|
-
this.chat.pruneToolOutputHistory();
|
|
249
|
+
this.chat.pruneToolOutputHistory(HISTORICAL_TOOL_OUTPUT_THRESHOLD, turnsToKeep);
|
|
241
250
|
}
|
|
242
251
|
/**
|
|
243
252
|
* Executes the sub-agent with a health monitor harness.
|
|
@@ -389,6 +398,11 @@ export class SubAgentRunner {
|
|
|
389
398
|
`DO NOT IMPLEMENT THIS FULL REQUEST. THIS IS JUST FOR CONTEXT.\n\n` +
|
|
390
399
|
`${scopedContext}\n` +
|
|
391
400
|
`</reference_context>\n\n` +
|
|
401
|
+
`<documentation_and_quality_standards>\n` +
|
|
402
|
+
`- Comprehensive Documentation: Write documentation for senior engineers: explain the 'why', document edge-cases/private states, use precise types, and avoid restating the code.\n` +
|
|
403
|
+
`- Provide JSDoc/TSDoc/DocStrings (as appropriate for the language) for all newly added or modified APIs, functions, classes, interfaces, and types (documenting parameters, return values, and behavior).\n` +
|
|
404
|
+
`- Use clean inline comments to explain complex or non-obvious logic, keeping implementation code highly organized and maintainable.\n` +
|
|
405
|
+
`</documentation_and_quality_standards>\n\n` +
|
|
392
406
|
`<critical_guidelines>\n` +
|
|
393
407
|
`1. You are ONE worker in a team. Focus EXCLUSIVELY on your specific objective: "${this.intent}".\n` +
|
|
394
408
|
`2. DO NOT attempt to fulfill the entire original user request in the reference context. Other agents handle other tasks.\n` +
|
|
@@ -76,6 +76,12 @@ export declare function accumulateTurnUsage(usage: {
|
|
|
76
76
|
candidatesTokenCount?: number;
|
|
77
77
|
cachedContentTokenCount?: number;
|
|
78
78
|
}, modelName: string): void;
|
|
79
|
+
/**
|
|
80
|
+
* Detects whether an error message relates to an expired or mismatched Gemini context cache.
|
|
81
|
+
* When models transition (e.g. Flash to Flash-Lite) or a server cache expires,
|
|
82
|
+
* this triggers a fast retry to refresh/recreate the cache under the active model.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isCacheMismatchOrExpiredError(errMsg: string | undefined): boolean;
|
|
79
85
|
/**
|
|
80
86
|
* Client service interacting directly with the serverless Gemini proxy endpoint.
|
|
81
87
|
* Ensures authorization via Firebase token passing and parses streamed content.
|
|
@@ -82,6 +82,22 @@ export function accumulateTurnUsage(usage, modelName) {
|
|
|
82
82
|
globalSessionAccumulatedUsage.totalTokenCount += pTokens + cachedTokens + cTokens;
|
|
83
83
|
globalSessionAccumulatedUsage.modelsUsed[modelName] = (globalSessionAccumulatedUsage.modelsUsed[modelName] || 0) + 1;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Detects whether an error message relates to an expired or mismatched Gemini context cache.
|
|
87
|
+
* When models transition (e.g. Flash to Flash-Lite) or a server cache expires,
|
|
88
|
+
* this triggers a fast retry to refresh/recreate the cache under the active model.
|
|
89
|
+
*/
|
|
90
|
+
export function isCacheMismatchOrExpiredError(errMsg) {
|
|
91
|
+
if (!errMsg)
|
|
92
|
+
return false;
|
|
93
|
+
const lower = errMsg.toLowerCase();
|
|
94
|
+
return (lower.includes('is expired') ||
|
|
95
|
+
lower.includes('cache content') ||
|
|
96
|
+
lower.includes('cached content') ||
|
|
97
|
+
lower.includes('cached_content') ||
|
|
98
|
+
lower.includes('does not match the model in the cached content') ||
|
|
99
|
+
lower.includes('model in the inference request'));
|
|
100
|
+
}
|
|
85
101
|
/**
|
|
86
102
|
* Client service interacting directly with the serverless Gemini proxy endpoint.
|
|
87
103
|
* Ensures authorization via Firebase token passing and parses streamed content.
|
|
@@ -194,9 +210,9 @@ export class ProxyClient {
|
|
|
194
210
|
if (!response.ok) {
|
|
195
211
|
const errorData = await response.json().catch(() => ({}));
|
|
196
212
|
const errorMsg = JSON.stringify(errorData) || response.statusText;
|
|
197
|
-
if (
|
|
213
|
+
if (isCacheMismatchOrExpiredError(errorMsg)) {
|
|
198
214
|
if (attempt < MAX_RETRIES) {
|
|
199
|
-
debugLog(`Gemini Context Cache expired on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
|
|
215
|
+
debugLog(`Gemini Context Cache expired or mismatched on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
|
|
200
216
|
await delay(500, abortSignal);
|
|
201
217
|
attempt++;
|
|
202
218
|
continue retryLoop;
|
|
@@ -293,8 +309,7 @@ export class ProxyClient {
|
|
|
293
309
|
err.name = 'AbortError';
|
|
294
310
|
throw err;
|
|
295
311
|
}
|
|
296
|
-
const isCacheExpired = streamError.message
|
|
297
|
-
streamError.message?.includes('Cache content');
|
|
312
|
+
const isCacheExpired = isCacheMismatchOrExpiredError(streamError.message);
|
|
298
313
|
if (streamError.message?.includes('429') ||
|
|
299
314
|
streamError.message?.includes('502') ||
|
|
300
315
|
streamError.message?.includes('503') ||
|
|
@@ -312,7 +327,7 @@ export class ProxyClient {
|
|
|
312
327
|
if (isDebugOn()) {
|
|
313
328
|
process.stdout.write('\n');
|
|
314
329
|
console.warn(isCacheExpired
|
|
315
|
-
? `Gemini Context Cache expired on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
|
|
330
|
+
? `Gemini Context Cache expired or mismatched on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
|
|
316
331
|
: `Server error or rate limit hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
317
332
|
}
|
|
318
333
|
await delay(delayTime, abortSignal);
|
|
@@ -435,9 +450,9 @@ export class ProxyClient {
|
|
|
435
450
|
// ignore parsing error
|
|
436
451
|
}
|
|
437
452
|
const errorMsg = errorData.error?.message || JSON.stringify(errorData) || response.statusText;
|
|
438
|
-
if (
|
|
453
|
+
if (isCacheMismatchOrExpiredError(errorMsg)) {
|
|
439
454
|
if (attempt < MAX_RETRIES) {
|
|
440
|
-
debugLog(`BYOK Gemini Context Cache expired on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
|
|
455
|
+
debugLog(`BYOK Gemini Context Cache expired or mismatched on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
|
|
441
456
|
await delay(500, abortSignal);
|
|
442
457
|
attempt++;
|
|
443
458
|
continue retryLoop;
|
|
@@ -526,8 +541,7 @@ export class ProxyClient {
|
|
|
526
541
|
err.name = 'AbortError';
|
|
527
542
|
throw err;
|
|
528
543
|
}
|
|
529
|
-
const isCacheExpired = streamError.message
|
|
530
|
-
streamError.message?.includes('Cache content');
|
|
544
|
+
const isCacheExpired = isCacheMismatchOrExpiredError(streamError.message);
|
|
531
545
|
if (streamError.message?.includes('429') ||
|
|
532
546
|
streamError.message?.includes('502') ||
|
|
533
547
|
streamError.message?.includes('503') ||
|
|
@@ -545,7 +559,7 @@ export class ProxyClient {
|
|
|
545
559
|
if (isDebugOn()) {
|
|
546
560
|
process.stdout.write('\n');
|
|
547
561
|
console.warn(isCacheExpired
|
|
548
|
-
? `BYOK Gemini Context Cache expired on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
|
|
562
|
+
? `BYOK Gemini Context Cache expired or mismatched on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
|
|
549
563
|
: `Server error or rate limit hit during BYOK stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
550
564
|
}
|
|
551
565
|
await delay(delayTime, abortSignal);
|
|
@@ -30,6 +30,19 @@ export interface UserProfileCognitiveTraits {
|
|
|
30
30
|
debuggingStyle?: string;
|
|
31
31
|
explanationFormat?: string;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Deep interpersonal, psychological, and conversational personality traits observed by the agent.
|
|
35
|
+
* Captures how the user speaks to the AI, relationship posture, banter, apology tolerance, and quirks.
|
|
36
|
+
*/
|
|
37
|
+
export interface UserProfileConversationalPersona {
|
|
38
|
+
relationshipModel?: string;
|
|
39
|
+
banterAffinity?: string;
|
|
40
|
+
formalityLevel?: string;
|
|
41
|
+
apologyTolerance?: string;
|
|
42
|
+
stressCadence?: string;
|
|
43
|
+
promptingHabit?: string;
|
|
44
|
+
conversationalQuirks?: string[];
|
|
45
|
+
}
|
|
33
46
|
/**
|
|
34
47
|
* Technical habits and technology strengths observed by the agent.
|
|
35
48
|
*/
|
|
@@ -43,6 +56,7 @@ export interface UserProfileTechnicalPreferences {
|
|
|
43
56
|
export interface UserProfile {
|
|
44
57
|
communicationStyle?: UserProfileCommunicationStyle;
|
|
45
58
|
cognitiveTraits?: UserProfileCognitiveTraits;
|
|
59
|
+
conversationalPersona?: UserProfileConversationalPersona;
|
|
46
60
|
technicalPreferences?: UserProfileTechnicalPreferences;
|
|
47
61
|
agentNotes: string[];
|
|
48
62
|
lastUpdated?: number;
|
|
@@ -50,6 +50,7 @@ export async function loadUserProfile() {
|
|
|
50
50
|
return {
|
|
51
51
|
communicationStyle: {},
|
|
52
52
|
cognitiveTraits: {},
|
|
53
|
+
conversationalPersona: { conversationalQuirks: [] },
|
|
53
54
|
technicalPreferences: { strengths: [], conventions: [] },
|
|
54
55
|
agentNotes: [],
|
|
55
56
|
lastUpdated: Date.now(),
|
|
@@ -61,6 +62,17 @@ export async function loadUserProfile() {
|
|
|
61
62
|
return {
|
|
62
63
|
communicationStyle: parsed.communicationStyle || {},
|
|
63
64
|
cognitiveTraits: parsed.cognitiveTraits || {},
|
|
65
|
+
conversationalPersona: {
|
|
66
|
+
relationshipModel: parsed.conversationalPersona?.relationshipModel,
|
|
67
|
+
banterAffinity: parsed.conversationalPersona?.banterAffinity,
|
|
68
|
+
formalityLevel: parsed.conversationalPersona?.formalityLevel,
|
|
69
|
+
apologyTolerance: parsed.conversationalPersona?.apologyTolerance,
|
|
70
|
+
stressCadence: parsed.conversationalPersona?.stressCadence,
|
|
71
|
+
promptingHabit: parsed.conversationalPersona?.promptingHabit,
|
|
72
|
+
conversationalQuirks: Array.isArray(parsed.conversationalPersona?.conversationalQuirks)
|
|
73
|
+
? parsed.conversationalPersona.conversationalQuirks
|
|
74
|
+
: [],
|
|
75
|
+
},
|
|
64
76
|
technicalPreferences: {
|
|
65
77
|
strengths: Array.isArray(parsed.technicalPreferences?.strengths) ? parsed.technicalPreferences.strengths : [],
|
|
66
78
|
conventions: Array.isArray(parsed.technicalPreferences?.conventions)
|
|
@@ -76,6 +88,7 @@ export async function loadUserProfile() {
|
|
|
76
88
|
return {
|
|
77
89
|
communicationStyle: {},
|
|
78
90
|
cognitiveTraits: {},
|
|
91
|
+
conversationalPersona: { conversationalQuirks: [] },
|
|
79
92
|
technicalPreferences: { strengths: [], conventions: [] },
|
|
80
93
|
agentNotes: [],
|
|
81
94
|
lastUpdated: Date.now(),
|
|
@@ -133,6 +146,8 @@ export async function deleteSideNote(index) {
|
|
|
133
146
|
export async function clearUserProfile() {
|
|
134
147
|
const emptyProfile = {
|
|
135
148
|
communicationStyle: {},
|
|
149
|
+
cognitiveTraits: {},
|
|
150
|
+
conversationalPersona: { conversationalQuirks: [] },
|
|
136
151
|
technicalPreferences: { strengths: [], conventions: [] },
|
|
137
152
|
agentNotes: [],
|
|
138
153
|
lastUpdated: Date.now(),
|
|
@@ -153,15 +168,43 @@ export function formatUserProfileForContext(profile) {
|
|
|
153
168
|
profile.cognitiveTraits?.delegationDepth ||
|
|
154
169
|
profile.cognitiveTraits?.debuggingStyle ||
|
|
155
170
|
profile.cognitiveTraits?.explanationFormat;
|
|
171
|
+
const hasPersona = profile.conversationalPersona?.relationshipModel ||
|
|
172
|
+
profile.conversationalPersona?.banterAffinity ||
|
|
173
|
+
profile.conversationalPersona?.formalityLevel ||
|
|
174
|
+
profile.conversationalPersona?.apologyTolerance ||
|
|
175
|
+
profile.conversationalPersona?.stressCadence ||
|
|
176
|
+
profile.conversationalPersona?.promptingHabit ||
|
|
177
|
+
(profile.conversationalPersona?.conversationalQuirks && profile.conversationalPersona.conversationalQuirks.length > 0);
|
|
156
178
|
const hasStrengths = profile.technicalPreferences?.strengths && profile.technicalPreferences.strengths.length > 0;
|
|
157
179
|
const hasConventions = profile.technicalPreferences?.conventions && profile.technicalPreferences.conventions.length > 0;
|
|
158
180
|
const hasNotes = profile.agentNotes && profile.agentNotes.length > 0;
|
|
159
|
-
if (!hasStyle && !hasCognitive && !hasStrengths && !hasConventions && !hasNotes) {
|
|
181
|
+
if (!hasStyle && !hasCognitive && !hasPersona && !hasStrengths && !hasConventions && !hasNotes) {
|
|
160
182
|
return '';
|
|
161
183
|
}
|
|
162
184
|
let block = '<user_profile>\n';
|
|
163
185
|
block +=
|
|
164
|
-
'The following are continuous, adaptive insights and preferences learned from past interactions with this user:\n';
|
|
186
|
+
'The following are continuous, adaptive insights, conversational personality traits, and technical preferences learned from past interactions with this user:\n';
|
|
187
|
+
if (profile.conversationalPersona?.relationshipModel) {
|
|
188
|
+
block += `- Teammate & Pairing Dynamic: ${profile.conversationalPersona.relationshipModel}\n`;
|
|
189
|
+
}
|
|
190
|
+
if (profile.conversationalPersona?.banterAffinity) {
|
|
191
|
+
block += `- Banter & Humor Affinity: ${profile.conversationalPersona.banterAffinity}\n`;
|
|
192
|
+
}
|
|
193
|
+
if (profile.conversationalPersona?.formalityLevel) {
|
|
194
|
+
block += `- Speech Formality: ${profile.conversationalPersona.formalityLevel}\n`;
|
|
195
|
+
}
|
|
196
|
+
if (profile.conversationalPersona?.apologyTolerance) {
|
|
197
|
+
block += `- Apology & Error Reaction: ${profile.conversationalPersona.apologyTolerance}\n`;
|
|
198
|
+
}
|
|
199
|
+
if (profile.conversationalPersona?.stressCadence) {
|
|
200
|
+
block += `- Stress & Urgency Cadence: ${profile.conversationalPersona.stressCadence}\n`;
|
|
201
|
+
}
|
|
202
|
+
if (profile.conversationalPersona?.promptingHabit) {
|
|
203
|
+
block += `- Prompting & Input Style: ${profile.conversationalPersona.promptingHabit}\n`;
|
|
204
|
+
}
|
|
205
|
+
if (profile.conversationalPersona?.conversationalQuirks && profile.conversationalPersona.conversationalQuirks.length > 0) {
|
|
206
|
+
block += `- Conversational Quirks & Catchphrases: ${profile.conversationalPersona.conversationalQuirks.join('; ')}\n`;
|
|
207
|
+
}
|
|
165
208
|
if (profile.communicationStyle?.tonePreference) {
|
|
166
209
|
block += `- Tone & Demeanor: ${profile.communicationStyle.tonePreference}\n`;
|
|
167
210
|
}
|
|
@@ -202,7 +245,7 @@ export function formatUserProfileForContext(profile) {
|
|
|
202
245
|
}
|
|
203
246
|
}
|
|
204
247
|
block +=
|
|
205
|
-
'Adopt these
|
|
248
|
+
'Adopt these conversational personality traits, tone, and technical preferences naturally as an intuitive pair programming partner without ever explicitly mentioning this profile block.\n';
|
|
206
249
|
block += '</user_profile>';
|
|
207
250
|
return block;
|
|
208
251
|
}
|
|
@@ -314,6 +357,65 @@ AI Response Summary:
|
|
|
314
357
|
}
|
|
315
358
|
}
|
|
316
359
|
}
|
|
360
|
+
// Process conversational persona & teammate dynamics
|
|
361
|
+
if ((parsed.conversationalPersona && typeof parsed.conversationalPersona === 'object') ||
|
|
362
|
+
parsed.relationshipModel ||
|
|
363
|
+
parsed.banterAffinity ||
|
|
364
|
+
parsed.formalityLevel ||
|
|
365
|
+
parsed.apologyTolerance ||
|
|
366
|
+
parsed.stressCadence ||
|
|
367
|
+
parsed.promptingHabit ||
|
|
368
|
+
parsed.conversationalQuirks) {
|
|
369
|
+
currentProfile.conversationalPersona = currentProfile.conversationalPersona || { conversationalQuirks: [] };
|
|
370
|
+
const sourceObj = (parsed.conversationalPersona && typeof parsed.conversationalPersona === 'object')
|
|
371
|
+
? parsed.conversationalPersona
|
|
372
|
+
: {};
|
|
373
|
+
const personaKeys = [
|
|
374
|
+
'relationshipModel',
|
|
375
|
+
'banterAffinity',
|
|
376
|
+
'formalityLevel',
|
|
377
|
+
'apologyTolerance',
|
|
378
|
+
'stressCadence',
|
|
379
|
+
'promptingHabit',
|
|
380
|
+
];
|
|
381
|
+
for (const k of personaKeys) {
|
|
382
|
+
const val = sourceObj[k] || parsed[k];
|
|
383
|
+
if (typeof val === 'string' && val.trim().length > 0) {
|
|
384
|
+
currentProfile.conversationalPersona[k] = val.trim();
|
|
385
|
+
changed = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const quirksToAdd = Array.isArray(sourceObj.conversationalQuirks)
|
|
389
|
+
? sourceObj.conversationalQuirks
|
|
390
|
+
: Array.isArray(parsed.conversationalQuirks)
|
|
391
|
+
? parsed.conversationalQuirks
|
|
392
|
+
: [];
|
|
393
|
+
if (quirksToAdd.length > 0) {
|
|
394
|
+
const existingQuirks = new Set(currentProfile.conversationalPersona.conversationalQuirks || []);
|
|
395
|
+
for (const q of quirksToAdd) {
|
|
396
|
+
if (typeof q === 'string' && q.trim().length > 0) {
|
|
397
|
+
existingQuirks.add(q.trim());
|
|
398
|
+
changed = true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
currentProfile.conversationalPersona.conversationalQuirks = Array.from(existingQuirks).slice(0, 10);
|
|
402
|
+
}
|
|
403
|
+
const quirksToRemove = Array.isArray(sourceObj.removeQuirks)
|
|
404
|
+
? sourceObj.removeQuirks
|
|
405
|
+
: Array.isArray(parsed.removeQuirks)
|
|
406
|
+
? parsed.removeQuirks
|
|
407
|
+
: [];
|
|
408
|
+
if (quirksToRemove.length > 0) {
|
|
409
|
+
if (currentProfile.conversationalPersona.conversationalQuirks) {
|
|
410
|
+
const toRemove = new Set(quirksToRemove.map((q) => q.toLowerCase().trim()));
|
|
411
|
+
const filtered = currentProfile.conversationalPersona.conversationalQuirks.filter((q) => !toRemove.has(q.toLowerCase().trim()));
|
|
412
|
+
if (filtered.length !== currentProfile.conversationalPersona.conversationalQuirks.length) {
|
|
413
|
+
currentProfile.conversationalPersona.conversationalQuirks = filtered;
|
|
414
|
+
changed = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
317
419
|
if (Array.isArray(parsed.strengths) && parsed.strengths.length > 0) {
|
|
318
420
|
currentProfile.technicalPreferences = currentProfile.technicalPreferences || { strengths: [], conventions: [] };
|
|
319
421
|
const existing = new Set(currentProfile.technicalPreferences.strengths || []);
|