claude-flow-novice 1.3.0 → 1.3.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/.claude-flow-novice/preferences/generation.json +147 -0
- package/.claude-flow-novice/preferences/language-configs/javascript.json +51 -0
- package/.claude-flow-novice/preferences/language-configs/python.json +50 -0
- package/.claude-flow-novice/preferences/language-configs/rust.json +237 -0
- package/.claude-flow-novice/preferences/language-configs/typescript.json +54 -0
- package/.claude-flow-novice/preferences/project-local.json +91 -0
- package/.claude-flow-novice/preferences/resource-delegation.json +120 -0
- package/.claude-flow-novice/preferences/team-shared.json +195 -0
- package/.claude-flow-novice/preferences/user-global.json +247 -0
- package/.claude-flow-novice/templates/claude-md-templates/CLAUDE-JAVASCRIPT.md +769 -0
- package/.claude-flow-novice/templates/claude-md-templates/CLAUDE-PYTHON.md +1214 -0
- package/.claude-flow-novice/templates/claude-md-templates/CLAUDE-RUST.md +475 -0
- package/.claude-flow-novice/templates/claude-md-templates/CLAUDE-TYPESCRIPT.md +851 -0
- package/.claude-flow-novice/templates/claude-md-templates/README.md +263 -0
- package/CLAUDE.md +81 -0
- package/README-NPM.md +0 -0
- package/package.json +11 -7
- package/scripts/build/README.md +167 -0
- package/scripts/build/build-config.js +27 -0
- package/scripts/build/build-prompt-copier.sh +30 -0
- package/scripts/build/performance-monitor.js +869 -0
- package/scripts/build/prepare-publish.js +150 -0
- package/scripts/build/typescript-fixer.js +621 -0
- package/scripts/build/unified-builder.sh +428 -0
- package/scripts/build/update-bin-version.js +32 -0
- package/scripts/dev/README.md +264 -0
- package/scripts/dev/claude-flow-wrapper.sh +35 -0
- package/scripts/dev/claude-monitor.py +419 -0
- package/scripts/dev/claude-sparc.sh +562 -0
- package/scripts/dev/claude-wrapper.sh +17 -0
- package/scripts/dev/demo-phase3-compliance.js +172 -0
- package/scripts/dev/demo-task-system.ts +224 -0
- package/scripts/dev/deployment-validator.js +315 -0
- package/scripts/dev/spawn-claude-terminal.sh +32 -0
- package/scripts/dev/start-portal.sh +506 -0
- package/scripts/dev/start-web-ui.js +15 -0
- package/scripts/dev/stop-portal.sh +311 -0
- package/scripts/dev/validate-examples.ts +288 -0
- package/scripts/dev/validate-phase2.cjs +451 -0
- package/scripts/dev/validate-phase2.js +785 -0
- package/scripts/dev/validate-phase3.cjs +208 -0
- package/scripts/dev/validate-security-remediation.js +1 -0
- package/scripts/legacy/README.md +272 -0
- package/scripts/legacy/batch-fix-ts.sh +54 -0
- package/scripts/legacy/build-migration.sh +105 -0
- package/scripts/legacy/build-monitor.js +209 -0
- package/scripts/legacy/build-with-filter.sh +84 -0
- package/scripts/legacy/build-workaround.sh +71 -0
- package/scripts/legacy/fix-ts-advanced.js +358 -0
- package/scripts/legacy/fix-ts-final.sh +50 -0
- package/scripts/legacy/fix-ts-targeted.sh +49 -0
- package/scripts/legacy/fix-typescript-errors.js +305 -0
- package/scripts/legacy/force-build.sh +63 -0
- package/scripts/legacy/optimize-performance.js +400 -0
- package/scripts/legacy/performance-monitor.js +263 -0
- package/scripts/legacy/performance-monitoring.js +532 -0
- package/scripts/legacy/performance-test-runner.js +645 -0
- package/scripts/legacy/quick-fix-ts.js +281 -0
- package/scripts/legacy/safe-build.sh +63 -0
- package/scripts/migration/README.md +434 -0
- package/scripts/migration/install-arm64.js +78 -0
- package/scripts/migration/install.js +83 -0
- package/scripts/migration/migrate-hooks.js +173 -0
- package/scripts/migration/migration-examples.ts +318 -0
- package/scripts/optimization/build-optimizer.js +438 -0
- package/scripts/optimization/config-validator.js +761 -0
- package/scripts/optimization/test-optimization.js +432 -0
- package/scripts/optimization/unified-activation.js +839 -0
- package/scripts/performance/ACTIVATION_COMMANDS.md +292 -0
- package/scripts/performance/sqlite-enhanced-activation.sh +583 -0
- package/scripts/performance/test-enhanced-backend.sh +504 -0
- package/scripts/performance-test-runner.js +698 -0
- package/scripts/security/README.md +339 -0
- package/scripts/security/install-git-hooks.sh +132 -0
- package/scripts/security/ruv-swarm-safe.js +74 -0
- package/scripts/test/README.md +236 -0
- package/scripts/test/check-links.ts +274 -0
- package/scripts/test/check-performance-regression.ts +168 -0
- package/scripts/test/coverage-report.ts +692 -0
- package/scripts/test/generate-swarm-tests.js +633 -0
- package/scripts/test/integration-test-validation.cjs +253 -0
- package/scripts/test/load-test-swarm.js +576 -0
- package/scripts/test/run-phase3-compliance-tests.js +427 -0
- package/scripts/test/test-batch-tasks.ts +29 -0
- package/scripts/test/test-byzantine-resolution.js +246 -0
- package/scripts/test/test-claude-spawn-options.sh +63 -0
- package/scripts/test/test-cli-wizard.js +331 -0
- package/scripts/test/test-comprehensive.js +401 -0
- package/scripts/test/test-coordination-features.ts +238 -0
- package/scripts/test/test-fallback-systems.js +276 -0
- package/scripts/test/test-init-command.ts +302 -0
- package/scripts/test/test-mcp.ts +251 -0
- package/scripts/test/test-runner.ts +568 -0
- package/scripts/test/test-swarm-integration.sh +92 -0
- package/scripts/test/test-swarm.ts +142 -0
- package/scripts/test/validation-summary.ts +408 -0
- package/scripts/utils/README.md +261 -0
- package/scripts/utils/clean-build-artifacts.sh +94 -0
- package/scripts/utils/cleanup-root.sh +69 -0
- package/scripts/utils/fix-cliffy-imports.js +307 -0
- package/scripts/utils/fix-duplicate-imports.js +114 -0
- package/scripts/utils/fix-error-handling.cjs +70 -0
- package/scripts/utils/fix-import-paths.js +104 -0
- package/scripts/utils/fix-imports.js +116 -0
- package/scripts/utils/fix-shebang.js +78 -0
- package/scripts/utils/fix-test-modules.js +27 -0
- package/scripts/utils/fix-timezone-issue-246.js +200 -0
- package/scripts/utils/fix-ts-comprehensive.py +182 -0
- package/scripts/utils/fix-ts-targeted-batch.js +250 -0
- package/scripts/utils/remove-benchmark-conflicts.sh +140 -0
- package/scripts/utils/simple-test-fixer.js +190 -0
- package/scripts/utils/validate-metrics-structure.cjs +144 -0
- package/scripts/verify-mcp-server.js +86 -0
- package/src/cli/simple-commands/__tests__/agent.test.js +291 -0
- package/src/cli/simple-commands/__tests__/memory.test.js +8 -0
- package/src/cli/simple-commands/__tests__/swarm.test.js +371 -0
- package/src/cli/simple-commands/__tests__/task.test.js +8 -0
- package/src/cli/simple-commands/agent.js +216 -0
- package/src/cli/simple-commands/analysis.js +570 -0
- package/src/cli/simple-commands/automation-executor.js +1603 -0
- package/src/cli/simple-commands/automation.js +627 -0
- package/src/cli/simple-commands/batch-manager.js +338 -0
- package/src/cli/simple-commands/claude-telemetry.js +311 -0
- package/src/cli/simple-commands/claude-track.js +102 -0
- package/src/cli/simple-commands/concurrent-display.js +348 -0
- package/src/cli/simple-commands/config.js +319 -0
- package/src/cli/simple-commands/coordination.js +307 -0
- package/src/cli/simple-commands/enhanced-ui-views.js +654 -0
- package/src/cli/simple-commands/enhanced-webui-complete.js +1038 -0
- package/src/cli/simple-commands/fix-hook-variables.js +363 -0
- package/src/cli/simple-commands/github/gh-coordinator.js +605 -0
- package/src/cli/simple-commands/github/github-api.js +624 -0
- package/src/cli/simple-commands/github/init.js +543 -0
- package/src/cli/simple-commands/github.js +377 -0
- package/src/cli/simple-commands/goal.js +145 -0
- package/src/cli/simple-commands/hive-mind/auto-save-middleware.js +311 -0
- package/src/cli/simple-commands/hive-mind/communication.js +740 -0
- package/src/cli/simple-commands/hive-mind/core.js +1031 -0
- package/src/cli/simple-commands/hive-mind/db-optimizer.js +872 -0
- package/src/cli/simple-commands/hive-mind/mcp-wrapper.js +1364 -0
- package/src/cli/simple-commands/hive-mind/memory.js +1292 -0
- package/src/cli/simple-commands/hive-mind/performance-optimizer.js +618 -0
- package/src/cli/simple-commands/hive-mind/performance-test.js +373 -0
- package/src/cli/simple-commands/hive-mind/queen.js +809 -0
- package/src/cli/simple-commands/hive-mind/session-manager.js +1223 -0
- package/src/cli/simple-commands/hive-mind-optimize.js +361 -0
- package/src/cli/simple-commands/hive-mind-wizard.js +281 -0
- package/src/cli/simple-commands/hive-mind.js +3112 -0
- package/src/cli/simple-commands/hive.js +140 -0
- package/src/cli/simple-commands/hook-safety.js +671 -0
- package/src/cli/simple-commands/hooks.js +1268 -0
- package/src/cli/simple-commands/init/.claude/checkpoints/1756224542.json +7 -0
- package/src/cli/simple-commands/init/.claude/checkpoints/1756224544.json +8 -0
- package/src/cli/simple-commands/init/README.md +106 -0
- package/src/cli/simple-commands/init/VALIDATION_ROLLBACK.md +488 -0
- package/src/cli/simple-commands/init/agent-copier.js +347 -0
- package/src/cli/simple-commands/init/batch-init.js +663 -0
- package/src/cli/simple-commands/init/claude-commands/claude-flow-commands.js +438 -0
- package/src/cli/simple-commands/init/claude-commands/optimized-claude-flow-commands.js +876 -0
- package/src/cli/simple-commands/init/claude-commands/optimized-slash-commands.js +356 -0
- package/src/cli/simple-commands/init/claude-commands/optimized-sparc-commands.js +501 -0
- package/src/cli/simple-commands/init/claude-commands/slash-commands.js +57 -0
- package/src/cli/simple-commands/init/claude-commands/sparc-commands.js +296 -0
- package/src/cli/simple-commands/init/copy-revised-templates.js +175 -0
- package/src/cli/simple-commands/init/executable-wrapper.js +122 -0
- package/src/cli/simple-commands/init/gitignore-updater.js +137 -0
- package/src/cli/simple-commands/init/help.js +110 -0
- package/src/cli/simple-commands/init/hive-mind-init.js +749 -0
- package/src/cli/simple-commands/init/index.js +1953 -0
- package/src/cli/simple-commands/init/performance-monitor.js +344 -0
- package/src/cli/simple-commands/init/rollback/backup-manager.js +542 -0
- package/src/cli/simple-commands/init/rollback/index.js +399 -0
- package/src/cli/simple-commands/init/rollback/recovery-manager.js +778 -0
- package/src/cli/simple-commands/init/rollback/rollback-executor.js +521 -0
- package/src/cli/simple-commands/init/rollback/state-tracker.js +486 -0
- package/src/cli/simple-commands/init/sparc/roo-readme.js +61 -0
- package/src/cli/simple-commands/init/sparc/roomodes-config.js +102 -0
- package/src/cli/simple-commands/init/sparc/workflows.js +40 -0
- package/src/cli/simple-commands/init/sparc-structure.js +68 -0
- package/src/cli/simple-commands/init/template-copier.js +640 -0
- package/src/cli/simple-commands/init/templates/CLAUDE.md +1185 -0
- package/src/cli/simple-commands/init/templates/CLAUDE.md.optimized +265 -0
- package/src/cli/simple-commands/init/templates/claude-flow-universal +81 -0
- package/src/cli/simple-commands/init/templates/claude-flow.bat +18 -0
- package/src/cli/simple-commands/init/templates/claude-flow.ps1 +24 -0
- package/src/cli/simple-commands/init/templates/claude-md.js +1101 -0
- package/src/cli/simple-commands/init/templates/commands/analysis/bottleneck-detect.md +162 -0
- package/src/cli/simple-commands/init/templates/commands/automation/auto-agent.md +122 -0
- package/src/cli/simple-commands/init/templates/commands/coordination/swarm-init.md +85 -0
- package/src/cli/simple-commands/init/templates/commands/github/github-swarm.md +121 -0
- package/src/cli/simple-commands/init/templates/commands/helpers/standard-checkpoint-hooks.sh +179 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/notification.md +113 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/post-command.md +116 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/post-edit.md +117 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/post-task.md +112 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/pre-command.md +113 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/pre-edit.md +113 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/pre-search.md +112 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/pre-task.md +111 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/session-end.md +118 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/session-restore.md +118 -0
- package/src/cli/simple-commands/init/templates/commands/hooks/session-start.md +117 -0
- package/src/cli/simple-commands/init/templates/coordination-md.js +340 -0
- package/src/cli/simple-commands/init/templates/coordination.md +16 -0
- package/src/cli/simple-commands/init/templates/enhanced-templates.js +2347 -0
- package/src/cli/simple-commands/init/templates/github-safe-enhanced.js +331 -0
- package/src/cli/simple-commands/init/templates/github-safe.js +106 -0
- package/src/cli/simple-commands/init/templates/memory-bank-md.js +259 -0
- package/src/cli/simple-commands/init/templates/memory-bank.md +16 -0
- package/src/cli/simple-commands/init/templates/readme-files.js +72 -0
- package/src/cli/simple-commands/init/templates/safe-hook-patterns.js +430 -0
- package/src/cli/simple-commands/init/templates/settings.json +109 -0
- package/src/cli/simple-commands/init/templates/settings.json.enhanced +35 -0
- package/src/cli/simple-commands/init/templates/sparc-modes.js +1401 -0
- package/src/cli/simple-commands/init/templates/verification-claude-md.js +432 -0
- package/src/cli/simple-commands/init/validation/config-validator.js +354 -0
- package/src/cli/simple-commands/init/validation/health-checker.js +599 -0
- package/src/cli/simple-commands/init/validation/index.js +388 -0
- package/src/cli/simple-commands/init/validation/mode-validator.js +387 -0
- package/src/cli/simple-commands/init/validation/post-init-validator.js +390 -0
- package/src/cli/simple-commands/init/validation/pre-init-validator.js +290 -0
- package/src/cli/simple-commands/init/validation/test-runner.js +488 -0
- package/src/cli/simple-commands/init.js +4 -0
- package/src/cli/simple-commands/mcp-health.js +163 -0
- package/src/cli/simple-commands/mcp-integration-layer.js +689 -0
- package/src/cli/simple-commands/mcp.js +420 -0
- package/src/cli/simple-commands/memory-consolidation.js +631 -0
- package/src/cli/simple-commands/memory.js +345 -0
- package/src/cli/simple-commands/migrate-hooks.js +63 -0
- package/src/cli/simple-commands/monitor.js +417 -0
- package/src/cli/simple-commands/neural.js +148 -0
- package/src/cli/simple-commands/pair-autofix-only.js +755 -0
- package/src/cli/simple-commands/pair-basic.js +751 -0
- package/src/cli/simple-commands/pair-old.js +623 -0
- package/src/cli/simple-commands/pair-working.js +849 -0
- package/src/cli/simple-commands/pair.js +849 -0
- package/src/cli/simple-commands/performance-hooks.js +149 -0
- package/src/cli/simple-commands/performance-metrics.js +601 -0
- package/src/cli/simple-commands/process-ui-enhanced.js +821 -0
- package/src/cli/simple-commands/process-ui.js +274 -0
- package/src/cli/simple-commands/realtime-update-system.js +659 -0
- package/src/cli/simple-commands/sparc/architecture.js +1750 -0
- package/src/cli/simple-commands/sparc/commands.js +575 -0
- package/src/cli/simple-commands/sparc/completion.js +1831 -0
- package/src/cli/simple-commands/sparc/coordinator.js +1045 -0
- package/src/cli/simple-commands/sparc/index.js +321 -0
- package/src/cli/simple-commands/sparc/phase-base.js +430 -0
- package/src/cli/simple-commands/sparc/pseudocode.js +984 -0
- package/src/cli/simple-commands/sparc/refinement.js +1856 -0
- package/src/cli/simple-commands/sparc/specification.js +736 -0
- package/src/cli/simple-commands/sparc-modes/architect.js +125 -0
- package/src/cli/simple-commands/sparc-modes/ask.js +126 -0
- package/src/cli/simple-commands/sparc-modes/code.js +148 -0
- package/src/cli/simple-commands/sparc-modes/debug.js +112 -0
- package/src/cli/simple-commands/sparc-modes/devops.js +137 -0
- package/src/cli/simple-commands/sparc-modes/docs-writer.js +38 -0
- package/src/cli/simple-commands/sparc-modes/generic.js +34 -0
- package/src/cli/simple-commands/sparc-modes/index.js +201 -0
- package/src/cli/simple-commands/sparc-modes/integration.js +55 -0
- package/src/cli/simple-commands/sparc-modes/mcp.js +38 -0
- package/src/cli/simple-commands/sparc-modes/monitoring.js +38 -0
- package/src/cli/simple-commands/sparc-modes/optimization.js +38 -0
- package/src/cli/simple-commands/sparc-modes/security-review.js +130 -0
- package/src/cli/simple-commands/sparc-modes/sparc-orchestrator.js +167 -0
- package/src/cli/simple-commands/sparc-modes/spec-pseudocode.js +38 -0
- package/src/cli/simple-commands/sparc-modes/supabase-admin.js +149 -0
- package/src/cli/simple-commands/sparc-modes/swarm.js +436 -0
- package/src/cli/simple-commands/sparc-modes/tdd.js +112 -0
- package/src/cli/simple-commands/sparc-modes/tutorial.js +277 -0
- package/src/cli/simple-commands/sparc.js +530 -0
- package/src/cli/simple-commands/start-ui.js +147 -0
- package/src/cli/simple-commands/start-wrapper.js +285 -0
- package/src/cli/simple-commands/start.js +2 -0
- package/src/cli/simple-commands/status.js +303 -0
- package/src/cli/simple-commands/stream-chain-clean.js +221 -0
- package/src/cli/simple-commands/stream-chain-fixed.js +89 -0
- package/src/cli/simple-commands/stream-chain-real.js +408 -0
- package/src/cli/simple-commands/stream-chain-working.js +323 -0
- package/src/cli/simple-commands/stream-chain.js +491 -0
- package/src/cli/simple-commands/stream-processor.js +340 -0
- package/src/cli/simple-commands/swarm-executor.js +253 -0
- package/src/cli/simple-commands/swarm-metrics-integration.js +371 -0
- package/src/cli/simple-commands/swarm-ui.js +741 -0
- package/src/cli/simple-commands/swarm-webui-integration.js +311 -0
- package/src/cli/simple-commands/swarm.js +2277 -0
- package/src/cli/simple-commands/task.js +228 -0
- package/src/cli/simple-commands/templates/mle-star-workflow.json +294 -0
- package/src/cli/simple-commands/timestamp-fix.js +104 -0
- package/src/cli/simple-commands/token-tracker.js +372 -0
- package/src/cli/simple-commands/tool-execution-framework.js +555 -0
- package/src/cli/simple-commands/train-and-stream.js +354 -0
- package/src/cli/simple-commands/training-pipeline.js +874 -0
- package/src/cli/simple-commands/training.js +288 -0
- package/src/cli/simple-commands/verification-hooks.js +336 -0
- package/src/cli/simple-commands/verification-integration.js +464 -0
- package/src/cli/simple-commands/verification-training-integration.js +646 -0
- package/src/cli/simple-commands/verification.js +551 -0
- package/src/cli/simple-commands/web-server.js +929 -0
- package/src/cli/simple-commands/webui-validator.js +136 -0
- package/src/language/README.md +503 -0
- package/src/language/claude-md-generator.js +618 -0
- package/src/language/cli.js +422 -0
- package/src/language/example.js +347 -0
- package/src/language/integration-system.js +619 -0
- package/src/language/language-detector.js +581 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive Mind Session Manager
|
|
3
|
+
* Handles session persistence and resume functionality for swarms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
8
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { cwd } from '../../node-compat.js';
|
|
11
|
+
import { createDatabase, isSQLiteAvailable, isWindows } from '../../../memory/sqlite-wrapper.js';
|
|
12
|
+
import { sessionSerializer } from '../../../memory/enhanced-session-serializer.js';
|
|
13
|
+
import { SerializationError, DeserializationError } from '../../../memory/advanced-serializer.js';
|
|
14
|
+
|
|
15
|
+
export class HiveMindSessionManager {
|
|
16
|
+
constructor(hiveMindDir = null) {
|
|
17
|
+
this.hiveMindDir = hiveMindDir || path.join(cwd(), '.hive-mind');
|
|
18
|
+
this.sessionsDir = path.join(this.hiveMindDir, 'sessions');
|
|
19
|
+
this.dbPath = path.join(this.hiveMindDir, 'hive.db');
|
|
20
|
+
this.db = null;
|
|
21
|
+
this.isInMemory = false;
|
|
22
|
+
this.memoryStore = null;
|
|
23
|
+
this.initializationPromise = null;
|
|
24
|
+
|
|
25
|
+
// Ensure directories exist
|
|
26
|
+
this.ensureDirectories();
|
|
27
|
+
|
|
28
|
+
// Initialize database connection (store promise for later)
|
|
29
|
+
this.initializationPromise = this.initializeDatabase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize database with fallback support
|
|
34
|
+
*/
|
|
35
|
+
async initializeDatabase() {
|
|
36
|
+
try {
|
|
37
|
+
const sqliteAvailable = await isSQLiteAvailable();
|
|
38
|
+
|
|
39
|
+
if (!sqliteAvailable) {
|
|
40
|
+
console.warn('SQLite not available, using in-memory session storage');
|
|
41
|
+
this.initializeInMemoryFallback();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.db = await createDatabase(this.dbPath);
|
|
46
|
+
if (this.db) {
|
|
47
|
+
this.initializeSchema();
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error('Failed to create database instance');
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Failed to create SQLite database:', error.message);
|
|
53
|
+
console.warn('Falling back to in-memory session storage');
|
|
54
|
+
this.initializeInMemoryFallback();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ensure database is initialized before use
|
|
60
|
+
*/
|
|
61
|
+
async ensureInitialized() {
|
|
62
|
+
if (this.initializationPromise) {
|
|
63
|
+
await this.initializationPromise;
|
|
64
|
+
this.initializationPromise = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.db === null && !this.isInMemory) {
|
|
68
|
+
await this.initializeDatabase();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Initialize in-memory fallback for session storage
|
|
74
|
+
*/
|
|
75
|
+
initializeInMemoryFallback() {
|
|
76
|
+
this.isInMemory = true;
|
|
77
|
+
this.memoryStore = {
|
|
78
|
+
sessions: new Map(),
|
|
79
|
+
checkpoints: new Map(),
|
|
80
|
+
logs: new Map(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (isWindows()) {
|
|
84
|
+
console.info(`
|
|
85
|
+
Note: Session data will not persist between runs on Windows without SQLite.
|
|
86
|
+
To enable persistence, see: https://github.com/ruvnet/claude-code-flow/docs/windows-installation.md
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Ensure required directories exist
|
|
93
|
+
*/
|
|
94
|
+
ensureDirectories() {
|
|
95
|
+
if (!existsSync(this.hiveMindDir)) {
|
|
96
|
+
mkdirSync(this.hiveMindDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
if (!existsSync(this.sessionsDir)) {
|
|
99
|
+
mkdirSync(this.sessionsDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialize database schema for sessions
|
|
105
|
+
*/
|
|
106
|
+
initializeSchema() {
|
|
107
|
+
if (!this.db) {
|
|
108
|
+
console.error('Database not initialized');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Create the base schema
|
|
112
|
+
this.db.exec(`
|
|
113
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
swarm_id TEXT NOT NULL,
|
|
116
|
+
swarm_name TEXT NOT NULL,
|
|
117
|
+
objective TEXT,
|
|
118
|
+
status TEXT DEFAULT 'active',
|
|
119
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
120
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
121
|
+
paused_at DATETIME,
|
|
122
|
+
resumed_at DATETIME,
|
|
123
|
+
completion_percentage REAL DEFAULT 0,
|
|
124
|
+
checkpoint_data TEXT,
|
|
125
|
+
metadata TEXT,
|
|
126
|
+
parent_pid INTEGER,
|
|
127
|
+
child_pids TEXT,
|
|
128
|
+
FOREIGN KEY (swarm_id) REFERENCES swarms(id)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
CREATE TABLE IF NOT EXISTS session_checkpoints (
|
|
132
|
+
id TEXT PRIMARY KEY,
|
|
133
|
+
session_id TEXT NOT NULL,
|
|
134
|
+
checkpoint_name TEXT NOT NULL,
|
|
135
|
+
checkpoint_data TEXT,
|
|
136
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
137
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
CREATE TABLE IF NOT EXISTS session_logs (
|
|
141
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
142
|
+
session_id TEXT NOT NULL,
|
|
143
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
144
|
+
log_level TEXT DEFAULT 'info',
|
|
145
|
+
message TEXT,
|
|
146
|
+
agent_id TEXT,
|
|
147
|
+
data TEXT,
|
|
148
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
149
|
+
);
|
|
150
|
+
`);
|
|
151
|
+
|
|
152
|
+
// Run migrations to add new columns
|
|
153
|
+
this.runMigrations();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Run database migrations
|
|
158
|
+
*/
|
|
159
|
+
runMigrations() {
|
|
160
|
+
if (!this.db) {
|
|
161
|
+
console.error('Database not initialized for migrations');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
// Check if required columns exist
|
|
166
|
+
const columns = this.db.prepare('PRAGMA table_info(sessions)').all();
|
|
167
|
+
|
|
168
|
+
// Core columns
|
|
169
|
+
const hasObjective = columns.some((col) => col.name === 'objective');
|
|
170
|
+
const hasSwarmName = columns.some((col) => col.name === 'swarm_name');
|
|
171
|
+
const hasCheckpointData = columns.some((col) => col.name === 'checkpoint_data');
|
|
172
|
+
const hasMetadata = columns.some((col) => col.name === 'metadata');
|
|
173
|
+
const hasParentPid = columns.some((col) => col.name === 'parent_pid');
|
|
174
|
+
const hasChildPids = columns.some((col) => col.name === 'child_pids');
|
|
175
|
+
|
|
176
|
+
// Timestamp columns
|
|
177
|
+
const hasUpdatedAt = columns.some((col) => col.name === 'updated_at');
|
|
178
|
+
const hasPausedAt = columns.some((col) => col.name === 'paused_at');
|
|
179
|
+
const hasResumedAt = columns.some((col) => col.name === 'resumed_at');
|
|
180
|
+
const hasCompletionPercentage = columns.some((col) => col.name === 'completion_percentage');
|
|
181
|
+
|
|
182
|
+
if (!hasObjective) {
|
|
183
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN objective TEXT');
|
|
184
|
+
console.log('Added objective column to sessions table');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!hasSwarmName) {
|
|
188
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN swarm_name TEXT');
|
|
189
|
+
console.log('Added swarm_name column to sessions table');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!hasCheckpointData) {
|
|
193
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN checkpoint_data TEXT');
|
|
194
|
+
console.log('Added checkpoint_data column to sessions table');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!hasMetadata) {
|
|
198
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN metadata TEXT');
|
|
199
|
+
console.log('Added metadata column to sessions table');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!hasParentPid) {
|
|
203
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN parent_pid INTEGER');
|
|
204
|
+
console.log('Added parent_pid column to sessions table');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!hasChildPids) {
|
|
208
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN child_pids TEXT');
|
|
209
|
+
console.log('Added child_pids column to sessions table');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!hasUpdatedAt) {
|
|
213
|
+
this.db.exec(
|
|
214
|
+
'ALTER TABLE sessions ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP',
|
|
215
|
+
);
|
|
216
|
+
console.log('Added updated_at column to sessions table');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!hasPausedAt) {
|
|
220
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN paused_at DATETIME');
|
|
221
|
+
console.log('Added paused_at column to sessions table');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!hasResumedAt) {
|
|
225
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN resumed_at DATETIME');
|
|
226
|
+
console.log('Added resumed_at column to sessions table');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!hasCompletionPercentage) {
|
|
230
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN completion_percentage REAL DEFAULT 0');
|
|
231
|
+
console.log('Added completion_percentage column to sessions table');
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('Migration error:', error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a new session for a swarm
|
|
240
|
+
*/
|
|
241
|
+
async createSession(swarmId, swarmName, objective, metadata = {}) {
|
|
242
|
+
await this.ensureInitialized();
|
|
243
|
+
|
|
244
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
245
|
+
|
|
246
|
+
if (this.isInMemory) {
|
|
247
|
+
// Use in-memory storage
|
|
248
|
+
const sessionData = {
|
|
249
|
+
id: sessionId,
|
|
250
|
+
swarm_id: swarmId,
|
|
251
|
+
swarm_name: swarmName,
|
|
252
|
+
objective,
|
|
253
|
+
status: 'active',
|
|
254
|
+
created_at: new Date().toISOString(),
|
|
255
|
+
updated_at: new Date().toISOString(),
|
|
256
|
+
metadata: sessionSerializer.serializeMetadata(metadata),
|
|
257
|
+
parent_pid: process.pid,
|
|
258
|
+
child_pids: '[]',
|
|
259
|
+
};
|
|
260
|
+
this.memoryStore.sessions.set(sessionId, sessionData);
|
|
261
|
+
} else {
|
|
262
|
+
// Use SQLite
|
|
263
|
+
const stmt = this.db.prepare(`
|
|
264
|
+
INSERT INTO sessions (id, swarm_id, swarm_name, objective, metadata, parent_pid)
|
|
265
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
266
|
+
`);
|
|
267
|
+
|
|
268
|
+
stmt.run(
|
|
269
|
+
sessionId,
|
|
270
|
+
swarmId,
|
|
271
|
+
swarmName,
|
|
272
|
+
objective,
|
|
273
|
+
sessionSerializer.serializeMetadata(metadata),
|
|
274
|
+
process.pid,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Log session creation
|
|
279
|
+
await this.logSessionEvent(sessionId, 'info', 'Session created', null, {
|
|
280
|
+
swarmId,
|
|
281
|
+
swarmName,
|
|
282
|
+
objective,
|
|
283
|
+
parentPid: process.pid,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return sessionId;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Save session checkpoint
|
|
291
|
+
*/
|
|
292
|
+
async saveCheckpoint(sessionId, checkpointName, checkpointData) {
|
|
293
|
+
await this.ensureInitialized();
|
|
294
|
+
|
|
295
|
+
const checkpointId = `checkpoint-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
296
|
+
|
|
297
|
+
if (this.isInMemory) {
|
|
298
|
+
// Use in-memory storage
|
|
299
|
+
const checkpointEntry = {
|
|
300
|
+
id: checkpointId,
|
|
301
|
+
session_id: sessionId,
|
|
302
|
+
checkpoint_name: checkpointName,
|
|
303
|
+
checkpoint_data: sessionSerializer.serializeCheckpointData(checkpointData),
|
|
304
|
+
created_at: new Date().toISOString(),
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
if (!this.memoryStore.checkpoints.has(sessionId)) {
|
|
308
|
+
this.memoryStore.checkpoints.set(sessionId, []);
|
|
309
|
+
}
|
|
310
|
+
this.memoryStore.checkpoints.get(sessionId).push(checkpointEntry);
|
|
311
|
+
|
|
312
|
+
// Update session data
|
|
313
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
314
|
+
if (session) {
|
|
315
|
+
session.checkpoint_data = sessionSerializer.serializeCheckpointData(checkpointData);
|
|
316
|
+
session.updated_at = new Date().toISOString();
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
// Save to database
|
|
320
|
+
const stmt = this.db.prepare(`
|
|
321
|
+
INSERT INTO session_checkpoints (id, session_id, checkpoint_name, checkpoint_data)
|
|
322
|
+
VALUES (?, ?, ?, ?)
|
|
323
|
+
`);
|
|
324
|
+
|
|
325
|
+
stmt.run(
|
|
326
|
+
checkpointId,
|
|
327
|
+
sessionId,
|
|
328
|
+
checkpointName,
|
|
329
|
+
sessionSerializer.serializeCheckpointData(checkpointData),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Update session checkpoint data and timestamp
|
|
333
|
+
const updateStmt = this.db.prepare(`
|
|
334
|
+
UPDATE sessions
|
|
335
|
+
SET checkpoint_data = ?, updated_at = CURRENT_TIMESTAMP
|
|
336
|
+
WHERE id = ?
|
|
337
|
+
`);
|
|
338
|
+
|
|
339
|
+
updateStmt.run(sessionSerializer.serializeCheckpointData(checkpointData), sessionId);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Save checkpoint file for backup
|
|
343
|
+
const checkpointFile = path.join(this.sessionsDir, `${sessionId}-${checkpointName}.json`);
|
|
344
|
+
await writeFile(
|
|
345
|
+
checkpointFile,
|
|
346
|
+
sessionSerializer.serializeSessionData({
|
|
347
|
+
sessionId,
|
|
348
|
+
checkpointId,
|
|
349
|
+
checkpointName,
|
|
350
|
+
timestamp: new Date().toISOString(),
|
|
351
|
+
data: checkpointData,
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
await this.logSessionEvent(sessionId, 'info', `Checkpoint saved: ${checkpointName}`, null, {
|
|
356
|
+
checkpointId,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return checkpointId;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Get active sessions
|
|
364
|
+
*/
|
|
365
|
+
async getActiveSessions() {
|
|
366
|
+
await this.ensureInitialized();
|
|
367
|
+
|
|
368
|
+
if (this.isInMemory) {
|
|
369
|
+
// Use in-memory storage
|
|
370
|
+
const sessions = [];
|
|
371
|
+
for (const [sessionId, session] of this.memoryStore.sessions) {
|
|
372
|
+
if (session.status === 'active' || session.status === 'paused') {
|
|
373
|
+
sessions.push({
|
|
374
|
+
...session,
|
|
375
|
+
metadata: session.metadata
|
|
376
|
+
? sessionSerializer.deserializeMetadata(session.metadata)
|
|
377
|
+
: {},
|
|
378
|
+
checkpoint_data: session.checkpoint_data
|
|
379
|
+
? sessionSerializer.deserializeCheckpointData(session.checkpoint_data)
|
|
380
|
+
: null,
|
|
381
|
+
agent_count: 0, // Not tracked in memory mode
|
|
382
|
+
task_count: 0, // Not tracked in memory mode
|
|
383
|
+
completed_tasks: 0, // Not tracked in memory mode
|
|
384
|
+
completion_percentage: 0,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return sessions.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
|
|
389
|
+
} else {
|
|
390
|
+
// Use SQLite
|
|
391
|
+
const stmt = this.db.prepare(`
|
|
392
|
+
SELECT s.*,
|
|
393
|
+
COUNT(DISTINCT a.id) as agent_count,
|
|
394
|
+
COUNT(DISTINCT t.id) as task_count,
|
|
395
|
+
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as completed_tasks
|
|
396
|
+
FROM sessions s
|
|
397
|
+
LEFT JOIN agents a ON s.swarm_id = a.swarm_id
|
|
398
|
+
LEFT JOIN tasks t ON s.swarm_id = t.swarm_id
|
|
399
|
+
WHERE s.status = 'active' OR s.status = 'paused'
|
|
400
|
+
GROUP BY s.id
|
|
401
|
+
ORDER BY s.updated_at DESC
|
|
402
|
+
`);
|
|
403
|
+
|
|
404
|
+
const sessions = stmt.all();
|
|
405
|
+
|
|
406
|
+
// Parse JSON fields
|
|
407
|
+
return sessions.map((session) => ({
|
|
408
|
+
...session,
|
|
409
|
+
metadata: session.metadata ? sessionSerializer.deserializeMetadata(session.metadata) : {},
|
|
410
|
+
checkpoint_data: session.checkpoint_data
|
|
411
|
+
? sessionSerializer.deserializeCheckpointData(session.checkpoint_data)
|
|
412
|
+
: null,
|
|
413
|
+
completion_percentage:
|
|
414
|
+
session.task_count > 0
|
|
415
|
+
? Math.round((session.completed_tasks / session.task_count) * 100)
|
|
416
|
+
: 0,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get session by ID with full details
|
|
423
|
+
*/
|
|
424
|
+
async getSession(sessionId) {
|
|
425
|
+
await this.ensureInitialized();
|
|
426
|
+
|
|
427
|
+
if (this.isInMemory) {
|
|
428
|
+
// Use in-memory storage
|
|
429
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
430
|
+
if (!session) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Return simplified session data for in-memory mode
|
|
435
|
+
return {
|
|
436
|
+
...session,
|
|
437
|
+
metadata: session.metadata ? sessionSerializer.deserializeMetadata(session.metadata) : {},
|
|
438
|
+
checkpoint_data: session.checkpoint_data
|
|
439
|
+
? sessionSerializer.deserializeCheckpointData(session.checkpoint_data)
|
|
440
|
+
: null,
|
|
441
|
+
swarm: null, // Not available in memory mode
|
|
442
|
+
agents: [], // Not available in memory mode
|
|
443
|
+
tasks: [], // Not available in memory mode
|
|
444
|
+
checkpoints: this.memoryStore.checkpoints.get(sessionId) || [],
|
|
445
|
+
recentLogs: this.memoryStore.logs.get(sessionId) || [],
|
|
446
|
+
statistics: {
|
|
447
|
+
totalAgents: 0,
|
|
448
|
+
activeAgents: 0,
|
|
449
|
+
totalTasks: 0,
|
|
450
|
+
completedTasks: 0,
|
|
451
|
+
pendingTasks: 0,
|
|
452
|
+
inProgressTasks: 0,
|
|
453
|
+
completionPercentage: session.completion_percentage || 0,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const session = this.db
|
|
459
|
+
.prepare(
|
|
460
|
+
`
|
|
461
|
+
SELECT * FROM sessions WHERE id = ?
|
|
462
|
+
`,
|
|
463
|
+
)
|
|
464
|
+
.get(sessionId);
|
|
465
|
+
|
|
466
|
+
if (!session) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Get associated swarm data
|
|
471
|
+
const swarm = this.db
|
|
472
|
+
.prepare(
|
|
473
|
+
`
|
|
474
|
+
SELECT * FROM swarms WHERE id = ?
|
|
475
|
+
`,
|
|
476
|
+
)
|
|
477
|
+
.get(session.swarm_id);
|
|
478
|
+
|
|
479
|
+
// Get agents
|
|
480
|
+
const agents = this.db
|
|
481
|
+
.prepare(
|
|
482
|
+
`
|
|
483
|
+
SELECT * FROM agents WHERE swarm_id = ?
|
|
484
|
+
`,
|
|
485
|
+
)
|
|
486
|
+
.all(session.swarm_id);
|
|
487
|
+
|
|
488
|
+
// Get tasks
|
|
489
|
+
const tasks = this.db
|
|
490
|
+
.prepare(
|
|
491
|
+
`
|
|
492
|
+
SELECT * FROM tasks WHERE swarm_id = ?
|
|
493
|
+
`,
|
|
494
|
+
)
|
|
495
|
+
.all(session.swarm_id);
|
|
496
|
+
|
|
497
|
+
// Get checkpoints
|
|
498
|
+
const checkpoints = this.db
|
|
499
|
+
.prepare(
|
|
500
|
+
`
|
|
501
|
+
SELECT * FROM session_checkpoints
|
|
502
|
+
WHERE session_id = ?
|
|
503
|
+
ORDER BY created_at DESC
|
|
504
|
+
`,
|
|
505
|
+
)
|
|
506
|
+
.all(sessionId);
|
|
507
|
+
|
|
508
|
+
// Get recent logs
|
|
509
|
+
const recentLogs = this.db
|
|
510
|
+
.prepare(
|
|
511
|
+
`
|
|
512
|
+
SELECT * FROM session_logs
|
|
513
|
+
WHERE session_id = ?
|
|
514
|
+
ORDER BY timestamp DESC
|
|
515
|
+
LIMIT 50
|
|
516
|
+
`,
|
|
517
|
+
)
|
|
518
|
+
.all(sessionId);
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
...session,
|
|
522
|
+
metadata: session.metadata ? sessionSerializer.deserializeMetadata(session.metadata) : {},
|
|
523
|
+
checkpoint_data: session.checkpoint_data
|
|
524
|
+
? sessionSerializer.deserializeCheckpointData(session.checkpoint_data)
|
|
525
|
+
: null,
|
|
526
|
+
swarm,
|
|
527
|
+
agents,
|
|
528
|
+
tasks,
|
|
529
|
+
checkpoints: checkpoints.map((cp) => ({
|
|
530
|
+
...cp,
|
|
531
|
+
checkpoint_data: sessionSerializer.deserializeCheckpointData(cp.checkpoint_data),
|
|
532
|
+
})),
|
|
533
|
+
recentLogs,
|
|
534
|
+
statistics: {
|
|
535
|
+
totalAgents: agents.length,
|
|
536
|
+
activeAgents: agents.filter((a) => a.status === 'active' || a.status === 'busy').length,
|
|
537
|
+
totalTasks: tasks.length,
|
|
538
|
+
completedTasks: tasks.filter((t) => t.status === 'completed').length,
|
|
539
|
+
pendingTasks: tasks.filter((t) => t.status === 'pending').length,
|
|
540
|
+
inProgressTasks: tasks.filter((t) => t.status === 'in_progress').length,
|
|
541
|
+
completionPercentage:
|
|
542
|
+
tasks.length > 0
|
|
543
|
+
? Math.round(
|
|
544
|
+
(tasks.filter((t) => t.status === 'completed').length / tasks.length) * 100,
|
|
545
|
+
)
|
|
546
|
+
: 0,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Pause a session
|
|
553
|
+
*/
|
|
554
|
+
async pauseSession(sessionId) {
|
|
555
|
+
await this.ensureInitialized();
|
|
556
|
+
|
|
557
|
+
if (this.isInMemory) {
|
|
558
|
+
// Use in-memory storage
|
|
559
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
560
|
+
if (session) {
|
|
561
|
+
session.status = 'paused';
|
|
562
|
+
session.paused_at = new Date().toISOString();
|
|
563
|
+
session.updated_at = new Date().toISOString();
|
|
564
|
+
|
|
565
|
+
await this.logSessionEvent(sessionId, 'info', 'Session paused');
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
} else {
|
|
570
|
+
// Use SQLite
|
|
571
|
+
const stmt = this.db.prepare(`
|
|
572
|
+
UPDATE sessions
|
|
573
|
+
SET status = 'paused', paused_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
|
574
|
+
WHERE id = ?
|
|
575
|
+
`);
|
|
576
|
+
|
|
577
|
+
const result = stmt.run(sessionId);
|
|
578
|
+
|
|
579
|
+
if (result.changes > 0) {
|
|
580
|
+
await this.logSessionEvent(sessionId, 'info', 'Session paused');
|
|
581
|
+
|
|
582
|
+
// Update swarm status
|
|
583
|
+
const session = this.db
|
|
584
|
+
.prepare('SELECT swarm_id FROM sessions WHERE id = ?')
|
|
585
|
+
.get(sessionId);
|
|
586
|
+
if (session) {
|
|
587
|
+
this.db
|
|
588
|
+
.prepare('UPDATE swarms SET status = ? WHERE id = ?')
|
|
589
|
+
.run('paused', session.swarm_id);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return result.changes > 0;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Resume any previous session (paused, stopped, or inactive)
|
|
599
|
+
*/
|
|
600
|
+
async resumeSession(sessionId) {
|
|
601
|
+
const session = await this.getSession(sessionId);
|
|
602
|
+
|
|
603
|
+
if (!session) {
|
|
604
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Allow resuming any session regardless of status
|
|
608
|
+
console.log(`Resuming session ${sessionId} from status: ${session.status}`);
|
|
609
|
+
|
|
610
|
+
// If session was stopped, log that we're restarting it
|
|
611
|
+
if (session.status === 'stopped') {
|
|
612
|
+
await this.logSessionEvent(
|
|
613
|
+
sessionId,
|
|
614
|
+
'info',
|
|
615
|
+
`Restarting stopped session with original configuration`,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Update session status
|
|
620
|
+
if (this.isInMemory) {
|
|
621
|
+
// Use in-memory storage
|
|
622
|
+
const sessionData = this.memoryStore.sessions.get(sessionId);
|
|
623
|
+
if (sessionData) {
|
|
624
|
+
sessionData.status = 'active';
|
|
625
|
+
sessionData.resumed_at = new Date().toISOString();
|
|
626
|
+
sessionData.updated_at = new Date().toISOString();
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
// Use SQLite
|
|
630
|
+
const stmt = this.db.prepare(`
|
|
631
|
+
UPDATE sessions
|
|
632
|
+
SET status = 'active', resumed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
|
633
|
+
WHERE id = ?
|
|
634
|
+
`);
|
|
635
|
+
|
|
636
|
+
stmt.run(sessionId);
|
|
637
|
+
|
|
638
|
+
// Update swarm status
|
|
639
|
+
this.db.prepare('UPDATE swarms SET status = ? WHERE id = ?').run('active', session.swarm_id);
|
|
640
|
+
|
|
641
|
+
// Update agent statuses
|
|
642
|
+
this.db
|
|
643
|
+
.prepare(
|
|
644
|
+
`
|
|
645
|
+
UPDATE agents
|
|
646
|
+
SET status = CASE
|
|
647
|
+
WHEN role = 'queen' THEN 'active'
|
|
648
|
+
ELSE 'idle'
|
|
649
|
+
END
|
|
650
|
+
WHERE swarm_id = ?
|
|
651
|
+
`,
|
|
652
|
+
)
|
|
653
|
+
.run(session.swarm_id);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
await this.logSessionEvent(sessionId, 'info', 'Session resumed', null, {
|
|
657
|
+
pausedDuration: session.paused_at ? new Date() - new Date(session.paused_at) : null,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
return session;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Mark session as completed
|
|
665
|
+
*/
|
|
666
|
+
async completeSession(sessionId) {
|
|
667
|
+
await this.ensureInitialized();
|
|
668
|
+
|
|
669
|
+
if (this.isInMemory) {
|
|
670
|
+
// Use in-memory storage
|
|
671
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
672
|
+
if (session) {
|
|
673
|
+
session.status = 'completed';
|
|
674
|
+
session.updated_at = new Date().toISOString();
|
|
675
|
+
session.completion_percentage = 100;
|
|
676
|
+
|
|
677
|
+
await this.logSessionEvent(sessionId, 'info', 'Session completed');
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
} else {
|
|
682
|
+
// Use SQLite
|
|
683
|
+
const stmt = this.db.prepare(`
|
|
684
|
+
UPDATE sessions
|
|
685
|
+
SET status = 'completed', updated_at = CURRENT_TIMESTAMP, completion_percentage = 100
|
|
686
|
+
WHERE id = ?
|
|
687
|
+
`);
|
|
688
|
+
|
|
689
|
+
const result = stmt.run(sessionId);
|
|
690
|
+
|
|
691
|
+
if (result.changes > 0) {
|
|
692
|
+
await this.logSessionEvent(sessionId, 'info', 'Session completed');
|
|
693
|
+
|
|
694
|
+
// Update swarm status
|
|
695
|
+
const session = this.db
|
|
696
|
+
.prepare('SELECT swarm_id FROM sessions WHERE id = ?')
|
|
697
|
+
.get(sessionId);
|
|
698
|
+
if (session) {
|
|
699
|
+
this.db
|
|
700
|
+
.prepare('UPDATE swarms SET status = ? WHERE id = ?')
|
|
701
|
+
.run('completed', session.swarm_id);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return result.changes > 0;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Archive old sessions
|
|
711
|
+
*/
|
|
712
|
+
async archiveSessions(daysOld = 30) {
|
|
713
|
+
await this.ensureInitialized();
|
|
714
|
+
|
|
715
|
+
if (this.isInMemory) {
|
|
716
|
+
// In-memory mode doesn't support archiving
|
|
717
|
+
console.warn('Session archiving not supported in in-memory mode');
|
|
718
|
+
return 0;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const cutoffDate = new Date();
|
|
722
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
|
|
723
|
+
|
|
724
|
+
const sessionsToArchive = this.db
|
|
725
|
+
.prepare(
|
|
726
|
+
`
|
|
727
|
+
SELECT * FROM sessions
|
|
728
|
+
WHERE status = 'completed' AND updated_at < ?
|
|
729
|
+
`,
|
|
730
|
+
)
|
|
731
|
+
.all(cutoffDate.toISOString());
|
|
732
|
+
|
|
733
|
+
const archiveDir = path.join(this.sessionsDir, 'archive');
|
|
734
|
+
if (!existsSync(archiveDir)) {
|
|
735
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
for (const session of sessionsToArchive) {
|
|
739
|
+
const sessionData = await this.getSession(session.id);
|
|
740
|
+
const archiveFile = path.join(archiveDir, `${session.id}-archive.json`);
|
|
741
|
+
|
|
742
|
+
await writeFile(archiveFile, sessionSerializer.serializeSessionData(sessionData));
|
|
743
|
+
|
|
744
|
+
// Remove from database
|
|
745
|
+
this.db.prepare('DELETE FROM session_logs WHERE session_id = ?').run(session.id);
|
|
746
|
+
this.db.prepare('DELETE FROM session_checkpoints WHERE session_id = ?').run(session.id);
|
|
747
|
+
this.db.prepare('DELETE FROM sessions WHERE id = ?').run(session.id);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return sessionsToArchive.length;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Log session event
|
|
755
|
+
*/
|
|
756
|
+
async logSessionEvent(sessionId, logLevel, message, agentId = null, data = null) {
|
|
757
|
+
await this.ensureInitialized();
|
|
758
|
+
|
|
759
|
+
if (this.isInMemory) {
|
|
760
|
+
// Use in-memory storage for logs
|
|
761
|
+
const logId = `log-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
762
|
+
const logEntry = {
|
|
763
|
+
id: logId,
|
|
764
|
+
session_id: sessionId,
|
|
765
|
+
timestamp: new Date().toISOString(),
|
|
766
|
+
log_level: logLevel,
|
|
767
|
+
message,
|
|
768
|
+
agent_id: agentId,
|
|
769
|
+
data: data ? sessionSerializer.serializeLogData(data) : null,
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
if (!this.memoryStore.logs.has(sessionId)) {
|
|
773
|
+
this.memoryStore.logs.set(sessionId, []);
|
|
774
|
+
}
|
|
775
|
+
this.memoryStore.logs.get(sessionId).push(logEntry);
|
|
776
|
+
} else {
|
|
777
|
+
// Use SQLite
|
|
778
|
+
const stmt = this.db.prepare(`
|
|
779
|
+
INSERT INTO session_logs (session_id, log_level, message, agent_id, data)
|
|
780
|
+
VALUES (?, ?, ?, ?, ?)
|
|
781
|
+
`);
|
|
782
|
+
|
|
783
|
+
stmt.run(
|
|
784
|
+
sessionId,
|
|
785
|
+
logLevel,
|
|
786
|
+
message,
|
|
787
|
+
agentId,
|
|
788
|
+
data ? sessionSerializer.serializeLogData(data) : null,
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Get session logs
|
|
795
|
+
*/
|
|
796
|
+
async getSessionLogs(sessionId, limit = 100, offset = 0) {
|
|
797
|
+
await this.ensureInitialized();
|
|
798
|
+
|
|
799
|
+
if (this.isInMemory) {
|
|
800
|
+
// Use in-memory storage
|
|
801
|
+
const logs = this.memoryStore.logs.get(sessionId) || [];
|
|
802
|
+
return logs.slice(offset, offset + limit).map((log) => ({
|
|
803
|
+
...log,
|
|
804
|
+
data: log.data ? sessionSerializer.deserializeLogData(log.data) : null,
|
|
805
|
+
}));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const stmt = this.db.prepare(`
|
|
809
|
+
SELECT * FROM session_logs
|
|
810
|
+
WHERE session_id = ?
|
|
811
|
+
ORDER BY timestamp DESC
|
|
812
|
+
LIMIT ? OFFSET ?
|
|
813
|
+
`);
|
|
814
|
+
|
|
815
|
+
const logs = stmt.all(sessionId, limit, offset);
|
|
816
|
+
|
|
817
|
+
return logs.map((log) => ({
|
|
818
|
+
...log,
|
|
819
|
+
data: log.data ? sessionSerializer.deserializeLogData(log.data) : null,
|
|
820
|
+
}));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Update session progress
|
|
825
|
+
*/
|
|
826
|
+
async updateSessionProgress(sessionId, completionPercentage) {
|
|
827
|
+
await this.ensureInitialized();
|
|
828
|
+
|
|
829
|
+
if (this.isInMemory) {
|
|
830
|
+
// Use in-memory storage
|
|
831
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
832
|
+
if (session) {
|
|
833
|
+
session.completion_percentage = completionPercentage;
|
|
834
|
+
session.updated_at = new Date().toISOString();
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
// Use SQLite
|
|
838
|
+
const stmt = this.db.prepare(`
|
|
839
|
+
UPDATE sessions
|
|
840
|
+
SET completion_percentage = ?, updated_at = CURRENT_TIMESTAMP
|
|
841
|
+
WHERE id = ?
|
|
842
|
+
`);
|
|
843
|
+
|
|
844
|
+
stmt.run(completionPercentage, sessionId);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Generate session summary
|
|
850
|
+
*/
|
|
851
|
+
async generateSessionSummary(sessionId) {
|
|
852
|
+
const session = await this.getSession(sessionId);
|
|
853
|
+
|
|
854
|
+
if (!session) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const duration =
|
|
859
|
+
session.paused_at && session.resumed_at
|
|
860
|
+
? new Date(session.updated_at) -
|
|
861
|
+
new Date(session.created_at) -
|
|
862
|
+
(new Date(session.resumed_at) - new Date(session.paused_at))
|
|
863
|
+
: new Date(session.updated_at) - new Date(session.created_at);
|
|
864
|
+
|
|
865
|
+
const tasksByType = session.agents.reduce((acc, agent) => {
|
|
866
|
+
const agentTasks = session.tasks.filter((t) => t.agent_id === agent.id);
|
|
867
|
+
if (!acc[agent.type]) {
|
|
868
|
+
acc[agent.type] = {
|
|
869
|
+
total: 0,
|
|
870
|
+
completed: 0,
|
|
871
|
+
inProgress: 0,
|
|
872
|
+
pending: 0,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
acc[agent.type].total += agentTasks.length;
|
|
876
|
+
acc[agent.type].completed += agentTasks.filter((t) => t.status === 'completed').length;
|
|
877
|
+
acc[agent.type].inProgress += agentTasks.filter((t) => t.status === 'in_progress').length;
|
|
878
|
+
acc[agent.type].pending += agentTasks.filter((t) => t.status === 'pending').length;
|
|
879
|
+
return acc;
|
|
880
|
+
}, {});
|
|
881
|
+
|
|
882
|
+
return {
|
|
883
|
+
sessionId: session.id,
|
|
884
|
+
swarmName: session.swarm_name,
|
|
885
|
+
objective: session.objective,
|
|
886
|
+
status: session.status,
|
|
887
|
+
duration: Math.round(duration / 1000 / 60), // minutes
|
|
888
|
+
statistics: session.statistics,
|
|
889
|
+
tasksByType,
|
|
890
|
+
checkpointCount: session.checkpoints.length,
|
|
891
|
+
lastCheckpoint: session.checkpoints[0] || null,
|
|
892
|
+
timeline: {
|
|
893
|
+
created: session.created_at,
|
|
894
|
+
lastUpdated: session.updated_at,
|
|
895
|
+
paused: session.paused_at,
|
|
896
|
+
resumed: session.resumed_at,
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Export session data
|
|
903
|
+
*/
|
|
904
|
+
async exportSession(sessionId, exportPath = null) {
|
|
905
|
+
const session = await this.getSession(sessionId);
|
|
906
|
+
|
|
907
|
+
if (!session) {
|
|
908
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const exportFile = exportPath || path.join(this.sessionsDir, `${sessionId}-export.json`);
|
|
912
|
+
|
|
913
|
+
await writeFile(exportFile, sessionSerializer.serializeSessionData(session));
|
|
914
|
+
|
|
915
|
+
return exportFile;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Import session data
|
|
920
|
+
*/
|
|
921
|
+
async importSession(importPath) {
|
|
922
|
+
const sessionData = sessionSerializer.deserializeSessionData(
|
|
923
|
+
await readFile(importPath, 'utf8'),
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
// Create new session with imported data
|
|
927
|
+
const newSessionId = this.createSession(
|
|
928
|
+
sessionData.swarm_id,
|
|
929
|
+
sessionData.swarm_name,
|
|
930
|
+
sessionData.objective,
|
|
931
|
+
sessionData.metadata,
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
// Import checkpoints
|
|
935
|
+
for (const checkpoint of sessionData.checkpoints || []) {
|
|
936
|
+
await this.saveCheckpoint(
|
|
937
|
+
newSessionId,
|
|
938
|
+
checkpoint.checkpoint_name,
|
|
939
|
+
checkpoint.checkpoint_data,
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Import logs
|
|
944
|
+
for (const log of sessionData.recentLogs || []) {
|
|
945
|
+
await this.logSessionEvent(
|
|
946
|
+
newSessionId,
|
|
947
|
+
log.log_level,
|
|
948
|
+
log.message,
|
|
949
|
+
log.agent_id,
|
|
950
|
+
log.data ? sessionSerializer.deserializeLogData(log.data) : null,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return newSessionId;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Add a child process PID to session
|
|
959
|
+
*/
|
|
960
|
+
async addChildPid(sessionId, pid) {
|
|
961
|
+
await this.ensureInitialized();
|
|
962
|
+
|
|
963
|
+
if (this.isInMemory) {
|
|
964
|
+
// Use in-memory storage
|
|
965
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
966
|
+
if (!session) return false;
|
|
967
|
+
|
|
968
|
+
const childPids = session.child_pids
|
|
969
|
+
? sessionSerializer.deserializeLogData(session.child_pids)
|
|
970
|
+
: [];
|
|
971
|
+
if (!childPids.includes(pid)) {
|
|
972
|
+
childPids.push(pid);
|
|
973
|
+
}
|
|
974
|
+
session.child_pids = sessionSerializer.serializeLogData(childPids);
|
|
975
|
+
session.updated_at = new Date().toISOString();
|
|
976
|
+
|
|
977
|
+
await this.logSessionEvent(sessionId, 'info', 'Child process added', null, { pid });
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const session = this.db.prepare('SELECT child_pids FROM sessions WHERE id = ?').get(sessionId);
|
|
982
|
+
if (!session) return false;
|
|
983
|
+
|
|
984
|
+
const childPids = session.child_pids
|
|
985
|
+
? sessionSerializer.deserializeLogData(session.child_pids)
|
|
986
|
+
: [];
|
|
987
|
+
if (!childPids.includes(pid)) {
|
|
988
|
+
childPids.push(pid);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const stmt = this.db.prepare(`
|
|
992
|
+
UPDATE sessions
|
|
993
|
+
SET child_pids = ?, updated_at = CURRENT_TIMESTAMP
|
|
994
|
+
WHERE id = ?
|
|
995
|
+
`);
|
|
996
|
+
|
|
997
|
+
stmt.run(sessionSerializer.serializeLogData(childPids), sessionId);
|
|
998
|
+
|
|
999
|
+
await this.logSessionEvent(sessionId, 'info', 'Child process added', null, { pid });
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Remove a child process PID from session
|
|
1005
|
+
*/
|
|
1006
|
+
async removeChildPid(sessionId, pid) {
|
|
1007
|
+
await this.ensureInitialized();
|
|
1008
|
+
|
|
1009
|
+
if (this.isInMemory) {
|
|
1010
|
+
// Use in-memory storage
|
|
1011
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
1012
|
+
if (!session) return false;
|
|
1013
|
+
|
|
1014
|
+
const childPids = session.child_pids
|
|
1015
|
+
? sessionSerializer.deserializeLogData(session.child_pids)
|
|
1016
|
+
: [];
|
|
1017
|
+
const index = childPids.indexOf(pid);
|
|
1018
|
+
if (index > -1) {
|
|
1019
|
+
childPids.splice(index, 1);
|
|
1020
|
+
}
|
|
1021
|
+
session.child_pids = sessionSerializer.serializeLogData(childPids);
|
|
1022
|
+
session.updated_at = new Date().toISOString();
|
|
1023
|
+
|
|
1024
|
+
await this.logSessionEvent(sessionId, 'info', 'Child process removed', null, { pid });
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Check if database connection is still open before operations
|
|
1029
|
+
if (!this.db || !this.db.open) {
|
|
1030
|
+
console.warn('Database connection closed, cannot remove child PID during cleanup');
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const session = this.db.prepare('SELECT child_pids FROM sessions WHERE id = ?').get(sessionId);
|
|
1035
|
+
if (!session) return false;
|
|
1036
|
+
|
|
1037
|
+
const childPids = session.child_pids
|
|
1038
|
+
? sessionSerializer.deserializeLogData(session.child_pids)
|
|
1039
|
+
: [];
|
|
1040
|
+
const index = childPids.indexOf(pid);
|
|
1041
|
+
if (index > -1) {
|
|
1042
|
+
childPids.splice(index, 1);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const stmt = this.db.prepare(`
|
|
1046
|
+
UPDATE sessions
|
|
1047
|
+
SET child_pids = ?, updated_at = CURRENT_TIMESTAMP
|
|
1048
|
+
WHERE id = ?
|
|
1049
|
+
`);
|
|
1050
|
+
|
|
1051
|
+
stmt.run(sessionSerializer.serializeLogData(childPids), sessionId);
|
|
1052
|
+
|
|
1053
|
+
await this.logSessionEvent(sessionId, 'info', 'Child process removed', null, { pid });
|
|
1054
|
+
return true;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Get all child PIDs for a session
|
|
1059
|
+
*/
|
|
1060
|
+
async getChildPids(sessionId) {
|
|
1061
|
+
await this.ensureInitialized();
|
|
1062
|
+
|
|
1063
|
+
if (this.isInMemory) {
|
|
1064
|
+
// Use in-memory storage
|
|
1065
|
+
const session = this.memoryStore.sessions.get(sessionId);
|
|
1066
|
+
if (!session || !session.child_pids) return [];
|
|
1067
|
+
return sessionSerializer.deserializeLogData(session.child_pids);
|
|
1068
|
+
} else {
|
|
1069
|
+
// Check if database connection is still open
|
|
1070
|
+
if (!this.db || !this.db.open) {
|
|
1071
|
+
console.warn('Database connection closed, cannot get child PIDs during cleanup');
|
|
1072
|
+
return [];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Use SQLite
|
|
1076
|
+
const session = this.db
|
|
1077
|
+
.prepare('SELECT child_pids FROM sessions WHERE id = ?')
|
|
1078
|
+
.get(sessionId);
|
|
1079
|
+
if (!session || !session.child_pids) return [];
|
|
1080
|
+
return sessionSerializer.deserializeLogData(session.child_pids);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Stop a session and terminate all child processes
|
|
1086
|
+
*/
|
|
1087
|
+
async stopSession(sessionId) {
|
|
1088
|
+
const session = await this.getSession(sessionId);
|
|
1089
|
+
if (!session) {
|
|
1090
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Get child PIDs
|
|
1094
|
+
const childPids = await this.getChildPids(sessionId);
|
|
1095
|
+
|
|
1096
|
+
// Terminate child processes
|
|
1097
|
+
for (const pid of childPids) {
|
|
1098
|
+
try {
|
|
1099
|
+
process.kill(pid, 'SIGTERM');
|
|
1100
|
+
await this.logSessionEvent(sessionId, 'info', 'Child process terminated', null, { pid });
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
// Process might already be dead
|
|
1103
|
+
await this.logSessionEvent(
|
|
1104
|
+
sessionId,
|
|
1105
|
+
'warning',
|
|
1106
|
+
'Failed to terminate child process',
|
|
1107
|
+
null,
|
|
1108
|
+
{
|
|
1109
|
+
pid,
|
|
1110
|
+
error: err.message,
|
|
1111
|
+
},
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Update session status
|
|
1117
|
+
if (this.isInMemory) {
|
|
1118
|
+
// Use in-memory storage
|
|
1119
|
+
const sessionData = this.memoryStore.sessions.get(sessionId);
|
|
1120
|
+
if (sessionData) {
|
|
1121
|
+
sessionData.status = 'stopped';
|
|
1122
|
+
sessionData.updated_at = new Date().toISOString();
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
// Use SQLite
|
|
1126
|
+
const stmt = this.db.prepare(`
|
|
1127
|
+
UPDATE sessions
|
|
1128
|
+
SET status = 'stopped', updated_at = CURRENT_TIMESTAMP
|
|
1129
|
+
WHERE id = ?
|
|
1130
|
+
`);
|
|
1131
|
+
|
|
1132
|
+
stmt.run(sessionId);
|
|
1133
|
+
|
|
1134
|
+
// Update swarm status
|
|
1135
|
+
this.db.prepare('UPDATE swarms SET status = ? WHERE id = ?').run('stopped', session.swarm_id);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
await this.logSessionEvent(sessionId, 'info', 'Session stopped');
|
|
1139
|
+
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Get active sessions with process information
|
|
1145
|
+
*/
|
|
1146
|
+
async getActiveSessionsWithProcessInfo() {
|
|
1147
|
+
const sessions = await this.getActiveSessions();
|
|
1148
|
+
|
|
1149
|
+
// Add process info to each session
|
|
1150
|
+
return sessions.map((session) => {
|
|
1151
|
+
const childPids = session.child_pids
|
|
1152
|
+
? sessionSerializer.deserializeLogData(session.child_pids)
|
|
1153
|
+
: [];
|
|
1154
|
+
const aliveChildPids = [];
|
|
1155
|
+
|
|
1156
|
+
// Check which child processes are still alive
|
|
1157
|
+
for (const pid of childPids) {
|
|
1158
|
+
try {
|
|
1159
|
+
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
1160
|
+
aliveChildPids.push(pid);
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
// Process is dead
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return {
|
|
1167
|
+
...session,
|
|
1168
|
+
parent_pid: session.parent_pid,
|
|
1169
|
+
child_pids: aliveChildPids,
|
|
1170
|
+
total_processes: 1 + aliveChildPids.length,
|
|
1171
|
+
};
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Clean up orphaned processes
|
|
1177
|
+
*/
|
|
1178
|
+
async cleanupOrphanedProcesses() {
|
|
1179
|
+
await this.ensureInitialized();
|
|
1180
|
+
|
|
1181
|
+
if (this.isInMemory) {
|
|
1182
|
+
// In-memory mode doesn't track orphaned processes
|
|
1183
|
+
return 0;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const sessions = this.db
|
|
1187
|
+
.prepare(
|
|
1188
|
+
`
|
|
1189
|
+
SELECT * FROM sessions
|
|
1190
|
+
WHERE status IN ('active', 'paused')
|
|
1191
|
+
`,
|
|
1192
|
+
)
|
|
1193
|
+
.all();
|
|
1194
|
+
|
|
1195
|
+
let cleanedCount = 0;
|
|
1196
|
+
|
|
1197
|
+
for (const session of sessions) {
|
|
1198
|
+
// Check if parent process is still alive
|
|
1199
|
+
try {
|
|
1200
|
+
process.kill(session.parent_pid, 0);
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
// Parent is dead, clean up session
|
|
1203
|
+
await this.stopSession(session.id);
|
|
1204
|
+
cleanedCount++;
|
|
1205
|
+
await this.logSessionEvent(session.id, 'info', 'Orphaned session cleaned up');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return cleanedCount;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Clean up and close database connection
|
|
1214
|
+
*/
|
|
1215
|
+
close() {
|
|
1216
|
+
if (this.db && !this.isInMemory) {
|
|
1217
|
+
this.db.close();
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Export for use in other modules
|
|
1223
|
+
export default HiveMindSessionManager;
|