minovative-mind-cli 2.14.1 → 2.14.3
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 +40 -59
- package/dist/services/agent/slashCommands.js +144 -13
- package/dist/services/agent/toolLoop.js +11 -2
- package/dist/services/agent/types.d.ts +9 -2
- package/dist/services/agent-tools.d.ts +20 -1
- package/dist/services/agent-tools.js +272 -47
- package/dist/services/agent.js +43 -18
- package/dist/services/ai.d.ts +9 -0
- package/dist/services/ai.js +71 -21
- package/dist/services/chatHistoryService.d.ts +5 -0
- package/dist/services/contextAgent.d.ts +14 -6
- package/dist/services/contextAgent.js +36 -10
- 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/sessionSettings.d.ts +66 -0
- package/dist/services/sessionSettings.js +126 -0
- package/dist/services/userProfileService.d.ts +14 -0
- package/dist/services/userProfileService.js +105 -3
- package/dist/services/verificationService.js +24 -2
- package/dist/utils/analysisRunner.d.ts +120 -8
- package/dist/utils/analysisRunner.js +946 -125
- package/dist/utils/antiCheatingGuard.d.ts +21 -0
- package/dist/utils/antiCheatingGuard.js +554 -0
- 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 +40 -2
- package/dist/utils/dependencyTracer.js +95 -3
- package/dist/utils/fileReadCache.d.ts +58 -0
- package/dist/utils/fileReadCache.js +162 -0
- package/dist/utils/projectStorage.js +2 -1
- package/dist/utils/symbolExtractor.d.ts +12 -0
- package/dist/utils/symbolExtractor.js +111 -15
- package/dist/utils/systemPrompts.d.ts +4 -3
- package/dist/utils/systemPrompts.js +66 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/services/agent.js
CHANGED
|
@@ -26,13 +26,13 @@ const execAsync = promisify(exec);
|
|
|
26
26
|
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
27
27
|
import { ensureProjectStorage, ensureIgnored, readCache, writeCache, invalidateCacheForDependents, } from '../utils/projectStorage.js';
|
|
28
28
|
import { GEMINI_MODELS, TPM_COOLING_DELAYS } from '../utils/config.js';
|
|
29
|
-
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, getModelThinkingLevel, summarizeChatHistory, } from './ai.js';
|
|
29
|
+
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, getResearchConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, getModelThinkingLevel, summarizeChatHistory, } from './ai.js';
|
|
30
30
|
import { getAndResetTurnUsage } from './proxyClient.js';
|
|
31
31
|
import { changeLogger } from './changeLogger.js';
|
|
32
32
|
import { chatHistoryService } from './chatHistoryService.js';
|
|
33
33
|
import { gatherContext, routeIntent, evaluateExecutionComplexity } from './contextAgent.js';
|
|
34
34
|
import { verifyChangedFiles } from './verificationService.js';
|
|
35
|
-
import {
|
|
35
|
+
import { buildTieredContextInjection, CONTEXT_BUDGET_CONFIG } from '../utils/contextPrompts.js';
|
|
36
36
|
import { registerContextFiles } from '../utils/fileReadGuard.js';
|
|
37
37
|
import { historyText } from '../utils/historyPrompt.js';
|
|
38
38
|
import { loadUserProfile, formatUserProfileForContext, extractAndSaveUserInsights } from './userProfileService.js';
|
|
@@ -43,6 +43,8 @@ import { handleSlashCommand } from './agent/slashCommands.js';
|
|
|
43
43
|
import { getMetricCollector } from './metrics.js';
|
|
44
44
|
import { Orchestrator } from './orchestration/orchestrator.js';
|
|
45
45
|
import { isSubAgentsEnabled, getApprovalMode } from './agent-tools.js';
|
|
46
|
+
import { resetSessionSettingsToDefault } from './sessionSettings.js';
|
|
47
|
+
import { clearFileReadCache } from '../utils/fileReadCache.js';
|
|
46
48
|
import { runWithAgentId } from '../utils/asyncContext.js';
|
|
47
49
|
// Export submodules for potential external uses if required
|
|
48
50
|
export { AsyncInputHandler } from './agent/inputHandler.js';
|
|
@@ -117,6 +119,8 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
117
119
|
changeLogger.init(workspaceRoot);
|
|
118
120
|
chatHistoryService.init(workspaceRoot);
|
|
119
121
|
const chat = createSharedChatSession();
|
|
122
|
+
resetSessionSettingsToDefault(chat);
|
|
123
|
+
clearFileReadCache();
|
|
120
124
|
const inputHandler = new AsyncInputHandler();
|
|
121
125
|
const chatSessionState = {
|
|
122
126
|
id: crypto.randomUUID(),
|
|
@@ -160,6 +164,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
160
164
|
chatSessionState.modelUsageCounts = {};
|
|
161
165
|
chatSessionState.latestUsageMetadata = undefined;
|
|
162
166
|
chatSessionState.previousUsageMetadata = undefined;
|
|
167
|
+
resetSessionSettingsToDefault(chat);
|
|
163
168
|
chat.setSessionInfo(chatSessionState.id, workspaceRoot);
|
|
164
169
|
}
|
|
165
170
|
inputHandler.stop();
|
|
@@ -359,21 +364,22 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
359
364
|
if (gatherRes.chainedMessages.length > 0) {
|
|
360
365
|
const chainedContent = gatherRes.chainedMessages.join('\n');
|
|
361
366
|
const newIntent = await routeIntent(chainedContent, '', ac.signal);
|
|
362
|
-
if (gatherRes.
|
|
363
|
-
//
|
|
364
|
-
effectiveTargetAgent =
|
|
367
|
+
if (gatherRes.targetAgent === 'EXECUTE') {
|
|
368
|
+
// EXECUTE permission sets are immutable during follow-ups to prevent downgrades
|
|
369
|
+
effectiveTargetAgent = 'EXECUTE';
|
|
370
|
+
}
|
|
371
|
+
else if (gatherRes.targetAgent === 'RESEARCH') {
|
|
372
|
+
// RESEARCH sessions stay in RESEARCH mode during chained requests
|
|
373
|
+
// Chained requests are incorporated into the research context for comprehensive investigation
|
|
374
|
+
effectiveTargetAgent = 'RESEARCH';
|
|
365
375
|
}
|
|
366
376
|
else {
|
|
367
|
-
// If
|
|
368
|
-
if (
|
|
369
|
-
// EXECUTE permission sets are immutable during follow-ups to prevent downgrades
|
|
377
|
+
// If in CHAT mode, escalate if chained instructions dictate
|
|
378
|
+
if (newIntent.targetAgent === 'EXECUTE') {
|
|
370
379
|
effectiveTargetAgent = 'EXECUTE';
|
|
371
380
|
}
|
|
372
|
-
else {
|
|
373
|
-
|
|
374
|
-
if (newIntent.targetAgent === 'EXECUTE' || newIntent.needsContext) {
|
|
375
|
-
effectiveTargetAgent = 'EXECUTE';
|
|
376
|
-
}
|
|
381
|
+
else if (newIntent.targetAgent === 'RESEARCH') {
|
|
382
|
+
effectiveTargetAgent = 'RESEARCH';
|
|
377
383
|
}
|
|
378
384
|
}
|
|
379
385
|
finalInput += `\n\n[USER FOLLOW-UP INSTRUCTIONS SENT DURING INVESTIGATION]:\n${chainedContent}\n\nPlease incorporate these instructions into your work. Address them appropriately, but ensure you do not lose track of the original request's primary objective.`;
|
|
@@ -383,16 +389,23 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
383
389
|
process.stdout.write('\x1b[2K\r');
|
|
384
390
|
return;
|
|
385
391
|
}
|
|
386
|
-
// Hot-swap the underlying LLM system instruction context depending on intent (conversational vs execution)
|
|
392
|
+
// Hot-swap the underlying LLM system instruction context depending on intent (conversational vs research vs execution)
|
|
387
393
|
debugLog(`Intent Router output: original targetAgent = ${gatherRes.targetAgent}, effective = ${effectiveTargetAgent}`);
|
|
388
|
-
const config = effectiveTargetAgent === 'CHAT'
|
|
394
|
+
const config = effectiveTargetAgent === 'CHAT'
|
|
395
|
+
? getGeneralChatConfig()
|
|
396
|
+
: effectiveTargetAgent === 'RESEARCH'
|
|
397
|
+
? getResearchConfig()
|
|
398
|
+
: getPlanExecutionConfig();
|
|
389
399
|
let dynamicSystemInstruction = config.systemInstruction;
|
|
390
400
|
// Inject gathered workspace directories, dependency configurations, and matching search patterns
|
|
391
401
|
if (gatherRes.contextResult) {
|
|
392
402
|
// Compress each relevant file individually using helper to avoid nested loop warning
|
|
393
403
|
gatherRes.contextResult.relevantFiles = await compressContextFiles(workspaceRoot, gatherRes.contextResult, ac.signal);
|
|
394
|
-
// Assemble the final context injection string
|
|
395
|
-
const contextInjection =
|
|
404
|
+
// Assemble the final context injection string using 3-tier IR/graph context ranking
|
|
405
|
+
const contextInjection = buildTieredContextInjection(gatherRes.contextResult, {
|
|
406
|
+
prompt: userInput,
|
|
407
|
+
maxChars: CONTEXT_BUDGET_CONFIG.DEFAULT_CONTEXT_MAX_CHARS,
|
|
408
|
+
});
|
|
396
409
|
debugLog(`Final compressed context injection size: ${contextInjection.length} chars`);
|
|
397
410
|
// Pre-register all injected files with the read-guard so the execution
|
|
398
411
|
// agent is allowed to modify them without calling read_file first.
|
|
@@ -492,6 +505,9 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
492
505
|
if (effectiveTargetAgent === 'CHAT') {
|
|
493
506
|
selectedModel = GEMINI_MODELS.FLASH_LITE;
|
|
494
507
|
}
|
|
508
|
+
else if (effectiveTargetAgent === 'RESEARCH') {
|
|
509
|
+
selectedModel = GEMINI_MODELS.FLASH;
|
|
510
|
+
}
|
|
495
511
|
else {
|
|
496
512
|
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
497
513
|
spinner.start(pc.blue('🧠 Evaluating execution complexity...'));
|
|
@@ -658,6 +674,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
658
674
|
creditsRemaining: usage.remainingBalance,
|
|
659
675
|
lastTurnDuration: turnDuration,
|
|
660
676
|
modelName: chat.getModel(),
|
|
677
|
+
thinkingLevel: (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
|
|
678
|
+
getModelThinkingLevel(chat.getModel()),
|
|
661
679
|
totalTokens: chatSessionState.totalTokens,
|
|
662
680
|
totalInputTokens: chatSessionState.totalInputTokens,
|
|
663
681
|
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
@@ -665,6 +683,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
665
683
|
gitBranch: gitBranch || undefined,
|
|
666
684
|
autoApprove: getApprovalMode() === 'skip-all',
|
|
667
685
|
subAgents: isSubAgentsEnabled(),
|
|
686
|
+
debugMode: isDebugOn(),
|
|
668
687
|
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
669
688
|
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
670
689
|
modelUsageCounts: chatSessionState.modelUsageCounts,
|
|
@@ -822,6 +841,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
822
841
|
creditsRemaining: latestUsage?.remainingBalance,
|
|
823
842
|
lastTurnDuration: turnDuration,
|
|
824
843
|
modelName: chat.getModel(),
|
|
844
|
+
thinkingLevel: (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
|
|
845
|
+
getModelThinkingLevel(chat.getModel()),
|
|
825
846
|
totalTokens: chatSessionState.totalTokens,
|
|
826
847
|
totalInputTokens: chatSessionState.totalInputTokens,
|
|
827
848
|
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
@@ -829,6 +850,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
829
850
|
gitBranch: gitBranch || undefined,
|
|
830
851
|
autoApprove: getApprovalMode() === 'skip-all',
|
|
831
852
|
subAgents: isSubAgentsEnabled(),
|
|
853
|
+
debugMode: isDebugOn(),
|
|
832
854
|
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
833
855
|
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
834
856
|
modelUsageCounts: chatSessionState.modelUsageCounts,
|
|
@@ -847,6 +869,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
847
869
|
creditsRemaining: latestUsage?.remainingBalance,
|
|
848
870
|
lastTurnDuration: turnDuration,
|
|
849
871
|
modelName: chat.getModel(),
|
|
872
|
+
thinkingLevel: (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
|
|
873
|
+
getModelThinkingLevel(chat.getModel()),
|
|
850
874
|
totalTokens: chatSessionState.totalTokens,
|
|
851
875
|
totalInputTokens: chatSessionState.totalInputTokens,
|
|
852
876
|
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
@@ -854,6 +878,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
854
878
|
gitBranch: gitBranch || undefined,
|
|
855
879
|
autoApprove: getApprovalMode() === 'skip-all',
|
|
856
880
|
subAgents: isSubAgentsEnabled(),
|
|
881
|
+
debugMode: isDebugOn(),
|
|
857
882
|
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
858
883
|
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
859
884
|
modelUsageCounts: chatSessionState.modelUsageCounts,
|
|
@@ -1201,7 +1226,7 @@ If you are not done, please continue working using your other tools.`;
|
|
|
1201
1226
|
.join('\n\n');
|
|
1202
1227
|
debugLog(`Verification failed on attempt ${correctionAttempts}/${MAX_CORRECTIONS}. Issues:\n${combinedIssuesForAI}`);
|
|
1203
1228
|
// Compile compilation and syntax diagnostic warnings into an auto-correction prompt
|
|
1204
|
-
const correctionPrompt = `AUTOMATED SYSTEM CHECK: Your previous changes resulted in the following issues:\n\n${combinedIssuesForAI}\n\
|
|
1229
|
+
const correctionPrompt = `AUTOMATED SYSTEM CHECK: Your previous changes resulted in the following issues:\n\n${combinedIssuesForAI}\n\nCRITICAL ANTI-CHEATING RULE: You MUST fix these issues head-on in the application source code. You are STRICTLY FORBIDDEN from modifying build configurations (e.g. next.config.*, tsconfig.json, vite.config.*, .eslintrc*, package.json scripts) to ignore, disable, or bypass build, compile, type, or lint errors (such as ignoreBuildErrors, ignoreDuringBuilds, or turning off checks). You are also forbidden from adding blanket suppressions like @ts-ignore, @ts-nocheck, or eslint-disable to silence errors. Fix the underlying root cause in the actual code.`;
|
|
1205
1230
|
// Cooling-off delay before initiating the self-correction turn
|
|
1206
1231
|
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.CORRECTION_TURN_MS));
|
|
1207
1232
|
if (signal.aborted)
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export declare function setModelThinkingLevel(model: string, level: ThinkingLeve
|
|
|
18
18
|
* Resets user-configured thinking levels back to default model presets.
|
|
19
19
|
*/
|
|
20
20
|
export declare function resetModelThinkingLevels(): void;
|
|
21
|
+
export declare const HISTORICAL_TOOL_OUTPUT_THRESHOLD = 1500;
|
|
22
|
+
export declare const COLLAPSED_TOOL_OUTPUT_MARKER = "\n... [Historical tool output collapsed to save context]";
|
|
23
|
+
export declare function collapseHistoricalOutput(val: string, threshold?: number): string;
|
|
21
24
|
export declare class ProxyChatSession {
|
|
22
25
|
private history;
|
|
23
26
|
private fullHistory;
|
|
@@ -114,6 +117,12 @@ export declare function getPlanExecutionConfig(): {
|
|
|
114
117
|
functionDeclarations: import("@google/generative-ai").FunctionDeclaration[];
|
|
115
118
|
}[];
|
|
116
119
|
};
|
|
120
|
+
export declare function getResearchConfig(): {
|
|
121
|
+
systemInstruction: string;
|
|
122
|
+
tools: {
|
|
123
|
+
functionDeclarations: import("@google/generative-ai").FunctionDeclaration[];
|
|
124
|
+
}[];
|
|
125
|
+
};
|
|
117
126
|
/**
|
|
118
127
|
* Compresses a large string of text using Gemini Flash.
|
|
119
128
|
* Used for shrinking context payloads to prevent OOM/choking.
|
package/dist/services/ai.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getMetricCollector } from './metrics.js';
|
|
|
5
5
|
import { getAuthorizedIdToken, checkByokSubscription } from './auth.js';
|
|
6
6
|
import { debugLog } from '../utils/logger.js';
|
|
7
7
|
import { readCache, writeCache } from '../utils/projectStorage.js';
|
|
8
|
-
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_SEMANTIC_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, USER_PROFILE_EXTRACTOR_SYSTEM_INSTRUCTION, TRIVIAL_MESSAGE_CLASSIFIER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
8
|
+
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, RESEARCH_AGENT_SYSTEM_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_SEMANTIC_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, USER_PROFILE_EXTRACTOR_SYSTEM_INSTRUCTION, TRIVIAL_MESSAGE_CLASSIFIER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
9
9
|
import { workspaceRegistry } from './workspaceRegistry.js';
|
|
10
10
|
import { loadCredentials } from '../utils/credentialStore.js';
|
|
11
11
|
import { ProxyClient } from './proxyClient.js';
|
|
@@ -100,9 +100,9 @@ const MAX_HISTORY_ENTRIES = 500;
|
|
|
100
100
|
* payload stays within sane memory bounds.
|
|
101
101
|
*/
|
|
102
102
|
const MAX_PART_TEXT_LENGTH = 60_000;
|
|
103
|
-
const HISTORICAL_TOOL_OUTPUT_THRESHOLD = 1500;
|
|
104
|
-
const COLLAPSED_TOOL_OUTPUT_MARKER = '\n... [Historical tool output collapsed to save context]';
|
|
105
|
-
function collapseHistoricalOutput(val, threshold = HISTORICAL_TOOL_OUTPUT_THRESHOLD) {
|
|
103
|
+
export const HISTORICAL_TOOL_OUTPUT_THRESHOLD = 1500;
|
|
104
|
+
export const COLLAPSED_TOOL_OUTPUT_MARKER = '\n... [Historical tool output collapsed to save context]';
|
|
105
|
+
export function collapseHistoricalOutput(val, threshold = HISTORICAL_TOOL_OUTPUT_THRESHOLD) {
|
|
106
106
|
if (typeof val !== 'string')
|
|
107
107
|
return String(val ?? '');
|
|
108
108
|
if (val.length <= threshold || val.includes('[Historical tool output collapsed')) {
|
|
@@ -354,20 +354,10 @@ export class ProxyChatSession {
|
|
|
354
354
|
const funcResp = part.functionResponse;
|
|
355
355
|
if (funcResp.response && typeof funcResp.response === 'object') {
|
|
356
356
|
const respObj = funcResp.response;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
respObj.error = collapseHistoricalOutput(respObj.error, threshold);
|
|
362
|
-
}
|
|
363
|
-
if (typeof respObj.result === 'string') {
|
|
364
|
-
respObj.result = collapseHistoricalOutput(respObj.result, threshold);
|
|
365
|
-
}
|
|
366
|
-
if (typeof respObj.stdout === 'string') {
|
|
367
|
-
respObj.stdout = collapseHistoricalOutput(respObj.stdout, threshold);
|
|
368
|
-
}
|
|
369
|
-
if (typeof respObj.stderr === 'string') {
|
|
370
|
-
respObj.stderr = collapseHistoricalOutput(respObj.stderr, threshold);
|
|
357
|
+
for (const key of Object.keys(respObj)) {
|
|
358
|
+
if (typeof respObj[key] === 'string') {
|
|
359
|
+
respObj[key] = collapseHistoricalOutput(respObj[key], threshold);
|
|
360
|
+
}
|
|
371
361
|
}
|
|
372
362
|
}
|
|
373
363
|
}
|
|
@@ -544,6 +534,12 @@ export function getPlanExecutionConfig() {
|
|
|
544
534
|
tools: [{ functionDeclarations: getToolDeclarations({ isExecutionAgent: true }) }],
|
|
545
535
|
};
|
|
546
536
|
}
|
|
537
|
+
export function getResearchConfig() {
|
|
538
|
+
return {
|
|
539
|
+
systemInstruction: RESEARCH_AGENT_SYSTEM_INSTRUCTION.replace('{{MULTI_WORKSPACE_BLOCK}}', getMultiWorkspaceBlock()),
|
|
540
|
+
tools: [{ functionDeclarations: getToolDeclarations({ isResearchAgent: true }) }],
|
|
541
|
+
};
|
|
542
|
+
}
|
|
547
543
|
/**
|
|
548
544
|
* Compresses a large string of text using Gemini Flash.
|
|
549
545
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
@@ -701,7 +697,17 @@ export function getContextToolDeclarations() {
|
|
|
701
697
|
},
|
|
702
698
|
{
|
|
703
699
|
name: 'run_analysis_script',
|
|
704
|
-
description: 'Write and execute a disposable analysis script to structurally map code in the workspace.
|
|
700
|
+
description: 'Write and execute a disposable analysis script to structurally map code in the workspace. ' +
|
|
701
|
+
'Sandbox Features: ' +
|
|
702
|
+
'(1) Preloaded Helpers: "emitResult(data)" / "__emitResult(data)" sends structured JSON payloads directly to structuredResult without manual stdout parsing; "inspectSymbols(target)" / "inspectObject(target)" inspects functions, classes, and properties. ' +
|
|
703
|
+
'(2) Module & Path Resolution: Automatically inherits tsconfig.json path aliases (@/*), NODE_PATH, and workspace virtualenvs (.venv, venv) for Python. ' +
|
|
704
|
+
'(3) Diagnostics Middleware: Provides actionable [SANDBOX DIAGNOSTIC] advisories for missing imports, ESM/CJS interop, and compiler errors. ' +
|
|
705
|
+
'Use this to get exact line ranges for functions, classes, and variables by leveraging the language\'s native AST parser (e.g., TypeScript compiler API, Python ast module, go/parser). ' +
|
|
706
|
+
'You can also use this to probe the development environment — detecting available runtimes (e.g., node --version, python3 --version), checking if ports are in use, identifying project type (monorepo, package manager), or diagnosing system-level issues (disk space, memory) that may affect execution. ' +
|
|
707
|
+
'For complex investigation tasks, you can write lightweight ML scripts (e.g., TF-IDF cosine similarity to rank file relevance, Z-score outlier detection for anomalous log lines, K-Means clustering, or Naive Bayes classification). ' +
|
|
708
|
+
'Default to "node" for generic math/analysis as a safe baseline, but act like a native inhabitant of the host environment — if Python, Go, Rust, or specialized libraries are available in the project context, leverage the host\'s native runtimes and standard libraries for maximum efficiency. ' +
|
|
709
|
+
'The script is executed from a temporary directory and automatically cleaned up after execution. ' +
|
|
710
|
+
'Use the results to make precise read_file calls with exact startLine/endLine instead of guessing. CRITICAL: Do not use this tool on binary, document, or non-code files (e.g. PDF, image, audio, docx).',
|
|
705
711
|
parameters: {
|
|
706
712
|
type: SchemaType.OBJECT,
|
|
707
713
|
properties: {
|
|
@@ -711,7 +717,7 @@ export function getContextToolDeclarations() {
|
|
|
711
717
|
},
|
|
712
718
|
code: {
|
|
713
719
|
type: SchemaType.STRING,
|
|
714
|
-
description: 'The analysis script code. Should output structured JSON to stdout with structural information (name, type, startLine, endLine for each code element).',
|
|
720
|
+
description: 'The analysis script code. Should output structured JSON to stdout or use emitResult(data) with structural information (name, type, startLine, endLine for each code element).',
|
|
715
721
|
},
|
|
716
722
|
targetFile: {
|
|
717
723
|
type: SchemaType.STRING,
|
|
@@ -794,7 +800,7 @@ export function createIntentRouterSession() {
|
|
|
794
800
|
},
|
|
795
801
|
agent: {
|
|
796
802
|
type: SchemaType.STRING,
|
|
797
|
-
enum: ['CHAT', 'EXECUTE'],
|
|
803
|
+
enum: ['CHAT', 'RESEARCH', 'EXECUTE'],
|
|
798
804
|
description: 'Target agent to route the request to',
|
|
799
805
|
},
|
|
800
806
|
},
|
|
@@ -1109,6 +1115,50 @@ export function createUserProfileExtractorSession() {
|
|
|
1109
1115
|
},
|
|
1110
1116
|
description: 'Observed cognitive decision-making, collaboration, and problem-solving traits',
|
|
1111
1117
|
},
|
|
1118
|
+
conversationalPersona: {
|
|
1119
|
+
type: SchemaType.OBJECT,
|
|
1120
|
+
properties: {
|
|
1121
|
+
relationshipModel: {
|
|
1122
|
+
type: SchemaType.STRING,
|
|
1123
|
+
description: 'e.g., "collaborative-peer", "command-operator", "rubber-duck", "socratic-explorer"',
|
|
1124
|
+
},
|
|
1125
|
+
banterAffinity: {
|
|
1126
|
+
type: SchemaType.STRING,
|
|
1127
|
+
description: 'e.g., "witty-banter", "dry-professional", "warm-encouraging"',
|
|
1128
|
+
},
|
|
1129
|
+
formalityLevel: {
|
|
1130
|
+
type: SchemaType.STRING,
|
|
1131
|
+
description: 'e.g., "casual-slang", "telegraphic-concise", "polite-cordial"',
|
|
1132
|
+
},
|
|
1133
|
+
apologyTolerance: {
|
|
1134
|
+
type: SchemaType.STRING,
|
|
1135
|
+
description: 'e.g., "zero-apologies", "tolerant-empathetic"',
|
|
1136
|
+
},
|
|
1137
|
+
stressCadence: {
|
|
1138
|
+
type: SchemaType.STRING,
|
|
1139
|
+
description: 'e.g., "urgent-surgical", "calm-exploratory"',
|
|
1140
|
+
},
|
|
1141
|
+
promptingHabit: {
|
|
1142
|
+
type: SchemaType.STRING,
|
|
1143
|
+
description: 'e.g., "code-dump-deducer", "bulleted-architect", "stream-of-consciousness", "rapid-breadcrumbs"',
|
|
1144
|
+
},
|
|
1145
|
+
conversationalQuirks: {
|
|
1146
|
+
type: SchemaType.ARRAY,
|
|
1147
|
+
items: {
|
|
1148
|
+
type: SchemaType.STRING,
|
|
1149
|
+
},
|
|
1150
|
+
description: 'Observed recurring catchphrases, quirks, or unique conversational habits',
|
|
1151
|
+
},
|
|
1152
|
+
removeQuirks: {
|
|
1153
|
+
type: SchemaType.ARRAY,
|
|
1154
|
+
items: {
|
|
1155
|
+
type: SchemaType.STRING,
|
|
1156
|
+
},
|
|
1157
|
+
description: 'Outdated or superseded conversational quirks to prune',
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
description: 'Observed interpersonal, psychological, and conversational pairing dynamics',
|
|
1161
|
+
},
|
|
1112
1162
|
strengths: {
|
|
1113
1163
|
type: SchemaType.ARRAY,
|
|
1114
1164
|
items: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Content } from '@google/generative-ai';
|
|
2
|
+
import type { ThinkingLevel } from '../utils/config.js';
|
|
2
3
|
/**
|
|
3
4
|
* Options for configuring dynamic history pruning.
|
|
4
5
|
*/
|
|
@@ -32,6 +33,8 @@ export interface ChatSessionData {
|
|
|
32
33
|
lastTurnDuration?: string;
|
|
33
34
|
/** Optional name of the AI model used during this session (e.g., "gemini-1.5-pro"). */
|
|
34
35
|
modelName?: string;
|
|
36
|
+
/** Optional thinking reasoning level used during this session (e.g., "LOW", "MEDIUM", "HIGH"). */
|
|
37
|
+
thinkingLevel?: ThinkingLevel;
|
|
35
38
|
/** Optional total cumulative tokens consumed during the session. */
|
|
36
39
|
totalTokens?: number;
|
|
37
40
|
/** Optional total cumulative input tokens (prompt + cached) consumed during the session. */
|
|
@@ -46,6 +49,8 @@ export interface ChatSessionData {
|
|
|
46
49
|
autoApprove?: boolean;
|
|
47
50
|
/** Optional flag indicating whether sub-agents were enabled or active during this session. */
|
|
48
51
|
subAgents?: boolean;
|
|
52
|
+
/** Optional flag indicating whether debug diagnostic logging was enabled in this session. */
|
|
53
|
+
debugMode?: boolean;
|
|
49
54
|
/** Optional metadata about the token usage from the very last turn. */
|
|
50
55
|
latestUsageMetadata?: any;
|
|
51
56
|
/** Optional metadata about the token usage from the turn prior to the last turn. */
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
import type { TierPartitionResult } from '../utils/contextRanker.js';
|
|
2
|
+
export interface ContextFileEntry {
|
|
3
|
+
text: string;
|
|
4
|
+
inlineData?: any;
|
|
5
|
+
scoped?: boolean;
|
|
6
|
+
tier?: 1 | 2 | 3;
|
|
7
|
+
outline?: string;
|
|
8
|
+
}
|
|
1
9
|
export interface ContextAgentResult {
|
|
2
10
|
projectTree: string;
|
|
3
11
|
projectType: string;
|
|
4
|
-
relevantFiles: Map<string,
|
|
5
|
-
text: string;
|
|
6
|
-
inlineData?: any;
|
|
7
|
-
}>;
|
|
12
|
+
relevantFiles: Map<string, ContextFileEntry>;
|
|
8
13
|
summary: string;
|
|
9
14
|
webSearchSummary?: string;
|
|
10
15
|
fromMemoryBank?: boolean;
|
|
11
16
|
isParallel?: boolean;
|
|
17
|
+
tierPartition?: TierPartitionResult;
|
|
12
18
|
}
|
|
13
19
|
export declare function detectProjectType(workspaceRoot: string): Promise<string>;
|
|
20
|
+
import type { TargetAgent } from './agent/types.js';
|
|
21
|
+
export type { TargetAgent } from './agent/types.js';
|
|
14
22
|
export interface IntentRoute {
|
|
15
23
|
needsContext: boolean;
|
|
16
|
-
targetAgent:
|
|
24
|
+
targetAgent: TargetAgent;
|
|
17
25
|
}
|
|
18
26
|
export declare function routeIntent(userRequest: string, chatHistory?: string, abortSignal?: AbortSignal): Promise<IntentRoute>;
|
|
19
27
|
export declare function evaluateExecutionComplexity(userRequest: string, investigationSummary: string | undefined, numRelevantFiles: number, chatHistory?: string, abortSignal?: AbortSignal): Promise<'EASY' | 'HARD'>;
|
|
@@ -22,6 +30,6 @@ export declare function gatherContext(workspaceRoot: string, userRequest: string
|
|
|
22
30
|
waitForPrompt: () => Promise<void>;
|
|
23
31
|
}, abortSignal: AbortSignal, onProgress?: (msg: string) => void, onToolCall?: (msg: string, label: string) => void): Promise<{
|
|
24
32
|
contextResult: ContextAgentResult | null;
|
|
25
|
-
targetAgent:
|
|
33
|
+
targetAgent: TargetAgent;
|
|
26
34
|
chainedMessages: string[];
|
|
27
35
|
}>;
|
|
@@ -219,9 +219,11 @@ export async function routeIntent(userRequest, chatHistory = '', abortSignal) {
|
|
|
219
219
|
const text = result.response.text()?.trim() || '{}';
|
|
220
220
|
const parsed = JSON.parse(text);
|
|
221
221
|
debugLog(`Intent Router Parsed: ${JSON.stringify(parsed)}`);
|
|
222
|
+
const rawAgent = parsed.agent?.toUpperCase?.() || parsed.agent;
|
|
223
|
+
const validAgent = rawAgent === 'CHAT' ? 'CHAT' : rawAgent === 'RESEARCH' ? 'RESEARCH' : 'EXECUTE';
|
|
222
224
|
return {
|
|
223
225
|
needsContext: parsed.context === 'SEARCH',
|
|
224
|
-
targetAgent:
|
|
226
|
+
targetAgent: validAgent,
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
catch (e) {
|
|
@@ -557,8 +559,8 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
557
559
|
else if (call.name === 'find_recent_changes') {
|
|
558
560
|
logMsg = ` [Context Agent] Looking for recently modified files`;
|
|
559
561
|
}
|
|
560
|
-
else if (call.name === 'run_analysis_script') {
|
|
561
|
-
logMsg = ` [Context Agent] Running analysis script (${args.language})`;
|
|
562
|
+
else if (call.name === 'run_analysis_script' || call.name === 'run_debug_script') {
|
|
563
|
+
logMsg = ` [Context Agent] Running analysis script (${args.language || 'auto'})`;
|
|
562
564
|
}
|
|
563
565
|
if (onProgress) {
|
|
564
566
|
onProgress(logMsg.trim());
|
|
@@ -745,18 +747,42 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
745
747
|
},
|
|
746
748
|
};
|
|
747
749
|
}
|
|
748
|
-
else if (call.name === 'run_analysis_script') {
|
|
749
|
-
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, {
|
|
750
|
+
else if (call.name === 'run_analysis_script' || call.name === 'run_debug_script') {
|
|
751
|
+
const analysisResult = await runEphemeralScript(workspaceRoot, args.language || 'auto', args.code, {
|
|
750
752
|
abortSignal,
|
|
751
753
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
754
|
+
let rawOutput = '';
|
|
755
|
+
if (analysisResult.exitCode !== 0) {
|
|
756
|
+
rawOutput += `Script failed with exit code ${analysisResult.exitCode}.\n`;
|
|
757
|
+
}
|
|
758
|
+
if (analysisResult.structuredResult !== undefined) {
|
|
759
|
+
const formattedStructured = typeof analysisResult.structuredResult === 'string'
|
|
760
|
+
? analysisResult.structuredResult
|
|
761
|
+
: JSON.stringify(analysisResult.structuredResult, null, 2);
|
|
762
|
+
rawOutput += `[STRUCTURED RESULT]\n${formattedStructured}\n`;
|
|
763
|
+
}
|
|
764
|
+
if (analysisResult.stdout) {
|
|
765
|
+
rawOutput += `[STDOUT]\n${analysisResult.stdout}\n`;
|
|
766
|
+
}
|
|
767
|
+
if (analysisResult.stderr) {
|
|
768
|
+
rawOutput += `[STDERR]\n${analysisResult.stderr}\n`;
|
|
769
|
+
}
|
|
770
|
+
if (!rawOutput.trim()) {
|
|
771
|
+
rawOutput = '(script produced no output)';
|
|
772
|
+
}
|
|
773
|
+
const output = boundToolOutput(rawOutput.trim());
|
|
756
774
|
return {
|
|
757
775
|
functionResponse: {
|
|
758
776
|
name: call.name,
|
|
759
|
-
response: {
|
|
777
|
+
response: {
|
|
778
|
+
output,
|
|
779
|
+
...(analysisResult.structuredResult !== undefined
|
|
780
|
+
? { structuredResult: analysisResult.structuredResult }
|
|
781
|
+
: {}),
|
|
782
|
+
...(analysisResult.exitCode !== 0
|
|
783
|
+
? { error: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}` }
|
|
784
|
+
: {}),
|
|
785
|
+
},
|
|
760
786
|
},
|
|
761
787
|
};
|
|
762
788
|
}
|
|
@@ -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
|
*/
|