tachibot-mcp 2.0.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/.env.example +260 -0
- package/CHANGELOG.md +54 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +54 -0
- package/Dockerfile +36 -0
- package/LICENSE +644 -0
- package/README.md +201 -0
- package/SECURITY.md +95 -0
- package/dist/personality/komaai-expressions.js +12 -0
- package/dist/profiles/balanced.json +33 -0
- package/dist/profiles/code_focus.json +33 -0
- package/dist/profiles/full.json +33 -0
- package/dist/profiles/minimal.json +33 -0
- package/dist/profiles/research_power.json +33 -0
- package/dist/scripts/build-profiles.js +46 -0
- package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
- package/dist/src/application/services/focus/FocusTool.service.js +109 -0
- package/dist/src/application/services/focus/ModeRegistry.js +46 -0
- package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
- package/dist/src/application/services/focus/modes/status.mode.js +50 -0
- package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
- package/dist/src/collaborative-orchestrator.js +391 -0
- package/dist/src/config/model-constants.js +188 -0
- package/dist/src/config/model-defaults.js +57 -0
- package/dist/src/config/model-preferences.js +382 -0
- package/dist/src/config/timeout-config.js +130 -0
- package/dist/src/config.js +173 -0
- package/dist/src/domain/interfaces/IFocusMode.js +5 -0
- package/dist/src/domain/interfaces/IProvider.js +6 -0
- package/dist/src/domain/interfaces/ITool.js +5 -0
- package/dist/src/focus-deep.js +245 -0
- package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
- package/dist/src/mcp-client.js +90 -0
- package/dist/src/memory/index.js +17 -0
- package/dist/src/memory/memory-config.js +135 -0
- package/dist/src/memory/memory-interface.js +174 -0
- package/dist/src/memory/memory-manager.js +383 -0
- package/dist/src/memory/providers/devlog-provider.js +385 -0
- package/dist/src/memory/providers/hybrid-provider.js +399 -0
- package/dist/src/memory/providers/local-provider.js +388 -0
- package/dist/src/memory/providers/mem0-provider.js +337 -0
- package/dist/src/modes/architect.js +477 -0
- package/dist/src/modes/auditor.js +362 -0
- package/dist/src/modes/challenger.js +841 -0
- package/dist/src/modes/code-reviewer.js +382 -0
- package/dist/src/modes/commit-guardian.js +424 -0
- package/dist/src/modes/documentation-writer.js +572 -0
- package/dist/src/modes/scout.js +587 -0
- package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
- package/dist/src/modes/shared/helpers/index.js +17 -0
- package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
- package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
- package/dist/src/modes/test-architect.js +767 -0
- package/dist/src/modes/verifier.js +378 -0
- package/dist/src/monitoring/performance-monitor.js +435 -0
- package/dist/src/optimization/batch-executor.js +121 -0
- package/dist/src/optimization/context-pruner.js +196 -0
- package/dist/src/optimization/cost-monitor.js +338 -0
- package/dist/src/optimization/index.js +65 -0
- package/dist/src/optimization/model-router.js +264 -0
- package/dist/src/optimization/result-cache.js +114 -0
- package/dist/src/optimization/token-optimizer.js +257 -0
- package/dist/src/optimization/token-tracker.js +118 -0
- package/dist/src/orchestrator-instructions.js +128 -0
- package/dist/src/orchestrator-lite.js +139 -0
- package/dist/src/orchestrator.js +191 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
- package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
- package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
- package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
- package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
- package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
- package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
- package/dist/src/profiles/balanced.js +37 -0
- package/dist/src/profiles/code_focus.js +37 -0
- package/dist/src/profiles/debug_intensive.js +59 -0
- package/dist/src/profiles/full.js +37 -0
- package/dist/src/profiles/minimal.js +37 -0
- package/dist/src/profiles/research_code.js +59 -0
- package/dist/src/profiles/research_power.js +37 -0
- package/dist/src/profiles/types.js +5 -0
- package/dist/src/profiles/workflow_builder.js +53 -0
- package/dist/src/prompt-engineer-lite.js +78 -0
- package/dist/src/prompt-engineer.js +399 -0
- package/dist/src/reasoning-chain.js +508 -0
- package/dist/src/sequential-thinking.js +291 -0
- package/dist/src/server-diagnostic.js +74 -0
- package/dist/src/server-raw.js +158 -0
- package/dist/src/server-simple.js +58 -0
- package/dist/src/server.js +514 -0
- package/dist/src/session/session-logger.js +617 -0
- package/dist/src/session/session-manager.js +571 -0
- package/dist/src/session/session-tools.js +400 -0
- package/dist/src/tools/advanced-modes.js +200 -0
- package/dist/src/tools/claude-integration.js +356 -0
- package/dist/src/tools/consolidated/ai-router.js +174 -0
- package/dist/src/tools/consolidated/ai-tool.js +48 -0
- package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
- package/dist/src/tools/consolidated/environment-detector.js +80 -0
- package/dist/src/tools/consolidated/index.js +50 -0
- package/dist/src/tools/consolidated/search-tool.js +110 -0
- package/dist/src/tools/consolidated/workflow-tool.js +238 -0
- package/dist/src/tools/gemini-tools.js +329 -0
- package/dist/src/tools/grok-enhanced.js +376 -0
- package/dist/src/tools/grok-tools.js +299 -0
- package/dist/src/tools/lmstudio-tools.js +223 -0
- package/dist/src/tools/openai-tools.js +498 -0
- package/dist/src/tools/openrouter-tools.js +317 -0
- package/dist/src/tools/optimized-wrapper.js +204 -0
- package/dist/src/tools/perplexity-tools.js +294 -0
- package/dist/src/tools/pingpong-tool.js +343 -0
- package/dist/src/tools/qwen-wrapper.js +74 -0
- package/dist/src/tools/tool-router.js +444 -0
- package/dist/src/tools/unified-ai-provider.js +260 -0
- package/dist/src/tools/workflow-runner.js +425 -0
- package/dist/src/tools/workflow-validator-tool.js +107 -0
- package/dist/src/types.js +23 -0
- package/dist/src/utils/input-validator.js +130 -0
- package/dist/src/utils/model-router.js +91 -0
- package/dist/src/utils/progress-stream.js +255 -0
- package/dist/src/utils/provider-router.js +88 -0
- package/dist/src/utils/smart-api-client.js +146 -0
- package/dist/src/utils/table-builder.js +218 -0
- package/dist/src/utils/timestamp-formatter.js +134 -0
- package/dist/src/utils/tool-compressor.js +257 -0
- package/dist/src/utils/tool-config.js +201 -0
- package/dist/src/validators/dependency-graph-validator.js +147 -0
- package/dist/src/validators/interpolation-validator.js +222 -0
- package/dist/src/validators/output-usage-validator.js +151 -0
- package/dist/src/validators/syntax-validator.js +102 -0
- package/dist/src/validators/tool-registry-validator.js +123 -0
- package/dist/src/validators/tool-types.js +97 -0
- package/dist/src/validators/types.js +8 -0
- package/dist/src/validators/workflow-validator.js +134 -0
- package/dist/src/visualizer-lite.js +42 -0
- package/dist/src/visualizer.js +179 -0
- package/dist/src/workflows/circuit-breaker.js +199 -0
- package/dist/src/workflows/custom-workflows.js +451 -0
- package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
- package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
- package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
- package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
- package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
- package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
- package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
- package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
- package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
- package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
- package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
- package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
- package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
- package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
- package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
- package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
- package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
- package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
- package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
- package/dist/src/workflows/fallback-strategies.js +373 -0
- package/dist/src/workflows/message-queue.js +455 -0
- package/dist/src/workflows/model-router.js +189 -0
- package/dist/src/workflows/orchestrator-examples.js +174 -0
- package/dist/src/workflows/orchestrator-integration.js +200 -0
- package/dist/src/workflows/self-healing.js +524 -0
- package/dist/src/workflows/tool-mapper.js +407 -0
- package/dist/src/workflows/tool-orchestrator.js +796 -0
- package/dist/src/workflows/workflow-engine.js +573 -0
- package/dist/src/workflows/workflow-parser.js +283 -0
- package/dist/src/workflows/workflow-types.js +95 -0
- package/dist/src/workflows.js +568 -0
- package/dist/test-workflow-file-output.js +93 -0
- package/docs/API_KEYS.md +570 -0
- package/docs/CLAUDE_CODE_SETUP.md +181 -0
- package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
- package/docs/CONFIGURATION.md +745 -0
- package/docs/FOCUS_MODES.md +240 -0
- package/docs/INSTALLATION_BOTH.md +145 -0
- package/docs/TERMS.md +352 -0
- package/docs/TOOLS_REFERENCE.md +1622 -0
- package/docs/TOOL_PARAMETERS.md +496 -0
- package/docs/TOOL_PROFILES.md +236 -0
- package/docs/WORKFLOWS.md +987 -0
- package/docs/WORKFLOW_OUTPUT.md +198 -0
- package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
- package/docs/workflows/design-brainstorm.md +335 -0
- package/package.json +97 -0
- package/profiles/balanced.json +37 -0
- package/profiles/code_focus.json +37 -0
- package/profiles/debug_intensive.json +34 -0
- package/profiles/full.json +37 -0
- package/profiles/minimal.json +37 -0
- package/profiles/research_power.json +37 -0
- package/profiles/workflow_builder.json +37 -0
- package/smithery.yaml +66 -0
- package/start.sh +8 -0
- package/tools.config.json +81 -0
- package/tsconfig.json +18 -0
- package/workflows/accessibility-code-audit.yaml +92 -0
- package/workflows/code-architecture-review.yaml +202 -0
- package/workflows/code-review.yaml +142 -0
- package/workflows/core/iterative-problem-solver.yaml +283 -0
- package/workflows/creative-brainstorm-yaml.yaml +215 -0
- package/workflows/pingpong.yaml +141 -0
- package/workflows/system/README.md +412 -0
- package/workflows/system/challenger.yaml +175 -0
- package/workflows/system/scout.yaml +164 -0
- package/workflows/system/verifier.yaml +133 -0
- package/workflows/ultra-creative-brainstorm.yaml +318 -0
- package/workflows/ux-research-flow.yaml +92 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Logger for Focus MCP Server
|
|
3
|
+
* Provides full visibility into multi-model brainstorming sessions
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import * as fs from "fs/promises";
|
|
7
|
+
import * as fsSync from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname } from "path";
|
|
11
|
+
import { generateSessionId as generateSessionIdUtil } from "../utils/timestamp-formatter.js";
|
|
12
|
+
// Session configuration schema
|
|
13
|
+
export const SessionConfigSchema = z.object({
|
|
14
|
+
saveSession: z.boolean().default(false),
|
|
15
|
+
outputFormat: z.enum(["markdown", "json", "html"]).default("markdown"),
|
|
16
|
+
includeTimestamps: z.boolean().default(true),
|
|
17
|
+
includeModelMetadata: z.boolean().default(true),
|
|
18
|
+
sessionDir: z.string().default("./workflow-output/sessions"),
|
|
19
|
+
verbose: z.boolean().default(false),
|
|
20
|
+
autoSave: z.boolean().default(false),
|
|
21
|
+
maxHistorySize: z.number().default(100)
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Session Logger Class
|
|
25
|
+
*/
|
|
26
|
+
export class SessionLogger {
|
|
27
|
+
constructor(config = {}) {
|
|
28
|
+
this.currentSession = null;
|
|
29
|
+
this.sessions = new Map();
|
|
30
|
+
this.config = SessionConfigSchema.parse(config);
|
|
31
|
+
// Get the project root directory (where this file is located)
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = dirname(__filename);
|
|
34
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
35
|
+
// If sessionDir is relative (starts with .), make it relative to project root
|
|
36
|
+
if (this.config.sessionDir.startsWith('.')) {
|
|
37
|
+
this.sessionDir = path.join(projectRoot, this.config.sessionDir);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Otherwise use as-is (absolute path)
|
|
41
|
+
this.sessionDir = path.resolve(this.config.sessionDir);
|
|
42
|
+
}
|
|
43
|
+
this.ensureSessionDirectorySync();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Update configuration at runtime
|
|
47
|
+
*/
|
|
48
|
+
updateConfig(config) {
|
|
49
|
+
this.config = SessionConfigSchema.parse({ ...this.config, ...config });
|
|
50
|
+
// Update sessionDir if it changed
|
|
51
|
+
if (config.sessionDir) {
|
|
52
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
53
|
+
const __dirname = dirname(__filename);
|
|
54
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
55
|
+
if (config.sessionDir.startsWith('.')) {
|
|
56
|
+
this.sessionDir = path.join(projectRoot, config.sessionDir);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.sessionDir = path.resolve(config.sessionDir);
|
|
60
|
+
}
|
|
61
|
+
this.ensureSessionDirectorySync();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ensure session directory exists (synchronous version for constructor)
|
|
66
|
+
*/
|
|
67
|
+
ensureSessionDirectorySync() {
|
|
68
|
+
try {
|
|
69
|
+
fsSync.mkdirSync(this.sessionDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Ignore EEXIST errors (directory already exists)
|
|
73
|
+
if (error?.code !== 'EEXIST') {
|
|
74
|
+
console.error(`Failed to create session directory: ${error}`);
|
|
75
|
+
// Try async as fallback
|
|
76
|
+
this.ensureSessionDirectory();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Ensure session directory exists (async version for other operations)
|
|
82
|
+
*/
|
|
83
|
+
async ensureSessionDirectory() {
|
|
84
|
+
try {
|
|
85
|
+
await fs.mkdir(this.sessionDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error(`Failed to create session directory: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Start a new session
|
|
93
|
+
*/
|
|
94
|
+
async startSession(mode, query, domain) {
|
|
95
|
+
const sessionId = this.generateSessionId(mode);
|
|
96
|
+
this.currentSession = {
|
|
97
|
+
id: sessionId,
|
|
98
|
+
timestamp: new Date(),
|
|
99
|
+
mode,
|
|
100
|
+
query,
|
|
101
|
+
domain,
|
|
102
|
+
steps: [],
|
|
103
|
+
status: "active"
|
|
104
|
+
};
|
|
105
|
+
this.sessions.set(sessionId, this.currentSession);
|
|
106
|
+
if (this.config.verbose) {
|
|
107
|
+
console.error(`\n🎬 Session Started: ${sessionId}`);
|
|
108
|
+
console.error(`Mode: ${mode}`);
|
|
109
|
+
console.error(`Query: ${query}`);
|
|
110
|
+
if (domain)
|
|
111
|
+
console.error(`Domain: ${domain}`);
|
|
112
|
+
console.error("─".repeat(50));
|
|
113
|
+
}
|
|
114
|
+
return sessionId;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Log a step in the current session
|
|
118
|
+
*/
|
|
119
|
+
async logStep(model, provider, mode, prompt, response, metadata) {
|
|
120
|
+
if (!this.currentSession) {
|
|
121
|
+
console.error("No active session to log step");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const startTime = new Date();
|
|
125
|
+
const step = {
|
|
126
|
+
stepNumber: this.currentSession.steps.length + 1,
|
|
127
|
+
model,
|
|
128
|
+
provider,
|
|
129
|
+
mode,
|
|
130
|
+
prompt,
|
|
131
|
+
response,
|
|
132
|
+
startTime,
|
|
133
|
+
endTime: new Date(),
|
|
134
|
+
duration: 0, // Will be updated
|
|
135
|
+
metadata
|
|
136
|
+
};
|
|
137
|
+
// Simulate processing time (in real usage, this would be actual API call time)
|
|
138
|
+
step.endTime = new Date();
|
|
139
|
+
step.duration = step.endTime.getTime() - step.startTime.getTime();
|
|
140
|
+
this.currentSession.steps.push(step);
|
|
141
|
+
if (this.config.verbose) {
|
|
142
|
+
this.printStepVerbose(step);
|
|
143
|
+
}
|
|
144
|
+
// Auto-save if configured
|
|
145
|
+
if (this.config.autoSave) {
|
|
146
|
+
await this.saveSession(this.currentSession.id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Print step in verbose mode
|
|
151
|
+
*/
|
|
152
|
+
printStepVerbose(step) {
|
|
153
|
+
console.error(`\n📍 Step ${step.stepNumber}: ${step.mode.toUpperCase()}`);
|
|
154
|
+
console.error(`Model: ${step.model} (${step.provider})`);
|
|
155
|
+
console.error(`Duration: ${step.duration}ms`);
|
|
156
|
+
if (step.tokens) {
|
|
157
|
+
console.error(`Tokens: ${step.tokens.total || 'N/A'}`);
|
|
158
|
+
}
|
|
159
|
+
console.error("\n🤖 Response:");
|
|
160
|
+
console.error("─".repeat(50));
|
|
161
|
+
// Truncate very long responses in verbose mode
|
|
162
|
+
const maxLength = 1000;
|
|
163
|
+
if (step.response.length > maxLength) {
|
|
164
|
+
console.error(step.response.substring(0, maxLength) + "...\n[Truncated]");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error(step.response);
|
|
168
|
+
}
|
|
169
|
+
console.error("─".repeat(50));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Add synthesis to session
|
|
173
|
+
*/
|
|
174
|
+
async addSynthesis(synthesis) {
|
|
175
|
+
if (!this.currentSession) {
|
|
176
|
+
console.error("No active session for synthesis");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.currentSession.synthesis = synthesis;
|
|
180
|
+
this.currentSession.status = "completed";
|
|
181
|
+
this.currentSession.totalDuration = this.currentSession.steps.reduce((sum, step) => sum + step.duration, 0);
|
|
182
|
+
if (this.config.verbose) {
|
|
183
|
+
console.error("\n🎯 Final Synthesis:");
|
|
184
|
+
console.error("═".repeat(50));
|
|
185
|
+
console.error(synthesis);
|
|
186
|
+
console.error("═".repeat(50));
|
|
187
|
+
console.error(`\nTotal Duration: ${this.currentSession.totalDuration}ms`);
|
|
188
|
+
console.error(`Total Steps: ${this.currentSession.steps.length}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* End the current session
|
|
193
|
+
*/
|
|
194
|
+
async endSession(save = true) {
|
|
195
|
+
if (!this.currentSession) {
|
|
196
|
+
console.error("No active session to end");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (this.currentSession.status === "active") {
|
|
200
|
+
this.currentSession.status = "completed";
|
|
201
|
+
}
|
|
202
|
+
let filepath;
|
|
203
|
+
if (save || this.config.saveSession) {
|
|
204
|
+
try {
|
|
205
|
+
filepath = await this.saveSession(this.currentSession.id);
|
|
206
|
+
if (this.config.verbose) {
|
|
207
|
+
console.error(`\n✅ Session saved: ${filepath}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.error(`\n❌ Failed to save session: ${error}`);
|
|
212
|
+
// Don't throw, just log the error
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.config.verbose) {
|
|
216
|
+
console.error(`\n🏁 Session Ended: ${this.currentSession.id}`);
|
|
217
|
+
}
|
|
218
|
+
const sessionId = this.currentSession.id;
|
|
219
|
+
this.currentSession = null;
|
|
220
|
+
// Return filepath if saved, otherwise return sessionId for reference
|
|
221
|
+
return filepath || (save ? sessionId : undefined);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Save session to file
|
|
225
|
+
*/
|
|
226
|
+
async saveSession(sessionId, format) {
|
|
227
|
+
// Try to get session from Map first, then check currentSession
|
|
228
|
+
let session = this.sessions.get(sessionId);
|
|
229
|
+
if (!session && this.currentSession?.id === sessionId) {
|
|
230
|
+
session = this.currentSession;
|
|
231
|
+
// Ensure it's also in the Map for consistency
|
|
232
|
+
this.sessions.set(sessionId, this.currentSession);
|
|
233
|
+
}
|
|
234
|
+
if (!session) {
|
|
235
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
236
|
+
}
|
|
237
|
+
const outputFormat = format || this.config.outputFormat;
|
|
238
|
+
const filename = `${sessionId}.${outputFormat === "html" ? "html" : outputFormat === "json" ? "json" : "md"}`;
|
|
239
|
+
const filepath = path.join(this.sessionDir, filename);
|
|
240
|
+
let content;
|
|
241
|
+
switch (outputFormat) {
|
|
242
|
+
case "json":
|
|
243
|
+
content = this.formatAsJSON(session);
|
|
244
|
+
break;
|
|
245
|
+
case "html":
|
|
246
|
+
content = this.formatAsHTML(session);
|
|
247
|
+
break;
|
|
248
|
+
case "markdown":
|
|
249
|
+
default:
|
|
250
|
+
content = this.formatAsMarkdown(session);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
await fs.writeFile(filepath, content, "utf-8");
|
|
254
|
+
if (this.config.verbose) {
|
|
255
|
+
console.error(`\n💾 Session saved: ${filepath}`);
|
|
256
|
+
}
|
|
257
|
+
return filepath;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Format session as Markdown
|
|
261
|
+
*/
|
|
262
|
+
formatAsMarkdown(session) {
|
|
263
|
+
const lines = [];
|
|
264
|
+
lines.push(`# Focus MCP ${session.mode} Session\n`);
|
|
265
|
+
lines.push("## Metadata");
|
|
266
|
+
lines.push(`- **Session ID**: ${session.id}`);
|
|
267
|
+
lines.push(`- **Date**: ${session.timestamp.toISOString()}`);
|
|
268
|
+
lines.push(`- **Mode**: ${session.mode}`);
|
|
269
|
+
if (session.domain) {
|
|
270
|
+
lines.push(`- **Domain**: ${session.domain}`);
|
|
271
|
+
}
|
|
272
|
+
const models = [...new Set(session.steps.map(s => `${s.model} (${s.provider})`))];
|
|
273
|
+
lines.push(`- **Models Used**: ${models.join(", ")}`);
|
|
274
|
+
if (session.totalDuration) {
|
|
275
|
+
lines.push(`- **Total Duration**: ${(session.totalDuration / 1000).toFixed(1)}s`);
|
|
276
|
+
}
|
|
277
|
+
lines.push(`- **Query**: "${session.query}"\n`);
|
|
278
|
+
lines.push("---\n");
|
|
279
|
+
// Add each step
|
|
280
|
+
session.steps.forEach(step => {
|
|
281
|
+
lines.push(`## Step ${step.stepNumber}: ${step.mode}`);
|
|
282
|
+
lines.push(`**Model**: ${step.model}`);
|
|
283
|
+
lines.push(`**Provider**: ${step.provider}`);
|
|
284
|
+
lines.push(`**Duration**: ${(step.duration / 1000).toFixed(1)}s`);
|
|
285
|
+
if (step.tokens && this.config.includeModelMetadata) {
|
|
286
|
+
lines.push(`**Tokens**: ${step.tokens.total || 'N/A'}`);
|
|
287
|
+
}
|
|
288
|
+
lines.push("\n### Prompt:");
|
|
289
|
+
lines.push("```");
|
|
290
|
+
lines.push(step.prompt);
|
|
291
|
+
lines.push("```\n");
|
|
292
|
+
lines.push("### Response:");
|
|
293
|
+
lines.push(step.response);
|
|
294
|
+
lines.push("\n---\n");
|
|
295
|
+
});
|
|
296
|
+
// Add synthesis if available
|
|
297
|
+
if (session.synthesis) {
|
|
298
|
+
lines.push("## Final Synthesis");
|
|
299
|
+
lines.push(session.synthesis);
|
|
300
|
+
lines.push("\n---\n");
|
|
301
|
+
}
|
|
302
|
+
// Add summary statistics
|
|
303
|
+
lines.push("## Summary Statistics");
|
|
304
|
+
lines.push(`- Total Models Used: ${models.length}`);
|
|
305
|
+
lines.push(`- Total Steps: ${session.steps.length}`);
|
|
306
|
+
if (session.totalDuration) {
|
|
307
|
+
lines.push(`- Total Response Time: ${(session.totalDuration / 1000).toFixed(1)}s`);
|
|
308
|
+
}
|
|
309
|
+
const totalTokens = session.steps.reduce((sum, step) => sum + (step.tokens?.total || 0), 0);
|
|
310
|
+
if (totalTokens > 0) {
|
|
311
|
+
lines.push(`- Total Tokens: ${totalTokens}`);
|
|
312
|
+
}
|
|
313
|
+
return lines.join("\n");
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Format session as JSON
|
|
317
|
+
*/
|
|
318
|
+
formatAsJSON(session) {
|
|
319
|
+
return JSON.stringify(session, null, 2);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Format session as HTML
|
|
323
|
+
*/
|
|
324
|
+
formatAsHTML(session) {
|
|
325
|
+
const html = `<!DOCTYPE html>
|
|
326
|
+
<html lang="en">
|
|
327
|
+
<head>
|
|
328
|
+
<meta charset="UTF-8">
|
|
329
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
330
|
+
<title>Focus MCP Session - ${session.id}</title>
|
|
331
|
+
<style>
|
|
332
|
+
body {
|
|
333
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
334
|
+
line-height: 1.6;
|
|
335
|
+
max-width: 1200px;
|
|
336
|
+
margin: 0 auto;
|
|
337
|
+
padding: 20px;
|
|
338
|
+
background: #f5f5f5;
|
|
339
|
+
}
|
|
340
|
+
.header {
|
|
341
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
342
|
+
color: white;
|
|
343
|
+
padding: 30px;
|
|
344
|
+
border-radius: 10px;
|
|
345
|
+
margin-bottom: 30px;
|
|
346
|
+
}
|
|
347
|
+
.metadata {
|
|
348
|
+
background: white;
|
|
349
|
+
padding: 20px;
|
|
350
|
+
border-radius: 10px;
|
|
351
|
+
margin-bottom: 20px;
|
|
352
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
353
|
+
}
|
|
354
|
+
.step {
|
|
355
|
+
background: white;
|
|
356
|
+
padding: 20px;
|
|
357
|
+
border-radius: 10px;
|
|
358
|
+
margin-bottom: 20px;
|
|
359
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
360
|
+
}
|
|
361
|
+
.step-header {
|
|
362
|
+
display: flex;
|
|
363
|
+
justify-content: space-between;
|
|
364
|
+
align-items: center;
|
|
365
|
+
margin-bottom: 15px;
|
|
366
|
+
padding-bottom: 10px;
|
|
367
|
+
border-bottom: 2px solid #f0f0f0;
|
|
368
|
+
}
|
|
369
|
+
.model-badge {
|
|
370
|
+
background: #667eea;
|
|
371
|
+
color: white;
|
|
372
|
+
padding: 5px 10px;
|
|
373
|
+
border-radius: 5px;
|
|
374
|
+
font-size: 14px;
|
|
375
|
+
}
|
|
376
|
+
.prompt, .response {
|
|
377
|
+
background: #f8f9fa;
|
|
378
|
+
padding: 15px;
|
|
379
|
+
border-radius: 5px;
|
|
380
|
+
margin: 10px 0;
|
|
381
|
+
border-left: 3px solid #667eea;
|
|
382
|
+
}
|
|
383
|
+
.synthesis {
|
|
384
|
+
background: #e8f5e9;
|
|
385
|
+
padding: 20px;
|
|
386
|
+
border-radius: 10px;
|
|
387
|
+
border-left: 4px solid #4caf50;
|
|
388
|
+
}
|
|
389
|
+
.stats {
|
|
390
|
+
display: grid;
|
|
391
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
392
|
+
gap: 15px;
|
|
393
|
+
margin-top: 20px;
|
|
394
|
+
}
|
|
395
|
+
.stat-card {
|
|
396
|
+
background: white;
|
|
397
|
+
padding: 15px;
|
|
398
|
+
border-radius: 8px;
|
|
399
|
+
text-align: center;
|
|
400
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
401
|
+
}
|
|
402
|
+
.stat-value {
|
|
403
|
+
font-size: 24px;
|
|
404
|
+
font-weight: bold;
|
|
405
|
+
color: #667eea;
|
|
406
|
+
}
|
|
407
|
+
.stat-label {
|
|
408
|
+
font-size: 14px;
|
|
409
|
+
color: #666;
|
|
410
|
+
margin-top: 5px;
|
|
411
|
+
}
|
|
412
|
+
</style>
|
|
413
|
+
</head>
|
|
414
|
+
<body>
|
|
415
|
+
<div class="header">
|
|
416
|
+
<h1>Focus MCP ${session.mode} Session</h1>
|
|
417
|
+
<p>${session.query}</p>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<div class="metadata">
|
|
421
|
+
<h2>Session Information</h2>
|
|
422
|
+
<p><strong>ID:</strong> ${session.id}</p>
|
|
423
|
+
<p><strong>Date:</strong> ${session.timestamp.toISOString()}</p>
|
|
424
|
+
<p><strong>Status:</strong> ${session.status}</p>
|
|
425
|
+
${session.domain ? `<p><strong>Domain:</strong> ${session.domain}</p>` : ''}
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
${session.steps.map(step => `
|
|
429
|
+
<div class="step">
|
|
430
|
+
<div class="step-header">
|
|
431
|
+
<h3>Step ${step.stepNumber}: ${step.mode}</h3>
|
|
432
|
+
<span class="model-badge">${step.model}</span>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="prompt">
|
|
435
|
+
<strong>Prompt:</strong><br>
|
|
436
|
+
${step.prompt.replace(/\n/g, '<br>')}
|
|
437
|
+
</div>
|
|
438
|
+
<div class="response">
|
|
439
|
+
<strong>Response:</strong><br>
|
|
440
|
+
${step.response.replace(/\n/g, '<br>')}
|
|
441
|
+
</div>
|
|
442
|
+
<p><small>Duration: ${(step.duration / 1000).toFixed(1)}s</small></p>
|
|
443
|
+
</div>
|
|
444
|
+
`).join('')}
|
|
445
|
+
|
|
446
|
+
${session.synthesis ? `
|
|
447
|
+
<div class="synthesis">
|
|
448
|
+
<h2>Final Synthesis</h2>
|
|
449
|
+
<p>${session.synthesis.replace(/\n/g, '<br>')}</p>
|
|
450
|
+
</div>
|
|
451
|
+
` : ''}
|
|
452
|
+
|
|
453
|
+
<div class="stats">
|
|
454
|
+
<div class="stat-card">
|
|
455
|
+
<div class="stat-value">${session.steps.length}</div>
|
|
456
|
+
<div class="stat-label">Total Steps</div>
|
|
457
|
+
</div>
|
|
458
|
+
<div class="stat-card">
|
|
459
|
+
<div class="stat-value">${[...new Set(session.steps.map(s => s.model))].length}</div>
|
|
460
|
+
<div class="stat-label">Models Used</div>
|
|
461
|
+
</div>
|
|
462
|
+
${session.totalDuration ? `
|
|
463
|
+
<div class="stat-card">
|
|
464
|
+
<div class="stat-value">${(session.totalDuration / 1000).toFixed(1)}s</div>
|
|
465
|
+
<div class="stat-label">Total Duration</div>
|
|
466
|
+
</div>
|
|
467
|
+
` : ''}
|
|
468
|
+
</div>
|
|
469
|
+
</body>
|
|
470
|
+
</html>`;
|
|
471
|
+
return html;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* List all saved sessions
|
|
475
|
+
*/
|
|
476
|
+
async listSessions(filter, limit = 10) {
|
|
477
|
+
try {
|
|
478
|
+
const files = await fs.readdir(this.sessionDir);
|
|
479
|
+
const sessions = [];
|
|
480
|
+
for (const file of files) {
|
|
481
|
+
if (file.endsWith(".json")) {
|
|
482
|
+
const filepath = path.join(this.sessionDir, file);
|
|
483
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
484
|
+
const session = JSON.parse(content);
|
|
485
|
+
if (!filter ||
|
|
486
|
+
session.mode.includes(filter) ||
|
|
487
|
+
session.query.includes(filter)) {
|
|
488
|
+
sessions.push({
|
|
489
|
+
id: session.id,
|
|
490
|
+
date: new Date(session.timestamp),
|
|
491
|
+
mode: session.mode,
|
|
492
|
+
query: session.query
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Sort by date descending and limit
|
|
498
|
+
return sessions
|
|
499
|
+
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
500
|
+
.slice(0, limit);
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
console.error(`Error listing sessions: ${error}`);
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Load and replay a session
|
|
509
|
+
*/
|
|
510
|
+
async replaySession(sessionId, format = "full") {
|
|
511
|
+
// Try different file extensions
|
|
512
|
+
const extensions = [".json", ".md", ".html"];
|
|
513
|
+
let session = null;
|
|
514
|
+
for (const ext of extensions) {
|
|
515
|
+
try {
|
|
516
|
+
const filepath = path.join(this.sessionDir, sessionId + ext);
|
|
517
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
518
|
+
if (ext === ".json") {
|
|
519
|
+
session = JSON.parse(content);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
// Try next extension
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!session) {
|
|
528
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
529
|
+
}
|
|
530
|
+
if (format === "summary") {
|
|
531
|
+
return this.formatSessionSummary(session);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
return this.formatAsMarkdown(session);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Format session summary
|
|
539
|
+
*/
|
|
540
|
+
formatSessionSummary(session) {
|
|
541
|
+
const lines = [];
|
|
542
|
+
lines.push(`# Session Summary: ${session.id}\n`);
|
|
543
|
+
lines.push(`**Query**: ${session.query}`);
|
|
544
|
+
lines.push(`**Mode**: ${session.mode}`);
|
|
545
|
+
lines.push(`**Date**: ${session.timestamp}`);
|
|
546
|
+
lines.push(`**Steps**: ${session.steps.length}`);
|
|
547
|
+
if (session.synthesis) {
|
|
548
|
+
lines.push("\n## Final Synthesis");
|
|
549
|
+
lines.push(session.synthesis);
|
|
550
|
+
}
|
|
551
|
+
return lines.join("\n");
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Generate session ID with human-readable format
|
|
555
|
+
* Format: session-YYYY-MM-DD-DayName-HH-MM-mode
|
|
556
|
+
* Example: session-2025-11-23-Sunday-22-44-advanced-pingpong
|
|
557
|
+
*/
|
|
558
|
+
generateSessionId(mode) {
|
|
559
|
+
return generateSessionIdUtil(mode);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Export session in different format
|
|
563
|
+
*/
|
|
564
|
+
async exportSession(sessionId, format, outputPath) {
|
|
565
|
+
const session = this.sessions.get(sessionId);
|
|
566
|
+
if (!session) {
|
|
567
|
+
// Try to load from file
|
|
568
|
+
const loaded = await this.replaySession(sessionId);
|
|
569
|
+
if (!loaded) {
|
|
570
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (format === "pdf") {
|
|
574
|
+
// PDF export would require additional dependencies
|
|
575
|
+
throw new Error("PDF export not yet implemented");
|
|
576
|
+
}
|
|
577
|
+
const filepath = outputPath ||
|
|
578
|
+
path.join(this.sessionDir, `${sessionId}-export.${format}`);
|
|
579
|
+
await this.saveSession(sessionId, format);
|
|
580
|
+
return filepath;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Get current session
|
|
584
|
+
*/
|
|
585
|
+
getCurrentSession() {
|
|
586
|
+
return this.currentSession;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Clear old sessions
|
|
590
|
+
*/
|
|
591
|
+
async clearOldSessions(daysToKeep = 30) {
|
|
592
|
+
const cutoffDate = new Date();
|
|
593
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
594
|
+
let deletedCount = 0;
|
|
595
|
+
try {
|
|
596
|
+
const files = await fs.readdir(this.sessionDir);
|
|
597
|
+
for (const file of files) {
|
|
598
|
+
const filepath = path.join(this.sessionDir, file);
|
|
599
|
+
const stats = await fs.stat(filepath);
|
|
600
|
+
if (stats.mtime < cutoffDate) {
|
|
601
|
+
await fs.unlink(filepath);
|
|
602
|
+
deletedCount++;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
console.error(`Error clearing old sessions: ${error}`);
|
|
608
|
+
}
|
|
609
|
+
return deletedCount;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// Export singleton instance with default config that can be updated
|
|
613
|
+
export const sessionLogger = new SessionLogger({
|
|
614
|
+
saveSession: true, // Default to true for saving sessions
|
|
615
|
+
verbose: false,
|
|
616
|
+
outputFormat: "markdown"
|
|
617
|
+
});
|