// v2.1 - Get session context for automatic context injection
// This tool aggregates memories, knowledge graph, and timeline for session start
import { ToolResult, ToolDefinition } from '../../types/tool.js';
import { MemoryManager } from '../../lib/MemoryManager.js';
export const getSessionContextDefinition: ToolDefinition = {
name: 'get_session_context',
description: `π [μ λν/μΈμ
μμ μ μλ μ€ν κΆμ₯] μ΄μ μΈμ
μ λ©λͺ¨λ¦¬, μ§μ κ·Έλν, μ΅κ·Ό μμ
λ΄μμ ν λ²μ μ‘°νν©λλ€.
μ΄ λꡬλ μλ‘μ΄ λνλ₯Ό μμν λ κ°μ₯ λ¨Όμ μ€ννλ©΄ μ’μ΅λλ€. νλ‘μ νΈμ 컨ν
μ€νΈλ₯Ό λΉ λ₯΄κ² νμ
ν μ μμ΅λλ€.
ν€μλ: μΈμ
μμ, 컨ν
μ€νΈ, μ΄μ μμ
, session start, context, previous work, what did we do
μ¬μ© μμ:
- "μ΄μ μ λ¬΄μ¨ μμ
νμμ§?"
- "νλ‘μ νΈ μ»¨ν
μ€νΈ μλ €μ€"
- "μΈμ
컨ν
μ€νΈ μ‘°ν"`,
inputSchema: {
type: 'object',
properties: {
projectName: {
type: 'string',
description: 'νλ‘μ νΈλͺ
μΌλ‘ νν°λ§ (μ ν)'
},
category: {
type: 'string',
description: 'μΉ΄ν
κ³ λ¦¬λ‘ νν°λ§ (μ ν)'
},
memoryLimit: {
type: 'number',
description: 'μ‘°νν λ©λͺ¨λ¦¬ μ (κΈ°λ³Έκ°: 15)',
default: 15
},
includeGraph: {
type: 'boolean',
description: 'μ§μ κ·Έλν ν¬ν¨ μ¬λΆ (κΈ°λ³Έκ°: true)',
default: true
},
includeTimeline: {
type: 'boolean',
description: 'νμλΌμΈ ν¬ν¨ μ¬λΆ (κΈ°λ³Έκ°: true)',
default: true
},
timeRange: {
type: 'string',
description: 'νμλΌμΈ μ‘°ν λ²μ',
enum: ['1d', '7d', '30d', 'all'],
default: '7d'
}
}
},
annotations: {
title: 'Get Session Context',
audience: ['user', 'assistant'],
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
};
interface GetSessionContextArgs {
projectName?: string;
category?: string;
memoryLimit?: number;
includeGraph?: boolean;
includeTimeline?: boolean;
timeRange?: '1d' | '7d' | '30d' | 'all';
}
export async function getSessionContext(args: GetSessionContextArgs): Promise<ToolResult> {
try {
const {
projectName,
category,
memoryLimit = 15,
includeGraph = true,
includeTimeline = true,
timeRange = '7d'
} = args;
const memoryManager = MemoryManager.getInstance();
const sections: string[] = [];
// Header
sections.push('# π§ μΈμ
컨ν
μ€νΈ\n');
sections.push(`> μ΄μ μΈμ
μ λ©λͺ¨λ¦¬μ μμ
λ΄μμ
λλ€.\n`);
// 1. Memory Statistics
const stats = memoryManager.getStats();
sections.push('## π λ©λͺ¨λ¦¬ ν΅κ³\n');
sections.push(`- **μ΄ λ©λͺ¨λ¦¬**: ${stats.total}κ°`);
const categoryStats = Object.entries(stats.byCategory)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([cat, count]) => `${cat}: ${count}`)
.join(', ');
sections.push(`- **μΉ΄ν
κ³ λ¦¬**: ${categoryStats || 'μμ'}\n`);
// 2. Recent Memories (Priority-sorted)
sections.push('## π μ£Όμ λ©λͺ¨λ¦¬\n');
let memories = memoryManager.list(category);
// Filter by project name if specified
if (projectName) {
memories = memories.filter(m =>
m.key.toLowerCase().includes(projectName.toLowerCase()) ||
m.value.toLowerCase().includes(projectName.toLowerCase()) ||
m.category.toLowerCase().includes(projectName.toLowerCase())
);
}
// Sort by priority (desc) then timestamp (desc)
memories.sort((a, b) => {
const priorityDiff = (b.priority || 0) - (a.priority || 0);
if (priorityDiff !== 0) return priorityDiff;
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
});
const topMemories = memories.slice(0, memoryLimit);
if (topMemories.length === 0) {
sections.push('_μ μ₯λ λ©λͺ¨λ¦¬κ° μμ΅λλ€._\n');
} else {
for (const memory of topMemories) {
const priority = memory.priority ? `β${memory.priority}` : '';
const preview = memory.value.length > 120
? memory.value.substring(0, 120) + '...'
: memory.value;
const date = formatDate(memory.timestamp);
sections.push(`### ${memory.key} ${priority}`);
sections.push(`**[${memory.category}]** | ${date}`);
sections.push(`> ${preview}\n`);
}
}
// 3. Knowledge Graph (if enabled and has relations)
if (includeGraph && memories.length > 0) {
const graph = memoryManager.getMemoryGraph(undefined, 2);
if (graph.edges.length > 0) {
sections.push('## π μ§μ κ·Έλν\n');
// Show key relationships
const relationSummary = summarizeRelations(graph.edges);
sections.push(relationSummary);
// Show clusters
if (graph.clusters.length > 0) {
sections.push('\n**κ΄λ ¨ κ·Έλ£Ή**:');
for (const cluster of graph.clusters.slice(0, 3)) {
sections.push(`- [${cluster.join(' β ')}]`);
}
}
sections.push('');
}
}
// 4. Recent Timeline (if enabled)
if (includeTimeline) {
sections.push('## π
μ΅κ·Ό νμλΌμΈ\n');
const startDate = getStartDate(timeRange);
const timeline = memoryManager.getTimeline(startDate, undefined, 10);
if (timeline.length === 0) {
sections.push('_μ΅κ·Ό νλμ΄ μμ΅λλ€._\n');
} else {
const groupedByDate = groupByDate(timeline);
for (const [date, items] of Object.entries(groupedByDate).slice(0, 5)) {
sections.push(`**${date}**`);
for (const item of (items as any[]).slice(0, 3)) {
sections.push(`- \`${item.key}\`: ${item.value.substring(0, 50)}${item.value.length > 50 ? '...' : ''}`);
}
}
sections.push('');
}
}
// 5. Quick Actions Hint
sections.push('---');
sections.push('## π‘ λ€μ λ¨κ³\n');
sections.push('- νΉμ λ©λͺ¨λ¦¬ μμΈ μ‘°ν: `recall_memory`');
sections.push('- μ λ©λͺ¨λ¦¬ μ μ₯: `save_memory`');
sections.push('- κ·Έλν νμ: `get_memory_graph`');
sections.push('- κ³ κΈ κ²μ: `search_memories_advanced`');
return {
content: [{
type: 'text',
text: sections.join('\n')
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `β μΈμ
컨ν
μ€νΈ μ‘°ν μ€λ₯: ${error instanceof Error ? error.message : 'μ μ μλ μ€λ₯'}`
}]
};
}
}
function formatDate(timestamp: string): string {
try {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'μ€λ';
if (diffDays === 1) return 'μ΄μ ';
if (diffDays < 7) return `${diffDays}μΌ μ `;
if (diffDays < 30) return `${Math.floor(diffDays / 7)}μ£Ό μ `;
return date.toLocaleDateString('ko-KR');
} catch {
return timestamp.substring(0, 10);
}
}
function getStartDate(timeRange: string): string | undefined {
const now = new Date();
switch (timeRange) {
case '1d':
now.setDate(now.getDate() - 1);
break;
case '7d':
now.setDate(now.getDate() - 7);
break;
case '30d':
now.setDate(now.getDate() - 30);
break;
case 'all':
return undefined;
default:
now.setDate(now.getDate() - 7);
}
return now.toISOString();
}
function groupByDate(memories: any[]): Record<string, any[]> {
const grouped: Record<string, any[]> = {};
for (const memory of memories) {
const date = memory.timestamp.substring(0, 10);
if (!grouped[date]) {
grouped[date] = [];
}
grouped[date].push(memory);
}
return grouped;
}
function summarizeRelations(edges: any[]): string {
const relationTypes: Record<string, number> = {};
for (const edge of edges) {
relationTypes[edge.relationType] = (relationTypes[edge.relationType] || 0) + 1;
}
const lines: string[] = [];
// Show top 5 relations
const topEdges = edges.slice(0, 5);
for (const edge of topEdges) {
const arrow = getRelationArrow(edge.relationType);
lines.push(`- ${edge.sourceKey} ${arrow} ${edge.targetKey} (${edge.relationType})`);
}
if (edges.length > 5) {
lines.push(`- _... μΈ ${edges.length - 5}κ°μ κ΄κ³_`);
}
return lines.join('\n');
}
function getRelationArrow(relationType: string): string {
const arrows: Record<string, string> = {
'related_to': 'β',
'depends_on': 'β',
'implements': 'β',
'extends': 'β',
'uses': 'β',
'references': 'β’',
'part_of': 'β'
};
return arrows[relationType] || 'β';
}