import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { type SessionData, type PlannerState, type Workflows, type PlanCycle, type PlanStep } from "./interfaces.js";
import { activeSessions } from "./hub.js";
// Enhanced resource configuration with tool attachments
interface ToolAttachedResource {
resourceName: string;
title?: string;
uri: string;
description: string;
mimeType: string;
attachedToTools: string[]; // Tools that should trigger this resource
handler: (sessionId?: string, toolContext?: ToolContext) => Promise<any>;
}
interface ToolContext {
toolName: string;
arguments: any;
result?: any;
timestamp: number;
sessionId?: string | null;
success: boolean;
}
// Load all JSON files from ./workflows as const workflows_json
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { title } from "process";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const workflowsDir = path.resolve(__dirname, 'workflows');
const workflowFiles = fs.readdirSync(workflowsDir).filter(f => f.endsWith('.json'));
export const workflows_json = workflowFiles.flatMap(file => {
const filePath = path.join(workflowsDir, file);
const data = fs.readFileSync(filePath, 'utf-8');
const parsed = JSON.parse(data);
// If the parsed data is an array, spread it; otherwise, wrap it in an array
return Array.isArray(parsed) ? parsed : [parsed];
});
// Registry of tool-attached resources
export const toolAttachedResources: ToolAttachedResource[] = [];
export function installResourcesFeature(server: McpServer, sessionId?: string) {
console.error(`[installResourcesFeature] Installing with sessionId: ${sessionId}`);
console.error(`[installResourcesFeature] Current active sessions:`, Array.from(activeSessions.keys()));
// Store the current session ID for stdio use
let currentStdioSessionId = sessionId;
// Register session memory resource with auto-updates
const sessionMemoryResource: ToolAttachedResource = {
resourceName: "session-memory",
title: "Session Memory",
uri: "internal://session/memory",
description: "Persistent conversation context and insights with auto-updates",
mimeType: "application/json",
attachedToTools: ["*"], // Attach to all tools for comprehensive memory updates
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
const timestamp = Date.now();
console.error(`[session-memory] Handler called with sessionId: ${sessionId}, toolContext: ${toolContext?.toolName}`);
console.error(`[session-memory] Found session:`, session ? {
hasMemory: !!session.memory,
conversationThreadLength: session.memory?.conversationThread?.length || 0,
memoryKeys: session.memory ? Object.keys(session.memory) : []
} : 'NO SESSION FOUND');
// Initialize memory structure with industry standard I/O patterns
let memory = {
// Clean conversation thread for LLM payloads
conversationThread: [] as Array<{ role: "user" | "assistant" | "system", content: string, timestamp: number }>,
// Core memory streams (for internal tracking)
conversationContext: {
buffer: [] as Array<{ timestamp: number, type: string, content: string, metadata?: any }>,
maxSize: 50, // Reduced buffer size to reduce noise
lastUpdated: timestamp
},
// Persistent insights with streaming updates
insights: {
userPreferences: {} as Record<string, any>,
patterns: [] as Array<{ pattern: string, confidence: number, firstSeen: number, lastSeen: number }>,
keyTopics: [] as Array<{ topic: string, relevance: number, mentions: number }>,
workflowHistory: [] as Array<{ workflow: string, success: boolean, timestamp: number, context: string }>
},
// Real-time state management
currentSession: {
startTime: session?.startTime || timestamp,
chronologicalUseOfTools: [] as string[], // Chronological sequence of unique tool usage
currentFocus: null as string | null,
sessionGoals: [] as string[],
completedTasks: session?.successfulTasks || 0
},
// I/O metadata for streaming and buffering
metadata: {
version: "1.0.0",
lastWrite: timestamp,
totalReads: 0,
totalWrites: 0,
compressionRatio: 1.0,
sessionId: sessionId || null
}
};
// Load existing memory from session if available
if (session?.memory) {
memory = { ...memory, ...session.memory };
memory.metadata.totalReads++;
// Ensure conversationThread exists
if (!memory.conversationThread) {
memory.conversationThread = [];
}
}
// Auto-update memory based on tool context (industry standard event streaming)
if (toolContext) {
memory.metadata.totalWrites++;
memory.metadata.lastWrite = timestamp;
// Add to clean conversation thread for LLM payloads
if (toolContext.toolName === "update_session_memory" && toolContext.arguments) {
const args = toolContext.arguments;
if (args.type === "user_message" || args.type === "assistant_message") {
const role = args.type === "user_message" ? "user" : "assistant";
// Check if this message already exists to prevent duplicates
const existingMessage = memory.conversationThread.find(
msg => msg.content === args.content && Math.abs(msg.timestamp - args.timestamp) < 1000
);
if (!existingMessage) {
memory.conversationThread.push({
role: role as "user" | "assistant",
content: args.content,
timestamp: args.timestamp
});
// Keep conversation thread clean (last 20 exchanges = 40 messages)
if (memory.conversationThread.length > 40) {
memory.conversationThread = memory.conversationThread.slice(-40);
}
}
}
}
// Add to conversation buffer (but only for non-memory-update tools to reduce noise)
if (toolContext.toolName !== "update_session_memory") {
const contextEntry = {
timestamp: toolContext.timestamp,
type: 'tool_execution',
content: `Tool: ${toolContext.toolName}`,
metadata: {
success: toolContext.success,
arguments: toolContext.arguments,
result: toolContext.result ? JSON.stringify(toolContext.result).substring(0, 200) : null
}
};
memory.conversationContext.buffer.push(contextEntry);
}
// Update chronological tool usage (only add if different from last tool)
const lastTool = memory.currentSession.chronologicalUseOfTools[memory.currentSession.chronologicalUseOfTools.length - 1];
if (lastTool !== toolContext.toolName) {
memory.currentSession.chronologicalUseOfTools.push(toolContext.toolName);
}
// Update current focus to the most recent tool
memory.currentSession.currentFocus = toolContext.toolName;
// Maintain ring buffer size (streaming I/O pattern)
if (memory.conversationContext.buffer.length > memory.conversationContext.maxSize) {
memory.conversationContext.buffer = memory.conversationContext.buffer.slice(-memory.conversationContext.maxSize);
}
// Update workflow history - now handled centrally in hub.ts triggerAttachedResources
// Individual resource handlers no longer add workflow entries to prevent duplicates
// The consolidation happens at the session level after all resources are processed
// Pattern recognition and insights extraction
const toolName = toolContext.toolName;
let existingPattern = memory.insights.patterns.find(p => p.pattern === `frequent_tool:${toolName}`);
if (existingPattern) {
existingPattern.confidence += 0.1;
existingPattern.lastSeen = timestamp;
} else {
memory.insights.patterns.push({
pattern: `frequent_tool:${toolName}`,
confidence: 0.1,
firstSeen: timestamp,
lastSeen: timestamp
});
}
// Update current focus based on recent activity
memory.currentSession.currentFocus = toolName;
memory.currentSession.completedTasks = session?.successfulTasks || memory.currentSession.completedTasks || 0;
// Sync session data with memory
if (session && memory.currentSession.completedTasks > session.successfulTasks) {
session.successfulTasks = memory.currentSession.completedTasks;
}
}
// Compress and optimize memory (industry standard data management)
memory.insights.patterns = memory.insights.patterns
.filter(p => p.confidence > 0.05) // Remove low-confidence patterns
.sort((a, b) => b.confidence - a.confidence)
.slice(0, 50); // Keep top 50 patterns
memory.insights.workflowHistory = memory.insights.workflowHistory
.slice(-100); // Keep last 100 workflows
// Calculate compression ratio
const originalSize = JSON.stringify(memory).length;
memory.metadata.compressionRatio = originalSize > 0 ? (originalSize / (originalSize + 100)) : 1.0;
// Save memory back to session (persistent storage pattern)
if (session) {
session.memory = memory;
}
return {
contents: [{
title: "Session Memory",
uri: "internal://session/memory",
mimeType: "application/json",
text: JSON.stringify({
// Stream-friendly summary view
summary: {
totalInteractions: memory.conversationContext.buffer.length,
currentFocus: memory.currentSession.currentFocus,
topPatterns: memory.insights.patterns.slice(0, 5),
recentActivities: memory.conversationContext.buffer.slice(-5),
completedTasks: memory.currentSession.completedTasks,
sessionDuration: Math.round((timestamp - memory.currentSession.startTime) / 1000 / 60) + " minutes"
},
// Full memory context for detailed access
fullContext: memory,
// I/O status for monitoring
ioStatus: {
reads: memory.metadata.totalReads,
writes: memory.metadata.totalWrites,
lastUpdate: new Date(memory.metadata.lastWrite).toISOString(),
bufferUtilization: `${memory.conversationContext.buffer.length}/${memory.conversationContext.maxSize}`,
compressionRatio: memory.metadata.compressionRatio.toFixed(3)
},
timestamp: new Date(timestamp).toISOString(),
sessionId: sessionId || null,
refreshId: timestamp.toString(36)
}, null, 2)
}]
};
}
};
const workFlowsResource: ToolAttachedResource = {
resourceName: "my_workflows",
title: "My Workflows",
uri: "internal://workflows/my_workflows",
description: "Predefined workflows you can run (updates with tool usage). always use endpoint verbatim ie: internal://workflows/image. Then pick one of the plan sequences to use.",
mimeType: "application/json",
attachedToTools: ["*"], // Attach to all tools
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
const timestamp = Date.now();
console.error(`[my_workflows] Handler called with sessionId: ${sessionId}, toolContext: ${toolContext?.toolName}`);
console.error(`[my_workflows] Found session:`, session ? {
hasMemory: !!session.memory,
conversationThreadLength: session.memory?.conversationThread?.length || 0,
} : 'NO SESSION');
// Example predefined workflows
const predefinedWorkflows: Workflows = workflows_json || [];
return {
contents: [{
title: "My Workflows",
uri: "internal://workflows/my_workflows",
mimeType: "application/json",
text: JSON.stringify(predefinedWorkflows, null, 2)
}]
};
}
}
server.resource(
workFlowsResource.resourceName,
workFlowsResource.uri,
{
title: workFlowsResource.title,
description: workFlowsResource.description,
mimeType: workFlowsResource.mimeType,
},
async (uri, extra) => {
// Optionally, you can use sessionId from extra if needed
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
return workFlowsResource.handler(sessionIdToUse);
}
);
// Add workflows resource to global registry
toolAttachedResources.push(workFlowsResource);
// Register session treats resource with tool attachments
const sessionTreatsResource: ToolAttachedResource = {
resourceName: "session-treats",
title: "Session Treats",
uri: "internal://session/treats",
description: "Personalized treats based on your recent activity. Don't call this directly. Auto-updates with tool usage.",
mimeType: "application/json",
attachedToTools: ["brave_search", "github_create_issue", "n8n_execute_workflow", "final_response"], // Attach to these tools
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
const timestamp = Date.now();
console.error(`[session-treats] Handler called with sessionId: ${sessionId}, toolContext: ${toolContext?.toolName}`);
console.error(`[session-treats] Session data:`, session ? {
toolsUsed: session.toolsUsed.length,
successfulTasks: session.successfulTasks,
achievements: session.achievements.length
} : 'NO SESSION');
if (!session) {
return {
contents: [{
title: "Session Treats",
uri: "internal://session/treats",
mimeType: "application/json",
text: JSON.stringify({
message: "Start using tools to earn personalized treats!",
generalEncouragement: "π Welcome! Ready to accomplish great things!",
timestamp: new Date(timestamp).toISOString(),
sessionId: sessionId || null,
}, null, 2)
}]
};
}
// Generate treats based on tools used, with special context if tool was just used
const toolSpecificTreats = generateToolSpecificTreats(session.toolsUsed, toolContext);
const achievementTreats = generateAchievementTreats(session.achievements);
return {
contents: [{
title: "Session Treats",
uri: "internal://session/treats",
mimeType: "application/json",
text: JSON.stringify({
personalizedTreat: toolSpecificTreats[Math.floor(Math.random() * toolSpecificTreats.length)],
toolsUsedCount: session.toolsUsed.length,
successfulTasks: session.successfulTasks,
achievements: session.achievements,
sessionDuration: Math.round((Date.now() - session.startTime) / 1000 / 60) + " minutes",
encouragement: achievementTreats,
// Add context from the tool that just ran
...(toolContext && {
justUsedTool: {
name: toolContext.toolName,
timestamp: new Date(toolContext.timestamp).toISOString(),
success: toolContext.success,
specialMessage: generateContextualMessage(toolContext)
}
}),
timestamp: new Date(timestamp).toISOString(),
sessionId: sessionId || null,
refreshId: timestamp.toString(36)
}, null, 2)
}]
};
}
};
// Register the resource with MCP server
server.resource(
sessionTreatsResource.resourceName,
sessionTreatsResource.uri,
{
title: sessionTreatsResource.title,
description: sessionTreatsResource.description,
mimeType: sessionTreatsResource.mimeType
},
async (uri, extra) => {
// Debug the parameters being passed
console.error(`[session-treats] Resource read - URI: ${uri}`);
console.error(`[session-treats] Resource read - extra:`, extra);
console.error(`[session-treats] Resource read - extra type:`, typeof extra);
// Try to get session ID from multiple sources
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
console.error(`[session-treats] Resource read: sessionId=${sessionIdToUse} (from ${extra?.sessionId ? 'extra' : 'stdio'})`);
console.error(`[session-treats] Available sessions:`, Array.from(activeSessions.keys()));
if (!sessionIdToUse) {
console.error(`[session-treats] No session ID in request context`);
return sessionTreatsResource.handler(undefined);
}
return sessionTreatsResource.handler(sessionIdToUse);
}
);
// Add to global registry
toolAttachedResources.push(sessionTreatsResource);
// Register the memory resource with MCP server
server.resource(
sessionMemoryResource.resourceName,
sessionMemoryResource.uri,
{
title: sessionMemoryResource.title,
description: sessionMemoryResource.description,
mimeType: sessionMemoryResource.mimeType
},
async (uri, extra) => {
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
console.error(`[session-memory] Resource read: sessionId=${sessionIdToUse}`);
console.error(`[session-memory] Extra object:`, extra);
console.error(`[session-memory] CurrentStdioSessionId:`, currentStdioSessionId);
console.error(`[session-memory] Available sessions:`, Array.from(activeSessions.keys()));
if (!sessionIdToUse) {
console.error("[session-memory] No session ID available, using handler without context");
return sessionMemoryResource.handler(undefined);
}
return sessionMemoryResource.handler(sessionIdToUse);
}
);
// Add memory resource to global registry
toolAttachedResources.push(sessionMemoryResource);
// Register analytics resource with different tool attachments
const analyticsResource: ToolAttachedResource = {
resourceName: "session-analytics",
title: "Session Analytics",
uri: "internal://session/analytics",
description: "Your personal tool usage analytics (updates with tool usage)",
mimeType: "application/json",
attachedToTools: ["*"], // Attach to ALL tools
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
console.error(`[session-analytics] Handler called with sessionId: ${sessionId}, toolContext: ${toolContext?.toolName}`);
console.error(`[session-analytics] Session data:`, session ? {
toolsUsed: session.toolsUsed.length,
successfulTasks: session.successfulTasks,
achievements: session.achievements.length,
lastTool: session.toolsUsed[session.toolsUsed.length - 1]
} : 'NO SESSION');
if (!session) {
return {
contents: [{
title: "Session Analytics",
uri: "internal://session/analytics",
mimeType: "application/json",
text: JSON.stringify({
message: "No session data available",
timestamp: new Date().toISOString(),
sessionId: sessionId || null,
}, null, 2)
}]
};
}
// Analyze tool usage patterns
const toolFrequency = session.toolsUsed.reduce((acc: Record<string, number>, tool: string) => {
acc[tool] = (acc[tool] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const mostUsedTool = Object.entries(toolFrequency)
.sort(([, a], [, b]) => (b as number) - (a as number))[0];
return {
contents: [{
uri: "internal://session/analytics",
mimeType: "application/json",
text: JSON.stringify({
totalToolsUsed: session.toolsUsed.length,
uniqueTools: Object.keys(toolFrequency).length,
mostUsedTool: mostUsedTool ? `${mostUsedTool[0]} (${mostUsedTool[1]} times)` : "None yet",
toolFrequency,
successRate: session.toolsUsed.length > 0 ? (session.successfulTasks / session.toolsUsed.length * 100).toFixed(1) + "%" : "N/A",
sessionActive: Math.round((Date.now() - session.startTime) / 1000 / 60) + " minutes",
// Add real-time tool context
...(toolContext && {
lastToolUsed: {
name: toolContext.toolName,
timestamp: new Date(toolContext.timestamp).toISOString(),
success: toolContext.success
}
}),
timestamp: new Date().toISOString(),
sessionId: sessionId || null,
}, null, 2)
}]
};
}
};
server.resource(
analyticsResource.resourceName,
analyticsResource.uri,
{
title: analyticsResource.title,
description: analyticsResource.description,
mimeType: analyticsResource.mimeType
},
async (uri, extra) => {
// Debug the parameters being passed
console.error(`[session-analytics] Resource read - URI: ${uri}`);
console.error(`[session-analytics] Resource read - extra:`, extra);
console.error(`[session-analytics] Resource read - extra type:`, typeof extra);
// Try to get session ID from multiple sources
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
console.error(`[session-analytics] Resource read: sessionId=${sessionIdToUse} (from ${extra?.sessionId ? 'extra' : 'stdio'})`);
if (!sessionIdToUse) {
console.error(`[session-analytics] No session ID in request context`);
return analyticsResource.handler(undefined);
}
return analyticsResource.handler(sessionIdToUse);
}
);
toolAttachedResources.push(analyticsResource);
// Keep the achievement resource as before but with tool attachment
const achievementResource: ToolAttachedResource = {
resourceName: "achievement-rewards",
title: "Achievement Rewards",
uri: "internal://session/achievements",
description: "Rewards based on your achievements (updates when milestones reached)",
mimeType: "application/json",
attachedToTools: ["github_create_issue", "n8n_execute_workflow"], // Only for significant tools
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
const achievements = checkForNewAchievements(session || null);
return {
contents: [{
title: "Achievement Rewards",
uri: "internal://session/achievements",
mimeType: "application/json",
text: JSON.stringify({
newAchievements: achievements,
totalAchievements: session?.achievements.length || 0,
nextMilestone: getNextMilestone(session || null),
specialRewards: getSpecialRewards(session || null),
...(toolContext && achievements.length > 0 && {
unlockedBy: {
tool: toolContext.toolName,
timestamp: new Date(toolContext.timestamp).toISOString()
}
}),
timestamp: new Date().toISOString(),
sessionId: sessionId || null,
}, null, 2)
}]
};
}
};
server.resource(
achievementResource.resourceName,
achievementResource.uri,
{
title: achievementResource.title,
description: achievementResource.description,
mimeType: achievementResource.mimeType
},
async (uri, extra) => {
// Try to get session ID from multiple sources
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
console.error(`[achievement-rewards] Resource read: sessionId=${sessionIdToUse} (from ${extra?.sessionId ? 'extra' : 'stdio'})`);
if (!sessionIdToUse) {
console.error(`[achievement-rewards] No session ID in request context`);
return achievementResource.handler(undefined);
}
return achievementResource.handler(sessionIdToUse);
}
);
toolAttachedResources.push(achievementResource);
// const finalResponseResource: ToolAttachedResource = {
// resourceName: "final-response",
// uri: "internal://session/final-response",
// description: `The context of the tool results returned as a readable response.
// Insightful, conversational tone. Format as markdown.
// Work in sections for example:
// ## Heading
// ### Subheading
// {context in proper markdown for images, links, code blocks, etc.}
// Conclude with a summary or call to action.
// End with a friendly sign-off or next steps suggestion.
// Markdown Examples:
// - use \\n\\n for new paragraphs.
// - use \\n for line breaks.
// - Use **bold** for emphasis.
// - Use _italics_ for subtle emphasis.
// - Use \`inline code\` for code snippets.
// - Use triple backticks for code blocks:
// \`\`\`javascript
// console.log("Hello, world!");
// \`\`\`
// - Use bullet points for lists:
// - Item 1
// - Item 2
// - Use numbered lists for sequences:
// 1. First step
// 2. Second step
// - Use [links](https://example.com) to reference resources.
// - Use > for blockquotes.
// - Use headings (#, ##, ###) to structure content.
// - Include images with .
// - Ensure proper spacing and line breaks for readability.
// `,
// mimeType: "text/markdown",
// attachedToTools: ["final_response"], // Attach to the final_response tool
// handler: async (sessionId?: string, toolContext?: ToolContext) => {
// // Expect the text to be in toolContext.arguments.text
// const text = toolContext?.arguments?.text ?? "";
// return {
// contents: [{
// uri: "internal://session/final-response",
// mimeType: "text/markdown",
// text
// }]
// };
// }
// };
// server.resource(
// finalResponseResource.resourceName,
// finalResponseResource.uri,
// {
// description: finalResponseResource.description,
// mimeType: finalResponseResource.mimeType
// },
// async (uri, extra) => {
// let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
// return finalResponseResource.handler(sessionIdToUse);
// }
// );
// toolAttachedResources.push(finalResponseResource);
// Register session planner resource for dynamic execution planning with auto-advancement
const sessionPlannerResource: ToolAttachedResource = {
resourceName: "session-planner",
title: "Session Planner",
uri: "internal://session/planner",
description: "Dynamic session execution planner with cycles and steps",
mimeType: "application/json",
attachedToTools: ["*"], // Available for ALL tools
handler: async (sessionId?: string, toolContext?: ToolContext) => {
const session = sessionId ? activeSessions.get(sessionId) : null;
// Default planner state
let plannerState: PlannerState = {
cycles: [],
currentCycle: 0,
currentStep: 0,
status: "idle",
lastUpdated: new Date().toISOString(),
sessionId: sessionId || null
};
// If we have session data, check if there's existing planner state
if (session && session.plannerState) {
plannerState = { ...session.plannerState };
}
// Check if this is a reset request (special tool context)
if (toolContext && toolContext.toolName === '__PLANNER_RESET__') {
console.error(`[session-planner] π Resetting planner state for session ${sessionId}`);
plannerState = {
cycles: [],
currentCycle: 0,
currentStep: 0,
status: "idle",
lastUpdated: new Date().toISOString(),
sessionId: sessionId || null
};
// Save reset state back to session
if (session) {
session.plannerState = plannerState;
}
return {
contents: [{
title: "Session Planner",
uri: "internal://session/planner",
mimeType: "application/json",
text: JSON.stringify(plannerState, null, 2)
}]
};
}
// If a tool was just called, handle automatic planner advancement
if (toolContext) {
console.error(`[session-planner] Tool context received: ${toolContext.toolName}`);
// Check if this tool execution corresponds to the current plan step
if (plannerState.cycles.length > 0 && plannerState.status !== 'completed') {
const currentCycle = plannerState.cycles[plannerState.currentCycle];
if (currentCycle && plannerState.currentStep < currentCycle.steps.length) {
const currentStep = currentCycle.steps[plannerState.currentStep];
// Check if the tool that was executed matches the current step
if (currentStep.type === 'tool' && currentStep.target === toolContext.toolName) {
console.error(`[session-planner] β
Tool execution matches plan step ${plannerState.currentCycle}.${plannerState.currentStep}`);
// Auto-advance to next step
plannerState.currentStep++;
// Check if we've completed the current cycle
if (plannerState.currentStep >= currentCycle.steps.length) {
plannerState.currentCycle++;
plannerState.currentStep = 0;
// Check if we've completed all cycles
if (plannerState.currentCycle >= plannerState.cycles.length) {
plannerState.status = 'completed';
console.error(`[session-planner] π All planner cycles completed!`);
} else {
console.error(`[session-planner] β‘οΈ Advanced to cycle ${plannerState.currentCycle}: ${plannerState.cycles[plannerState.currentCycle].name}`);
}
} else {
console.error(`[session-planner] β‘οΈ Advanced to step ${plannerState.currentCycle}.${plannerState.currentStep}`);
}
// Record the successful execution
plannerState.lastExecuted = {
tool: toolContext.toolName,
timestamp: toolContext.timestamp,
success: toolContext.success,
cycleIndex: plannerState.currentCycle,
stepIndex: plannerState.currentStep - 1 // Previous step index
};
}
}
}
// Auto-generate a simple plan if none exists (fallback)
if (plannerState.cycles.length === 0) {
plannerState.cycles = [
// {
// name: "Discovery",
// steps: [
// { type: "resource", target: "internal://session/analytics", description: "Check current session state" },
// { type: "resource", target: "internal://session/treats", description: "Get personalized insights" }
// ]
// },
// {
// name: "Analysis",
// steps: [
// { type: "tool", target: "echo", arguments: { text: "Analysis phase started" }, description: "Begin analysis" }
// ]
// }
] as PlanCycle[];
plannerState.status = "ready";
}
plannerState.lastUpdated = new Date().toISOString();
// Save planner state back to session
if (session) {
session.plannerState = plannerState;
}
}
return {
contents: [{
title: "Session Planner",
uri: "internal://session/planner",
mimeType: "application/json",
text: JSON.stringify(plannerState, null, 2)
}]
};
}
};
server.resource(
sessionPlannerResource.resourceName,
sessionPlannerResource.uri,
{
title: sessionPlannerResource.title,
description: sessionPlannerResource.description,
mimeType: sessionPlannerResource.mimeType
},
async (uri, extra) => {
let sessionIdToUse = extra?.sessionId || currentStdioSessionId;
console.error(`[session-planner] Resource read: sessionId=${sessionIdToUse}`);
if (!sessionIdToUse) {
console.error(`[session-planner] No session ID in request context`);
return sessionPlannerResource.handler(undefined);
}
return sessionPlannerResource.handler(sessionIdToUse);
}
);
toolAttachedResources.push(sessionPlannerResource);
}
// Enhanced helper functions
function generateToolSpecificTreats(toolsUsed: string[], toolContext?: ToolContext): string[] {
const baseTreats = [
"πͺ Nice work with those tools!",
"π Your tool mastery is improving!",
"β Excellent tool selection!"
];
// Add context-specific treats if we have tool context
if (toolContext) {
switch (toolContext.toolName) {
case "brave_search":
baseTreats.push(`π Just searched! Your research skills are on point!`);
break;
case "github_create_issue":
baseTreats.push(`π GitHub issue created! You're mastering project management!`);
break;
case "n8n_execute_workflow":
baseTreats.push(`π Workflow executed! Automation wizard in action!`);
break;
case "echo":
baseTreats.push(`π’ Echo used! Testing tools like a pro!`);
break;
}
}
// Original tool-based treats
if (toolsUsed.includes('github_create_issue')) {
baseTreats.push("π GitHub ninja! You're mastering issue creation!");
}
if (toolsUsed.includes('brave_search')) {
baseTreats.push("π Research expert! Your search skills are on point!");
}
if (toolsUsed.includes('n8n_execute_workflow')) {
baseTreats.push("π Automation wizard! n8n workflows are your specialty!");
}
return baseTreats;
}
function generateContextualMessage(toolContext: ToolContext): string {
const messages = {
"brave_search": `π You just searched for something! Knowledge is power!`,
"github_create_issue": `π New GitHub issue created! Great project management!`,
"n8n_execute_workflow": `π Workflow executed successfully! Automation FTW!`,
"echo": `π’ Echo test completed! System check passed!`
};
return messages[toolContext.toolName as keyof typeof messages] ||
`β¨ Tool "${toolContext.toolName}" used ${toolContext.success ? 'successfully' : 'with issues'}!`;
}
// Keep existing helper functions...
function generateAchievementTreats(achievements: string[]): string {
if (achievements.length === 0) return "π± Just getting started - exciting things ahead!";
if (achievements.length < 5) return "π― Building momentum - keep it up!";
if (achievements.length < 10) return "π Achievement hunter - you're on fire!";
return "π Master achiever - absolutely incredible!";
}
function checkForNewAchievements(session: SessionData | null): string[] {
if (!session) return [];
const newAchievements: string[] = [];
if (session.toolsUsed.length >= 5 && !session.achievements.includes('tool-explorer')) {
newAchievements.push('tool-explorer');
session.achievements.push('tool-explorer');
}
if (session.successfulTasks >= 10 && !session.achievements.includes('task-master')) {
newAchievements.push('task-master');
session.achievements.push('task-master');
}
return newAchievements;
}
function getNextMilestone(session: SessionData | null): string {
if (!session) return "Use your first tool!";
if (session.toolsUsed.length < 5) return `Use ${5 - session.toolsUsed.length} more tools to unlock Tool Explorer badge`;
if (session.successfulTasks < 10) return `Complete ${10 - session.successfulTasks} more tasks to unlock Task Master badge`;
return "You're crushing all milestones! π";
}
function getSpecialRewards(session: SessionData | null): string[] {
if (!session) return [];
const rewards: string[] = [];
if (session.toolsUsed.length >= 20) {
rewards.push("π Power User Bonus: Access to advanced analytics!");
}
if (session.successfulTasks >= 50) {
rewards.push("π Expert Level: Unlock premium encouragement messages!");
}
return rewards;
}