@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
327 lines (325 loc) • 9.3 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { logger } from "../../../core/monitoring/logger.js";
class TraceHandlers {
constructor(deps) {
this.deps = deps;
}
/**
* Get traces with optional filtering
*/
async handleGetTraces(args) {
if (args.analyze) {
return this.handleAnalyzeTraces(args);
}
try {
const {
limit = 20,
pattern,
start_time,
end_time,
include_context = false
} = args;
const filters = { limit };
if (pattern) {
filters.pattern = pattern;
}
if (start_time) {
filters.startTime = new Date(start_time);
}
if (end_time) {
filters.endTime = new Date(end_time);
}
const traces = await this.deps.traceDetector.getTraces();
if (traces.length === 0) {
return {
content: [
{
type: "text",
text: "No traces found matching criteria"
}
]
};
}
const tracesSummary = traces.map((trace) => {
const duration = trace.metadata.endTime && trace.metadata.startTime ? trace.metadata.endTime - trace.metadata.startTime : "ongoing";
return {
id: trace.id,
pattern: trace.compressed?.pattern || "Unknown",
toolCount: trace.tools.length,
duration: typeof duration === "number" ? `${duration}ms` : duration,
status: "completed",
startTime: new Date(trace.metadata.startTime).toISOString()
};
});
const summaryText = tracesSummary.map(
(t) => `${t.id}: ${t.pattern} (${t.toolCount} tools, ${t.duration}) [${t.status}]`
).join("\n");
const result = {
content: [
{
type: "text",
text: `Traces (${traces.length}):
${summaryText}`
}
],
metadata: {
traces: tracesSummary,
totalCount: traces.length,
filters
}
};
if (include_context) {
result.metadata.fullTraces = traces;
}
return result;
} catch (error) {
logger.error(
"Error getting traces",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* Analyze trace patterns
*/
async handleAnalyzeTraces(args) {
try {
const {
trace_id,
analysis_type = "performance",
include_recommendations = true
} = args;
let analysis;
if (trace_id) {
const traces = this.deps.traceDetector.getTraces();
const trace = traces.find((t) => t.id === trace_id);
if (!trace) {
throw new Error(`Trace not found: ${trace_id}`);
}
analysis = this.analyzeTrace(trace, analysis_type);
} else {
const traces = this.deps.traceDetector.getTraces();
analysis = this.analyzeRecentTraces(traces, analysis_type);
}
let analysisText = `Trace Analysis (${analysis_type}):
`;
switch (analysis_type) {
case "performance":
analysisText += `Performance Metrics:
- Avg duration: ${analysis.avgDuration}ms
- Slowest operation: ${analysis.slowestOperation?.name} (${analysis.slowestOperation?.duration}ms)
- Tool usage: ${analysis.toolUsageStats}
- Bottlenecks: ${analysis.bottlenecks?.join(", ") || "None detected"}`;
break;
case "patterns":
analysisText += `Pattern Analysis:
- Common sequences: ${analysis.commonSequences?.join(", ") || "None"}
- Repetitive operations: ${analysis.repetitiveOps?.join(", ") || "None"}
- Success rate: ${analysis.successRate}%
- Failure patterns: ${analysis.failurePatterns?.join(", ") || "None"}`;
break;
case "errors":
analysisText += `Error Analysis:
- Error rate: ${analysis.errorRate}%
- Common errors: ${analysis.commonErrors?.join(", ") || "None"}
- Error sources: ${analysis.errorSources?.join(", ") || "None"}
- Recovery patterns: ${analysis.recoveryPatterns?.join(", ") || "None"}`;
break;
default:
analysisText += JSON.stringify(analysis, null, 2);
}
if (include_recommendations && analysis.recommendations) {
analysisText += "\n\nRecommendations:\n";
analysisText += analysis.recommendations.map((rec, i) => `${i + 1}. ${rec}`).join("\n");
}
return {
content: [
{
type: "text",
text: analysisText
}
],
metadata: {
analysis,
analysisType: analysis_type,
traceId: trace_id
}
};
} catch (error) {
logger.error(
"Error analyzing traces",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* Start browser debugging session
*/
async handleStartBrowserDebug(args) {
try {
const {
url,
headless = false,
width = 1280,
height = 720,
capture_screenshots = true
} = args;
if (!url) {
throw new Error("URL is required for browser debugging");
}
const sessionId = `session_${Date.now()}`;
logger.info(`Would navigate session ${sessionId} to ${url}`);
logger.info("Started browser debug session", { sessionId, url });
return {
content: [
{
type: "text",
text: `Started browser debug session: ${sessionId}
Navigated to: ${url}`
}
],
metadata: {
sessionId,
url,
options: { headless, width, height, capture_screenshots }
}
};
} catch (error) {
logger.error(
"Error starting browser debug session",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* Take screenshot for debugging
*/
async handleTakeScreenshot(args) {
try {
const { session_id, selector, full_page = false } = args;
if (!session_id) {
throw new Error("Session ID is required");
}
const screenshot = { data: "mock-screenshot-data", format: "png" };
return {
content: [
{
type: "text",
text: "Screenshot captured successfully"
},
{
type: "image",
data: screenshot.data,
mimeType: "image/png"
}
],
metadata: {
sessionId: session_id,
selector,
fullPage: full_page,
timestamp: Date.now()
}
};
} catch (error) {
logger.error(
"Error taking screenshot",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* Execute JavaScript in browser for debugging
*/
async handleExecuteScript(args) {
try {
const { session_id, script, args: _scriptArgs = [] } = args;
if (!session_id) {
throw new Error("Session ID is required");
}
if (!script) {
throw new Error("Script is required");
}
const result = { output: "Mock script execution result" };
return {
content: [
{
type: "text",
text: `Script executed successfully:
Result: ${JSON.stringify(result, null, 2)}`
}
],
metadata: {
sessionId: session_id,
script,
result,
timestamp: Date.now()
}
};
} catch (error) {
logger.error(
"Error executing script",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* Stop browser debugging session
*/
async handleStopBrowserDebug(args) {
try {
const { session_id } = args;
if (!session_id) {
throw new Error("Session ID is required");
}
logger.info(`Would close session ${session_id}`);
logger.info("Stopped browser debug session", { sessionId: session_id });
return {
content: [
{
type: "text",
text: `Stopped browser debug session: ${session_id}`
}
],
metadata: {
sessionId: session_id,
timestamp: Date.now()
}
};
} catch (error) {
logger.error(
"Error stopping browser debug session",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
analyzeTrace(trace, analysisType) {
return {
type: analysisType,
summary: `Analysis of trace ${trace.id}`,
toolCount: trace.tools.length,
score: trace.score,
patterns: trace.compressed?.pattern || "Unknown"
};
}
analyzeRecentTraces(traces, analysisType) {
return {
type: analysisType,
summary: `Analysis of ${traces.length} recent traces`,
totalTraces: traces.length,
avgScore: traces.length > 0 ? traces.reduce((sum, t) => sum + t.score, 0) / traces.length : 0,
commonPatterns: traces.map((t) => t.compressed?.pattern).filter(Boolean)
};
}
}
export {
TraceHandlers
};