@vinaes/succ 1.3.19
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/LICENSE +87 -0
- package/README.md +588 -0
- package/agents/succ-checkpoint-manager.md +51 -0
- package/agents/succ-code-reviewer.md +181 -0
- package/agents/succ-context-optimizer.md +83 -0
- package/agents/succ-debug.md +224 -0
- package/agents/succ-decision-auditor.md +74 -0
- package/agents/succ-deep-search.md +41 -0
- package/agents/succ-diff-reviewer.md +123 -0
- package/agents/succ-explore.md +83 -0
- package/agents/succ-general.md +109 -0
- package/agents/succ-knowledge-indexer.md +45 -0
- package/agents/succ-knowledge-mapper.md +61 -0
- package/agents/succ-memory-curator.md +43 -0
- package/agents/succ-memory-health-monitor.md +55 -0
- package/agents/succ-pattern-detective.md +62 -0
- package/agents/succ-plan.md +172 -0
- package/agents/succ-quality-improvement-coach.md +63 -0
- package/agents/succ-readiness-improver.md +62 -0
- package/agents/succ-session-handoff-orchestrator.md +54 -0
- package/agents/succ-session-reviewer.md +46 -0
- package/agents/succ-style-tracker.md +73 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents-md.d.ts +16 -0
- package/dist/commands/agents-md.d.ts.map +1 -0
- package/dist/commands/agents-md.js +33 -0
- package/dist/commands/agents-md.js.map +1 -0
- package/dist/commands/analyze-agents.d.ts +20 -0
- package/dist/commands/analyze-agents.d.ts.map +1 -0
- package/dist/commands/analyze-agents.js +305 -0
- package/dist/commands/analyze-agents.js.map +1 -0
- package/dist/commands/analyze-helpers.d.ts +22 -0
- package/dist/commands/analyze-helpers.d.ts.map +1 -0
- package/dist/commands/analyze-helpers.js +38 -0
- package/dist/commands/analyze-helpers.js.map +1 -0
- package/dist/commands/analyze-profile.d.ts +53 -0
- package/dist/commands/analyze-profile.d.ts.map +1 -0
- package/dist/commands/analyze-profile.js +638 -0
- package/dist/commands/analyze-profile.js.map +1 -0
- package/dist/commands/analyze-recursive.d.ts +20 -0
- package/dist/commands/analyze-recursive.d.ts.map +1 -0
- package/dist/commands/analyze-recursive.js +326 -0
- package/dist/commands/analyze-recursive.js.map +1 -0
- package/dist/commands/analyze-utils.d.ts +83 -0
- package/dist/commands/analyze-utils.d.ts.map +1 -0
- package/dist/commands/analyze-utils.js +541 -0
- package/dist/commands/analyze-utils.js.map +1 -0
- package/dist/commands/analyze.d.ts +15 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +265 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/backfill.d.ts +22 -0
- package/dist/commands/backfill.d.ts.map +1 -0
- package/dist/commands/backfill.js +62 -0
- package/dist/commands/backfill.js.map +1 -0
- package/dist/commands/benchmark-quality.d.ts +18 -0
- package/dist/commands/benchmark-quality.d.ts.map +1 -0
- package/dist/commands/benchmark-quality.js +316 -0
- package/dist/commands/benchmark-quality.js.map +1 -0
- package/dist/commands/benchmark-sqlite-vec.d.ts +12 -0
- package/dist/commands/benchmark-sqlite-vec.d.ts.map +1 -0
- package/dist/commands/benchmark-sqlite-vec.js +281 -0
- package/dist/commands/benchmark-sqlite-vec.js.map +1 -0
- package/dist/commands/benchmark.d.ts +57 -0
- package/dist/commands/benchmark.d.ts.map +1 -0
- package/dist/commands/benchmark.js +682 -0
- package/dist/commands/benchmark.js.map +1 -0
- package/dist/commands/chat.d.ts +14 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +84 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/checkpoint.d.ts +27 -0
- package/dist/commands/checkpoint.d.ts.map +1 -0
- package/dist/commands/checkpoint.js +181 -0
- package/dist/commands/checkpoint.js.map +1 -0
- package/dist/commands/clear.d.ts +9 -0
- package/dist/commands/clear.d.ts.map +1 -0
- package/dist/commands/clear.js +31 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/config.d.ts +26 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +247 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/consolidate.d.ts +21 -0
- package/dist/commands/consolidate.d.ts.map +1 -0
- package/dist/commands/consolidate.js +117 -0
- package/dist/commands/consolidate.js.map +1 -0
- package/dist/commands/daemon.d.ts +48 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +218 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/embedding.d.ts +26 -0
- package/dist/commands/embedding.d.ts.map +1 -0
- package/dist/commands/embedding.js +168 -0
- package/dist/commands/embedding.js.map +1 -0
- package/dist/commands/graph.d.ts +20 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +128 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/index-code.d.ts +23 -0
- package/dist/commands/index-code.d.ts.map +1 -0
- package/dist/commands/index-code.js +218 -0
- package/dist/commands/index-code.js.map +1 -0
- package/dist/commands/index.d.ts +23 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +217 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init-templates.d.ts +21 -0
- package/dist/commands/init-templates.d.ts.map +1 -0
- package/dist/commands/init-templates.js +487 -0
- package/dist/commands/init-templates.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +865 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/memories.d.ts +47 -0
- package/dist/commands/memories.d.ts.map +1 -0
- package/dist/commands/memories.js +597 -0
- package/dist/commands/memories.js.map +1 -0
- package/dist/commands/migrate.d.ts +19 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +154 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/prd.d.ts +46 -0
- package/dist/commands/prd.d.ts.map +1 -0
- package/dist/commands/prd.js +378 -0
- package/dist/commands/prd.js.map +1 -0
- package/dist/commands/precompute-context.d.ts +11 -0
- package/dist/commands/precompute-context.d.ts.map +1 -0
- package/dist/commands/precompute-context.js +12 -0
- package/dist/commands/precompute-context.js.map +1 -0
- package/dist/commands/progress.d.ts +16 -0
- package/dist/commands/progress.d.ts.map +1 -0
- package/dist/commands/progress.js +38 -0
- package/dist/commands/progress.js.map +1 -0
- package/dist/commands/reindex.d.ts +17 -0
- package/dist/commands/reindex.d.ts.map +1 -0
- package/dist/commands/reindex.js +83 -0
- package/dist/commands/reindex.js.map +1 -0
- package/dist/commands/retention.d.ts +19 -0
- package/dist/commands/retention.d.ts.map +1 -0
- package/dist/commands/retention.js +162 -0
- package/dist/commands/retention.js.map +1 -0
- package/dist/commands/score.d.ts +10 -0
- package/dist/commands/score.d.ts.map +1 -0
- package/dist/commands/score.js +28 -0
- package/dist/commands/score.js.map +1 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +89 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/session-summary.d.ts +15 -0
- package/dist/commands/session-summary.d.ts.map +1 -0
- package/dist/commands/session-summary.js +16 -0
- package/dist/commands/session-summary.js.map +1 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +222 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/soul.d.ts +9 -0
- package/dist/commands/soul.d.ts.map +1 -0
- package/dist/commands/soul.js +256 -0
- package/dist/commands/soul.js.map +1 -0
- package/dist/commands/stats.d.ts +18 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +138 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +145 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/train-bpe.d.ts +8 -0
- package/dist/commands/train-bpe.d.ts.map +1 -0
- package/dist/commands/train-bpe.js +35 -0
- package/dist/commands/train-bpe.js.map +1 -0
- package/dist/commands/watch.d.ts +22 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +171 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/daemon/analyzer.d.ts +54 -0
- package/dist/daemon/analyzer.d.ts.map +1 -0
- package/dist/daemon/analyzer.js +362 -0
- package/dist/daemon/analyzer.js.map +1 -0
- package/dist/daemon/client.d.ts +87 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +356 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/index.d.ts +12 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +12 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/service.d.ts +51 -0
- package/dist/daemon/service.d.ts.map +1 -0
- package/dist/daemon/service.js +1203 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/daemon/session-processor.d.ts +85 -0
- package/dist/daemon/session-processor.d.ts.map +1 -0
- package/dist/daemon/session-processor.js +571 -0
- package/dist/daemon/session-processor.js.map +1 -0
- package/dist/daemon/sessions.d.ts +62 -0
- package/dist/daemon/sessions.d.ts.map +1 -0
- package/dist/daemon/sessions.js +192 -0
- package/dist/daemon/sessions.js.map +1 -0
- package/dist/daemon/watcher.d.ts +52 -0
- package/dist/daemon/watcher.d.ts.map +1 -0
- package/dist/daemon/watcher.js +363 -0
- package/dist/daemon/watcher.js.map +1 -0
- package/dist/lib/agents-md-generator.d.ts +33 -0
- package/dist/lib/agents-md-generator.d.ts.map +1 -0
- package/dist/lib/agents-md-generator.js +156 -0
- package/dist/lib/agents-md-generator.js.map +1 -0
- package/dist/lib/ai-readiness.d.ts +132 -0
- package/dist/lib/ai-readiness.d.ts.map +1 -0
- package/dist/lib/ai-readiness.js +702 -0
- package/dist/lib/ai-readiness.js.map +1 -0
- package/dist/lib/analyze-state.d.ts +34 -0
- package/dist/lib/analyze-state.d.ts.map +1 -0
- package/dist/lib/analyze-state.js +106 -0
- package/dist/lib/analyze-state.js.map +1 -0
- package/dist/lib/benchmark.d.ts +250 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +778 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/bm25.d.ts +114 -0
- package/dist/lib/bm25.d.ts.map +1 -0
- package/dist/lib/bm25.js +727 -0
- package/dist/lib/bm25.js.map +1 -0
- package/dist/lib/bpe.d.ts +70 -0
- package/dist/lib/bpe.d.ts.map +1 -0
- package/dist/lib/bpe.js +270 -0
- package/dist/lib/bpe.js.map +1 -0
- package/dist/lib/checkpoint.d.ts +124 -0
- package/dist/lib/checkpoint.d.ts.map +1 -0
- package/dist/lib/checkpoint.js +321 -0
- package/dist/lib/checkpoint.js.map +1 -0
- package/dist/lib/chunker.d.ts +47 -0
- package/dist/lib/chunker.d.ts.map +1 -0
- package/dist/lib/chunker.js +358 -0
- package/dist/lib/chunker.js.map +1 -0
- package/dist/lib/claude-ws-transport.d.ts +76 -0
- package/dist/lib/claude-ws-transport.d.ts.map +1 -0
- package/dist/lib/claude-ws-transport.js +487 -0
- package/dist/lib/claude-ws-transport.js.map +1 -0
- package/dist/lib/compact-briefing.d.ts +22 -0
- package/dist/lib/compact-briefing.d.ts.map +1 -0
- package/dist/lib/compact-briefing.js +180 -0
- package/dist/lib/compact-briefing.js.map +1 -0
- package/dist/lib/config-defaults.d.ts +41 -0
- package/dist/lib/config-defaults.d.ts.map +1 -0
- package/dist/lib/config-defaults.js +99 -0
- package/dist/lib/config-defaults.js.map +1 -0
- package/dist/lib/config-display.d.ts +16 -0
- package/dist/lib/config-display.d.ts.map +1 -0
- package/dist/lib/config-display.js +408 -0
- package/dist/lib/config-display.js.map +1 -0
- package/dist/lib/config-types.d.ts +601 -0
- package/dist/lib/config-types.d.ts.map +1 -0
- package/dist/lib/config-types.js +7 -0
- package/dist/lib/config-types.js.map +1 -0
- package/dist/lib/config-validation.d.ts +19 -0
- package/dist/lib/config-validation.d.ts.map +1 -0
- package/dist/lib/config-validation.js +136 -0
- package/dist/lib/config-validation.js.map +1 -0
- package/dist/lib/config.d.ts +143 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +689 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/consolidate.d.ts +115 -0
- package/dist/lib/consolidate.d.ts.map +1 -0
- package/dist/lib/consolidate.js +600 -0
- package/dist/lib/consolidate.js.map +1 -0
- package/dist/lib/db/bm25-indexes.d.ts +58 -0
- package/dist/lib/db/bm25-indexes.d.ts.map +1 -0
- package/dist/lib/db/bm25-indexes.js +333 -0
- package/dist/lib/db/bm25-indexes.js.map +1 -0
- package/dist/lib/db/bpe.d.ts +18 -0
- package/dist/lib/db/bpe.d.ts.map +1 -0
- package/dist/lib/db/bpe.js +44 -0
- package/dist/lib/db/bpe.js.map +1 -0
- package/dist/lib/db/connection.d.ts +47 -0
- package/dist/lib/db/connection.d.ts.map +1 -0
- package/dist/lib/db/connection.js +114 -0
- package/dist/lib/db/connection.js.map +1 -0
- package/dist/lib/db/documents.d.ts +58 -0
- package/dist/lib/db/documents.d.ts.map +1 -0
- package/dist/lib/db/documents.js +374 -0
- package/dist/lib/db/documents.js.map +1 -0
- package/dist/lib/db/file-hash.d.ts +26 -0
- package/dist/lib/db/file-hash.d.ts.map +1 -0
- package/dist/lib/db/file-hash.js +56 -0
- package/dist/lib/db/file-hash.js.map +1 -0
- package/dist/lib/db/global-memories.d.ts +62 -0
- package/dist/lib/db/global-memories.d.ts.map +1 -0
- package/dist/lib/db/global-memories.js +173 -0
- package/dist/lib/db/global-memories.js.map +1 -0
- package/dist/lib/db/graph.d.ts +163 -0
- package/dist/lib/db/graph.d.ts.map +1 -0
- package/dist/lib/db/graph.js +488 -0
- package/dist/lib/db/graph.js.map +1 -0
- package/dist/lib/db/helpers.d.ts +13 -0
- package/dist/lib/db/helpers.d.ts.map +1 -0
- package/dist/lib/db/helpers.js +21 -0
- package/dist/lib/db/helpers.js.map +1 -0
- package/dist/lib/db/hybrid-search.d.ts +64 -0
- package/dist/lib/db/hybrid-search.d.ts.map +1 -0
- package/dist/lib/db/hybrid-search.js +549 -0
- package/dist/lib/db/hybrid-search.js.map +1 -0
- package/dist/lib/db/index.d.ts +23 -0
- package/dist/lib/db/index.d.ts.map +1 -0
- package/dist/lib/db/index.js +23 -0
- package/dist/lib/db/index.js.map +1 -0
- package/dist/lib/db/memories.d.ts +174 -0
- package/dist/lib/db/memories.d.ts.map +1 -0
- package/dist/lib/db/memories.js +866 -0
- package/dist/lib/db/memories.js.map +1 -0
- package/dist/lib/db/retention.d.ts +64 -0
- package/dist/lib/db/retention.d.ts.map +1 -0
- package/dist/lib/db/retention.js +115 -0
- package/dist/lib/db/retention.js.map +1 -0
- package/dist/lib/db/schema.d.ts +29 -0
- package/dist/lib/db/schema.d.ts.map +1 -0
- package/dist/lib/db/schema.js +832 -0
- package/dist/lib/db/schema.js.map +1 -0
- package/dist/lib/db/skills.d.ts +65 -0
- package/dist/lib/db/skills.d.ts.map +1 -0
- package/dist/lib/db/skills.js +119 -0
- package/dist/lib/db/skills.js.map +1 -0
- package/dist/lib/db/token-frequency.d.ts +34 -0
- package/dist/lib/db/token-frequency.d.ts.map +1 -0
- package/dist/lib/db/token-frequency.js +92 -0
- package/dist/lib/db/token-frequency.js.map +1 -0
- package/dist/lib/db/token-stats.d.ts +43 -0
- package/dist/lib/db/token-stats.d.ts.map +1 -0
- package/dist/lib/db/token-stats.js +59 -0
- package/dist/lib/db/token-stats.js.map +1 -0
- package/dist/lib/db/types.d.ts +57 -0
- package/dist/lib/db/types.d.ts.map +1 -0
- package/dist/lib/db/types.js +5 -0
- package/dist/lib/db/types.js.map +1 -0
- package/dist/lib/db/web-search-history.d.ts +22 -0
- package/dist/lib/db/web-search-history.d.ts.map +1 -0
- package/dist/lib/db/web-search-history.js +107 -0
- package/dist/lib/db/web-search-history.js.map +1 -0
- package/dist/lib/debug/state.d.ts +17 -0
- package/dist/lib/debug/state.d.ts.map +1 -0
- package/dist/lib/debug/state.js +131 -0
- package/dist/lib/debug/state.js.map +1 -0
- package/dist/lib/debug/types.d.ts +74 -0
- package/dist/lib/debug/types.d.ts.map +1 -0
- package/dist/lib/debug/types.js +85 -0
- package/dist/lib/debug/types.js.map +1 -0
- package/dist/lib/embedding-pool.d.ts +37 -0
- package/dist/lib/embedding-pool.d.ts.map +1 -0
- package/dist/lib/embedding-pool.js +159 -0
- package/dist/lib/embedding-pool.js.map +1 -0
- package/dist/lib/embedding-worker.d.ts +7 -0
- package/dist/lib/embedding-worker.d.ts.map +1 -0
- package/dist/lib/embedding-worker.js +49 -0
- package/dist/lib/embedding-worker.js.map +1 -0
- package/dist/lib/embeddings.d.ts +54 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/lib/embeddings.js +437 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/errors.d.ts +40 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +66 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/fault-logger.d.ts +35 -0
- package/dist/lib/fault-logger.d.ts.map +1 -0
- package/dist/lib/fault-logger.js +182 -0
- package/dist/lib/fault-logger.js.map +1 -0
- package/dist/lib/graph/centrality.d.ts +34 -0
- package/dist/lib/graph/centrality.d.ts.map +1 -0
- package/dist/lib/graph/centrality.js +76 -0
- package/dist/lib/graph/centrality.js.map +1 -0
- package/dist/lib/graph/cleanup.d.ts +36 -0
- package/dist/lib/graph/cleanup.d.ts.map +1 -0
- package/dist/lib/graph/cleanup.js +96 -0
- package/dist/lib/graph/cleanup.js.map +1 -0
- package/dist/lib/graph/community-detection.d.ts +61 -0
- package/dist/lib/graph/community-detection.d.ts.map +1 -0
- package/dist/lib/graph/community-detection.js +218 -0
- package/dist/lib/graph/community-detection.js.map +1 -0
- package/dist/lib/graph/contextual-proximity.d.ts +41 -0
- package/dist/lib/graph/contextual-proximity.d.ts.map +1 -0
- package/dist/lib/graph/contextual-proximity.js +125 -0
- package/dist/lib/graph/contextual-proximity.js.map +1 -0
- package/dist/lib/graph/llm-relations.d.ts +54 -0
- package/dist/lib/graph/llm-relations.d.ts.map +1 -0
- package/dist/lib/graph/llm-relations.js +265 -0
- package/dist/lib/graph/llm-relations.js.map +1 -0
- package/dist/lib/graph-export.d.ts +13 -0
- package/dist/lib/graph-export.d.ts.map +1 -0
- package/dist/lib/graph-export.js +637 -0
- package/dist/lib/graph-export.js.map +1 -0
- package/dist/lib/graph-scheduler.d.ts +7 -0
- package/dist/lib/graph-scheduler.d.ts.map +1 -0
- package/dist/lib/graph-scheduler.js +21 -0
- package/dist/lib/graph-scheduler.js.map +1 -0
- package/dist/lib/indexer.d.ts +36 -0
- package/dist/lib/indexer.d.ts.map +1 -0
- package/dist/lib/indexer.js +226 -0
- package/dist/lib/indexer.js.map +1 -0
- package/dist/lib/learning-delta.d.ts +35 -0
- package/dist/lib/learning-delta.d.ts.map +1 -0
- package/dist/lib/learning-delta.js +53 -0
- package/dist/lib/learning-delta.js.map +1 -0
- package/dist/lib/llm.d.ts +118 -0
- package/dist/lib/llm.d.ts.map +1 -0
- package/dist/lib/llm.js +439 -0
- package/dist/lib/llm.js.map +1 -0
- package/dist/lib/lock.d.ts +27 -0
- package/dist/lib/lock.d.ts.map +1 -0
- package/dist/lib/lock.js +143 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/md-fetch.d.ts +61 -0
- package/dist/lib/md-fetch.d.ts.map +1 -0
- package/dist/lib/md-fetch.js +141 -0
- package/dist/lib/md-fetch.js.map +1 -0
- package/dist/lib/mmr.d.ts +29 -0
- package/dist/lib/mmr.d.ts.map +1 -0
- package/dist/lib/mmr.js +89 -0
- package/dist/lib/mmr.js.map +1 -0
- package/dist/lib/onboarding/ai-chat.d.ts +11 -0
- package/dist/lib/onboarding/ai-chat.d.ts.map +1 -0
- package/dist/lib/onboarding/ai-chat.js +85 -0
- package/dist/lib/onboarding/ai-chat.js.map +1 -0
- package/dist/lib/onboarding/index.d.ts +7 -0
- package/dist/lib/onboarding/index.d.ts.map +1 -0
- package/dist/lib/onboarding/index.js +7 -0
- package/dist/lib/onboarding/index.js.map +1 -0
- package/dist/lib/onboarding/wizard.d.ts +11 -0
- package/dist/lib/onboarding/wizard.d.ts.map +1 -0
- package/dist/lib/onboarding/wizard.js +104 -0
- package/dist/lib/onboarding/wizard.js.map +1 -0
- package/dist/lib/ort-provider.d.ts +27 -0
- package/dist/lib/ort-provider.d.ts.map +1 -0
- package/dist/lib/ort-provider.js +83 -0
- package/dist/lib/ort-provider.js.map +1 -0
- package/dist/lib/ort-session.d.ts +32 -0
- package/dist/lib/ort-session.d.ts.map +1 -0
- package/dist/lib/ort-session.js +227 -0
- package/dist/lib/ort-session.js.map +1 -0
- package/dist/lib/patterns.d.ts +43 -0
- package/dist/lib/patterns.d.ts.map +1 -0
- package/dist/lib/patterns.js +395 -0
- package/dist/lib/patterns.js.map +1 -0
- package/dist/lib/prd/codebase-context.d.ts +27 -0
- package/dist/lib/prd/codebase-context.d.ts.map +1 -0
- package/dist/lib/prd/codebase-context.js +420 -0
- package/dist/lib/prd/codebase-context.js.map +1 -0
- package/dist/lib/prd/context.d.ts +24 -0
- package/dist/lib/prd/context.d.ts.map +1 -0
- package/dist/lib/prd/context.js +68 -0
- package/dist/lib/prd/context.js.map +1 -0
- package/dist/lib/prd/executor.d.ts +52 -0
- package/dist/lib/prd/executor.d.ts.map +1 -0
- package/dist/lib/prd/executor.js +154 -0
- package/dist/lib/prd/executor.js.map +1 -0
- package/dist/lib/prd/export.d.ts +40 -0
- package/dist/lib/prd/export.d.ts.map +1 -0
- package/dist/lib/prd/export.js +511 -0
- package/dist/lib/prd/export.js.map +1 -0
- package/dist/lib/prd/gates.d.ts +30 -0
- package/dist/lib/prd/gates.d.ts.map +1 -0
- package/dist/lib/prd/gates.js +100 -0
- package/dist/lib/prd/gates.js.map +1 -0
- package/dist/lib/prd/generate.d.ts +56 -0
- package/dist/lib/prd/generate.d.ts.map +1 -0
- package/dist/lib/prd/generate.js +357 -0
- package/dist/lib/prd/generate.js.map +1 -0
- package/dist/lib/prd/parse.d.ts +21 -0
- package/dist/lib/prd/parse.d.ts.map +1 -0
- package/dist/lib/prd/parse.js +270 -0
- package/dist/lib/prd/parse.js.map +1 -0
- package/dist/lib/prd/prompt-builder.d.ts +18 -0
- package/dist/lib/prd/prompt-builder.d.ts.map +1 -0
- package/dist/lib/prd/prompt-builder.js +61 -0
- package/dist/lib/prd/prompt-builder.js.map +1 -0
- package/dist/lib/prd/runner.d.ts +54 -0
- package/dist/lib/prd/runner.d.ts.map +1 -0
- package/dist/lib/prd/runner.js +563 -0
- package/dist/lib/prd/runner.js.map +1 -0
- package/dist/lib/prd/scheduler.d.ts +27 -0
- package/dist/lib/prd/scheduler.d.ts.map +1 -0
- package/dist/lib/prd/scheduler.js +113 -0
- package/dist/lib/prd/scheduler.js.map +1 -0
- package/dist/lib/prd/state.d.ts +77 -0
- package/dist/lib/prd/state.d.ts.map +1 -0
- package/dist/lib/prd/state.js +253 -0
- package/dist/lib/prd/state.js.map +1 -0
- package/dist/lib/prd/team-runner.d.ts +48 -0
- package/dist/lib/prd/team-runner.d.ts.map +1 -0
- package/dist/lib/prd/team-runner.js +261 -0
- package/dist/lib/prd/team-runner.js.map +1 -0
- package/dist/lib/prd/types.d.ts +169 -0
- package/dist/lib/prd/types.d.ts.map +1 -0
- package/dist/lib/prd/types.js +143 -0
- package/dist/lib/prd/types.js.map +1 -0
- package/dist/lib/prd/worktree.d.ts +54 -0
- package/dist/lib/prd/worktree.d.ts.map +1 -0
- package/dist/lib/prd/worktree.js +255 -0
- package/dist/lib/prd/worktree.js.map +1 -0
- package/dist/lib/precompute-context.d.ts +48 -0
- package/dist/lib/precompute-context.d.ts.map +1 -0
- package/dist/lib/precompute-context.js +265 -0
- package/dist/lib/precompute-context.js.map +1 -0
- package/dist/lib/pricing.d.ts +60 -0
- package/dist/lib/pricing.d.ts.map +1 -0
- package/dist/lib/pricing.js +258 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/process-registry.d.ts +30 -0
- package/dist/lib/process-registry.d.ts.map +1 -0
- package/dist/lib/process-registry.js +92 -0
- package/dist/lib/process-registry.js.map +1 -0
- package/dist/lib/progress-log.d.ts +44 -0
- package/dist/lib/progress-log.d.ts.map +1 -0
- package/dist/lib/progress-log.js +58 -0
- package/dist/lib/progress-log.js.map +1 -0
- package/dist/lib/public-api.d.ts +56 -0
- package/dist/lib/public-api.d.ts.map +1 -0
- package/dist/lib/public-api.js +63 -0
- package/dist/lib/public-api.js.map +1 -0
- package/dist/lib/quality.d.ts +66 -0
- package/dist/lib/quality.d.ts.map +1 -0
- package/dist/lib/quality.js +486 -0
- package/dist/lib/quality.js.map +1 -0
- package/dist/lib/query-expansion.d.ts +16 -0
- package/dist/lib/query-expansion.d.ts.map +1 -0
- package/dist/lib/query-expansion.js +53 -0
- package/dist/lib/query-expansion.js.map +1 -0
- package/dist/lib/readiness.d.ts +35 -0
- package/dist/lib/readiness.d.ts.map +1 -0
- package/dist/lib/readiness.js +142 -0
- package/dist/lib/readiness.js.map +1 -0
- package/dist/lib/reference-embeddings.d.ts +39 -0
- package/dist/lib/reference-embeddings.d.ts.map +1 -0
- package/dist/lib/reference-embeddings.js +95 -0
- package/dist/lib/reference-embeddings.js.map +1 -0
- package/dist/lib/reflection-synthesizer.d.ts +27 -0
- package/dist/lib/reflection-synthesizer.d.ts.map +1 -0
- package/dist/lib/reflection-synthesizer.js +149 -0
- package/dist/lib/reflection-synthesizer.js.map +1 -0
- package/dist/lib/retention.d.ts +105 -0
- package/dist/lib/retention.d.ts.map +1 -0
- package/dist/lib/retention.js +246 -0
- package/dist/lib/retention.js.map +1 -0
- package/dist/lib/sensitive-filter.d.ts +34 -0
- package/dist/lib/sensitive-filter.d.ts.map +1 -0
- package/dist/lib/sensitive-filter.js +344 -0
- package/dist/lib/sensitive-filter.js.map +1 -0
- package/dist/lib/session-observations.d.ts +59 -0
- package/dist/lib/session-observations.d.ts.map +1 -0
- package/dist/lib/session-observations.js +128 -0
- package/dist/lib/session-observations.js.map +1 -0
- package/dist/lib/session-summary.d.ts +65 -0
- package/dist/lib/session-summary.d.ts.map +1 -0
- package/dist/lib/session-summary.js +344 -0
- package/dist/lib/session-summary.js.map +1 -0
- package/dist/lib/similarity-worker.d.ts +7 -0
- package/dist/lib/similarity-worker.d.ts.map +1 -0
- package/dist/lib/similarity-worker.js +36 -0
- package/dist/lib/similarity-worker.js.map +1 -0
- package/dist/lib/skills.d.ts +78 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +439 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/skyll-client.d.ts +59 -0
- package/dist/lib/skyll-client.d.ts.map +1 -0
- package/dist/lib/skyll-client.js +257 -0
- package/dist/lib/skyll-client.js.map +1 -0
- package/dist/lib/storage/backends/interface.d.ts +383 -0
- package/dist/lib/storage/backends/interface.d.ts.map +1 -0
- package/dist/lib/storage/backends/interface.js +12 -0
- package/dist/lib/storage/backends/interface.js.map +1 -0
- package/dist/lib/storage/backends/postgresql.d.ts +454 -0
- package/dist/lib/storage/backends/postgresql.d.ts.map +1 -0
- package/dist/lib/storage/backends/postgresql.js +2528 -0
- package/dist/lib/storage/backends/postgresql.js.map +1 -0
- package/dist/lib/storage/benchmark.d.ts +16 -0
- package/dist/lib/storage/benchmark.d.ts.map +1 -0
- package/dist/lib/storage/benchmark.js +219 -0
- package/dist/lib/storage/benchmark.js.map +1 -0
- package/dist/lib/storage/dispatcher-export.d.ts +108 -0
- package/dist/lib/storage/dispatcher-export.d.ts.map +1 -0
- package/dist/lib/storage/dispatcher-export.js +593 -0
- package/dist/lib/storage/dispatcher-export.js.map +1 -0
- package/dist/lib/storage/dispatcher.d.ts +468 -0
- package/dist/lib/storage/dispatcher.d.ts.map +1 -0
- package/dist/lib/storage/dispatcher.js +1926 -0
- package/dist/lib/storage/dispatcher.js.map +1 -0
- package/dist/lib/storage/index.d.ts +481 -0
- package/dist/lib/storage/index.d.ts.map +1 -0
- package/dist/lib/storage/index.js +727 -0
- package/dist/lib/storage/index.js.map +1 -0
- package/dist/lib/storage/migration/export-import.d.ts +133 -0
- package/dist/lib/storage/migration/export-import.d.ts.map +1 -0
- package/dist/lib/storage/migration/export-import.js +264 -0
- package/dist/lib/storage/migration/export-import.js.map +1 -0
- package/dist/lib/storage/types.d.ts +313 -0
- package/dist/lib/storage/types.d.ts.map +1 -0
- package/dist/lib/storage/types.js +30 -0
- package/dist/lib/storage/types.js.map +1 -0
- package/dist/lib/storage/vector/interface.d.ts +89 -0
- package/dist/lib/storage/vector/interface.d.ts.map +1 -0
- package/dist/lib/storage/vector/interface.js +10 -0
- package/dist/lib/storage/vector/interface.js.map +1 -0
- package/dist/lib/storage/vector/qdrant.d.ts +221 -0
- package/dist/lib/storage/vector/qdrant.d.ts.map +1 -0
- package/dist/lib/storage/vector/qdrant.js +880 -0
- package/dist/lib/storage/vector/qdrant.js.map +1 -0
- package/dist/lib/supersession.d.ts +26 -0
- package/dist/lib/supersession.d.ts.map +1 -0
- package/dist/lib/supersession.js +97 -0
- package/dist/lib/supersession.js.map +1 -0
- package/dist/lib/temporal.d.ts +140 -0
- package/dist/lib/temporal.d.ts.map +1 -0
- package/dist/lib/temporal.js +303 -0
- package/dist/lib/temporal.js.map +1 -0
- package/dist/lib/token-budget.d.ts +79 -0
- package/dist/lib/token-budget.d.ts.map +1 -0
- package/dist/lib/token-budget.js +146 -0
- package/dist/lib/token-budget.js.map +1 -0
- package/dist/lib/token-counter.d.ts +27 -0
- package/dist/lib/token-counter.d.ts.map +1 -0
- package/dist/lib/token-counter.js +52 -0
- package/dist/lib/token-counter.js.map +1 -0
- package/dist/lib/tree-sitter/chunker-ts.d.ts +24 -0
- package/dist/lib/tree-sitter/chunker-ts.d.ts.map +1 -0
- package/dist/lib/tree-sitter/chunker-ts.js +206 -0
- package/dist/lib/tree-sitter/chunker-ts.js.map +1 -0
- package/dist/lib/tree-sitter/extractor.d.ts +43 -0
- package/dist/lib/tree-sitter/extractor.d.ts.map +1 -0
- package/dist/lib/tree-sitter/extractor.js +297 -0
- package/dist/lib/tree-sitter/extractor.js.map +1 -0
- package/dist/lib/tree-sitter/index.d.ts +13 -0
- package/dist/lib/tree-sitter/index.d.ts.map +1 -0
- package/dist/lib/tree-sitter/index.js +14 -0
- package/dist/lib/tree-sitter/index.js.map +1 -0
- package/dist/lib/tree-sitter/parser.d.ts +70 -0
- package/dist/lib/tree-sitter/parser.d.ts.map +1 -0
- package/dist/lib/tree-sitter/parser.js +354 -0
- package/dist/lib/tree-sitter/parser.js.map +1 -0
- package/dist/lib/tree-sitter/public.d.ts +28 -0
- package/dist/lib/tree-sitter/public.d.ts.map +1 -0
- package/dist/lib/tree-sitter/public.js +50 -0
- package/dist/lib/tree-sitter/public.js.map +1 -0
- package/dist/lib/tree-sitter/queries.d.ts +28 -0
- package/dist/lib/tree-sitter/queries.d.ts.map +1 -0
- package/dist/lib/tree-sitter/queries.js +383 -0
- package/dist/lib/tree-sitter/queries.js.map +1 -0
- package/dist/lib/tree-sitter/types.d.ts +69 -0
- package/dist/lib/tree-sitter/types.d.ts.map +1 -0
- package/dist/lib/tree-sitter/types.js +131 -0
- package/dist/lib/tree-sitter/types.js.map +1 -0
- package/dist/lib/working-memory-pipeline.d.ts +118 -0
- package/dist/lib/working-memory-pipeline.d.ts.map +1 -0
- package/dist/lib/working-memory-pipeline.js +344 -0
- package/dist/lib/working-memory-pipeline.js.map +1 -0
- package/dist/mcp/helpers.d.ts +44 -0
- package/dist/mcp/helpers.d.ts.map +1 -0
- package/dist/mcp/helpers.js +244 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/index.d.ts +10 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/resources.d.ts +12 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +136 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +131 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/config.d.ts +10 -0
- package/dist/mcp/tools/config.d.ts.map +1 -0
- package/dist/mcp/tools/config.js +236 -0
- package/dist/mcp/tools/config.js.map +1 -0
- package/dist/mcp/tools/dead-end.d.ts +9 -0
- package/dist/mcp/tools/dead-end.d.ts.map +1 -0
- package/dist/mcp/tools/dead-end.js +137 -0
- package/dist/mcp/tools/dead-end.js.map +1 -0
- package/dist/mcp/tools/debug.d.ts +9 -0
- package/dist/mcp/tools/debug.d.ts.map +1 -0
- package/dist/mcp/tools/debug.js +387 -0
- package/dist/mcp/tools/debug.js.map +1 -0
- package/dist/mcp/tools/graph.d.ts +9 -0
- package/dist/mcp/tools/graph.d.ts.map +1 -0
- package/dist/mcp/tools/graph.js +337 -0
- package/dist/mcp/tools/graph.js.map +1 -0
- package/dist/mcp/tools/indexing.d.ts +12 -0
- package/dist/mcp/tools/indexing.d.ts.map +1 -0
- package/dist/mcp/tools/indexing.js +289 -0
- package/dist/mcp/tools/indexing.js.map +1 -0
- package/dist/mcp/tools/memory.d.ts +14 -0
- package/dist/mcp/tools/memory.d.ts.map +1 -0
- package/dist/mcp/tools/memory.js +1170 -0
- package/dist/mcp/tools/memory.js.map +1 -0
- package/dist/mcp/tools/prd.d.ts +12 -0
- package/dist/mcp/tools/prd.d.ts.map +1 -0
- package/dist/mcp/tools/prd.js +276 -0
- package/dist/mcp/tools/prd.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +9 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +279 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/status.d.ts +10 -0
- package/dist/mcp/tools/status.d.ts.map +1 -0
- package/dist/mcp/tools/status.js +296 -0
- package/dist/mcp/tools/status.js.map +1 -0
- package/dist/mcp/tools/web-fetch.d.ts +10 -0
- package/dist/mcp/tools/web-fetch.d.ts.map +1 -0
- package/dist/mcp/tools/web-fetch.js +107 -0
- package/dist/mcp/tools/web-fetch.js.map +1 -0
- package/dist/mcp/tools/web-search.d.ts +14 -0
- package/dist/mcp/tools/web-search.d.ts.map +1 -0
- package/dist/mcp/tools/web-search.js +427 -0
- package/dist/mcp/tools/web-search.js.map +1 -0
- package/dist/mcp/types.d.ts +16 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +5 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +9 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/prompts/analyze.d.ts +21 -0
- package/dist/prompts/analyze.d.ts.map +1 -0
- package/dist/prompts/analyze.js +27 -0
- package/dist/prompts/analyze.js.map +1 -0
- package/dist/prompts/briefing.d.ts +27 -0
- package/dist/prompts/briefing.d.ts.map +1 -0
- package/dist/prompts/briefing.js +123 -0
- package/dist/prompts/briefing.js.map +1 -0
- package/dist/prompts/chat.d.ts +8 -0
- package/dist/prompts/chat.d.ts.map +1 -0
- package/dist/prompts/chat.js +39 -0
- package/dist/prompts/chat.js.map +1 -0
- package/dist/prompts/daemon.d.ts +16 -0
- package/dist/prompts/daemon.d.ts.map +1 -0
- package/dist/prompts/daemon.js +46 -0
- package/dist/prompts/daemon.js.map +1 -0
- package/dist/prompts/extraction.d.ts +13 -0
- package/dist/prompts/extraction.d.ts.map +1 -0
- package/dist/prompts/extraction.js +93 -0
- package/dist/prompts/extraction.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +27 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/memory.d.ts +11 -0
- package/dist/prompts/memory.d.ts.map +1 -0
- package/dist/prompts/memory.js +23 -0
- package/dist/prompts/memory.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +16 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +183 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/prd.d.ts +12 -0
- package/dist/prompts/prd.d.ts.map +1 -0
- package/dist/prompts/prd.js +211 -0
- package/dist/prompts/prd.js.map +1 -0
- package/dist/prompts/quality.d.ts +11 -0
- package/dist/prompts/quality.d.ts.map +1 -0
- package/dist/prompts/quality.js +11 -0
- package/dist/prompts/quality.js.map +1 -0
- package/dist/prompts/skills.d.ts +16 -0
- package/dist/prompts/skills.d.ts.map +1 -0
- package/dist/prompts/skills.js +30 -0
- package/dist/prompts/skills.js.map +1 -0
- package/hooks/succ-post-tool.cjs +227 -0
- package/hooks/succ-pre-tool.cjs +312 -0
- package/hooks/succ-session-end.cjs +85 -0
- package/hooks/succ-session-start.cjs +618 -0
- package/hooks/succ-stop-reflection.cjs +87 -0
- package/hooks/succ-user-prompt.cjs +220 -0
- package/package.json +128 -0
|
@@ -0,0 +1,1203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Daemon Service for succ
|
|
3
|
+
*
|
|
4
|
+
* Single HTTP server per project that handles:
|
|
5
|
+
* - Multiple Claude Code sessions (idle tracking, reflection)
|
|
6
|
+
* - Watch service (file monitoring)
|
|
7
|
+
* - Analyze queue (code analysis)
|
|
8
|
+
* - Data operations (search, recall, remember)
|
|
9
|
+
*
|
|
10
|
+
* Benefits:
|
|
11
|
+
* - No CMD windows on Windows (starts once, stays running)
|
|
12
|
+
* - Fast operations via HTTP (~5ms vs ~500ms spawn)
|
|
13
|
+
* - Shared DB connections, embeddings cache
|
|
14
|
+
* - Per-session idle tracking with multi-session support
|
|
15
|
+
*/
|
|
16
|
+
import http from 'http';
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { createSessionManager, createIdleWatcher } from './sessions.js';
|
|
20
|
+
import { logError, logWarn } from '../lib/fault-logger.js';
|
|
21
|
+
import { processRegistry } from '../lib/process-registry.js';
|
|
22
|
+
import { processSessionEnd } from './session-processor.js';
|
|
23
|
+
import { ValidationError, NotFoundError, NetworkError } from '../lib/errors.js';
|
|
24
|
+
import { startWatcher, stopWatcher, getWatcherStatus, indexFileOnDemand } from './watcher.js';
|
|
25
|
+
import { startAnalyzer, stopAnalyzer, getAnalyzerStatus, triggerAnalysis } from './analyzer.js';
|
|
26
|
+
import { getProjectRoot, getSuccDir, getIdleReflectionConfig, getIdleWatcherConfig, getConfig, getObserverConfig, } from '../lib/config.js';
|
|
27
|
+
import { hybridSearchDocs, hybridSearchCode, hybridSearchMemories, saveMemory, closeDb, getStats, getMemoryStats, incrementMemoryAccessBatch, getRecentMemories, getPinnedMemories, setMemoryInvariant,
|
|
28
|
+
// Global memory
|
|
29
|
+
saveGlobalMemory, closeGlobalDb,
|
|
30
|
+
// Dispatcher lifecycle
|
|
31
|
+
initStorageDispatcher, closeStorageDispatcher, getStorageDispatcher, } from '../lib/storage/index.js';
|
|
32
|
+
import { getEmbedding, cleanupEmbeddings } from '../lib/embeddings.js';
|
|
33
|
+
import { scoreMemory, passesQualityThreshold, cleanupQualityScoring } from '../lib/quality.js';
|
|
34
|
+
import { scanSensitive } from '../lib/sensitive-filter.js';
|
|
35
|
+
import { generateCompactBriefing } from '../lib/compact-briefing.js';
|
|
36
|
+
import { callLLM } from '../lib/llm.js';
|
|
37
|
+
import { extractSessionSummary } from '../lib/session-summary.js';
|
|
38
|
+
import { recordTranscriptTokens, recordExtraction, resetTranscriptCounter, loadBudgets, flushBudgets, removeBudget, } from '../lib/token-budget.js';
|
|
39
|
+
import { appendObservations, removeObservations, cleanupStaleObservations, } from '../lib/session-observations.js';
|
|
40
|
+
import { REFLECTION_PROMPT } from '../prompts/index.js';
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Constants
|
|
43
|
+
// ============================================================================
|
|
44
|
+
const DEFAULT_PORT_RANGE_START = 37842;
|
|
45
|
+
const MAX_PORT_ATTEMPTS = 100;
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Daemon State
|
|
48
|
+
// ============================================================================
|
|
49
|
+
let state = null;
|
|
50
|
+
let sessionManager = null;
|
|
51
|
+
let idleWatcher = null;
|
|
52
|
+
const briefingCache = new Map();
|
|
53
|
+
const briefingGenerationInProgress = new Set();
|
|
54
|
+
// In-flight dedup: prevents race condition when identical /api/remember requests
|
|
55
|
+
// arrive within a short window (e.g. hook fires twice for same tool_use)
|
|
56
|
+
const rememberInFlight = new Map();
|
|
57
|
+
const REMEMBER_DEDUP_TTL_MS = 5000;
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// File Paths
|
|
60
|
+
// ============================================================================
|
|
61
|
+
function getDaemonPidFile() {
|
|
62
|
+
const succDir = getSuccDir();
|
|
63
|
+
const tmpDir = path.join(succDir, '.tmp');
|
|
64
|
+
if (!fs.existsSync(tmpDir)) {
|
|
65
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
return path.join(tmpDir, 'daemon.pid');
|
|
68
|
+
}
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Progress File Management
|
|
71
|
+
// ============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Get path to session progress file
|
|
74
|
+
* Progress files accumulate idle reflection briefings for session-end processing
|
|
75
|
+
*/
|
|
76
|
+
function getProgressFilePath(sessionId) {
|
|
77
|
+
const succDir = getSuccDir();
|
|
78
|
+
const tmpDir = path.join(succDir, '.tmp');
|
|
79
|
+
if (!fs.existsSync(tmpDir)) {
|
|
80
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
return path.join(tmpDir, `session-${sessionId}-progress.md`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Append a briefing to the session progress file
|
|
86
|
+
* Creates file with header if it doesn't exist
|
|
87
|
+
*/
|
|
88
|
+
export function appendToProgressFile(sessionId, briefing) {
|
|
89
|
+
const progressPath = getProgressFilePath(sessionId);
|
|
90
|
+
const timestamp = new Date().toISOString();
|
|
91
|
+
const timeStr = new Date().toLocaleTimeString('en-US', {
|
|
92
|
+
hour12: false,
|
|
93
|
+
hour: '2-digit',
|
|
94
|
+
minute: '2-digit',
|
|
95
|
+
});
|
|
96
|
+
let content = '';
|
|
97
|
+
if (!fs.existsSync(progressPath)) {
|
|
98
|
+
content = `---\nsession_id: ${sessionId}\ncreated: ${timestamp}\n---\n\n`;
|
|
99
|
+
}
|
|
100
|
+
content += `## ${timeStr} - Idle Reflection\n\n`;
|
|
101
|
+
content += briefing;
|
|
102
|
+
content += '\n\n---\n\n';
|
|
103
|
+
fs.appendFileSync(progressPath, content);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Read tail of transcript file (for fallback when no progress file)
|
|
107
|
+
* Returns the last maxBytes of the file, starting from a complete line
|
|
108
|
+
*/
|
|
109
|
+
export function readTailTranscript(transcriptPath, maxBytes = 2 * 1024 * 1024) {
|
|
110
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
const stats = fs.statSync(transcriptPath);
|
|
114
|
+
if (stats.size <= maxBytes) {
|
|
115
|
+
return fs.readFileSync(transcriptPath, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
// Read only tail
|
|
118
|
+
const fd = fs.openSync(transcriptPath, 'r');
|
|
119
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
120
|
+
fs.readSync(fd, buffer, 0, maxBytes, stats.size - maxBytes);
|
|
121
|
+
fs.closeSync(fd);
|
|
122
|
+
// Find first complete line (skip partial line at start)
|
|
123
|
+
const content = buffer.toString('utf8');
|
|
124
|
+
const firstNewline = content.indexOf('\n');
|
|
125
|
+
return firstNewline > 0 ? content.slice(firstNewline + 1) : content;
|
|
126
|
+
}
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Briefing Pre-Generation
|
|
129
|
+
// ============================================================================
|
|
130
|
+
const BRIEFING_CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
131
|
+
const BRIEFING_MIN_TRANSCRIPT_GROWTH = 5000; // Re-generate after 5KB growth
|
|
132
|
+
// const BRIEFING_PREGENERATE_IDLE_MS = 120 * 1000; // Pre-generate after 2 min idle
|
|
133
|
+
/**
|
|
134
|
+
* Pre-generate briefing for a session in background
|
|
135
|
+
* Called when session is idle or transcript grows significantly
|
|
136
|
+
*/
|
|
137
|
+
async function preGenerateBriefing(sessionId, transcriptPath) {
|
|
138
|
+
// Skip if already generating
|
|
139
|
+
if (briefingGenerationInProgress.has(sessionId)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const stats = fs.statSync(transcriptPath);
|
|
146
|
+
const currentSize = stats.size;
|
|
147
|
+
// Check if we need to regenerate
|
|
148
|
+
const cached = briefingCache.get(sessionId);
|
|
149
|
+
if (cached) {
|
|
150
|
+
const age = Date.now() - cached.generatedAt;
|
|
151
|
+
const growth = currentSize - cached.transcriptSize;
|
|
152
|
+
// Skip if cache is fresh and transcript hasn't grown much
|
|
153
|
+
if (age < BRIEFING_CACHE_MAX_AGE_MS && growth < BRIEFING_MIN_TRANSCRIPT_GROWTH) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
briefingGenerationInProgress.add(sessionId);
|
|
158
|
+
log(`[briefing] Pre-generating for session ${sessionId.slice(0, 8)}...`);
|
|
159
|
+
try {
|
|
160
|
+
const transcriptContent = fs.readFileSync(transcriptPath, 'utf-8');
|
|
161
|
+
const result = await generateCompactBriefing(transcriptContent);
|
|
162
|
+
if (result.success && result.briefing) {
|
|
163
|
+
briefingCache.set(sessionId, {
|
|
164
|
+
briefing: result.briefing,
|
|
165
|
+
generatedAt: Date.now(),
|
|
166
|
+
transcriptSize: currentSize,
|
|
167
|
+
});
|
|
168
|
+
log(`[briefing] Pre-generated for session ${sessionId.slice(0, 8)} (${result.briefing.length} chars)`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
log(`[briefing] Pre-generation failed: ${result.error}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
log(`[briefing] Pre-generation error: ${error}`);
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
briefingGenerationInProgress.delete(sessionId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get cached briefing or generate on-demand
|
|
183
|
+
*/
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
185
|
+
async function getCachedBriefing(sessionId, transcriptPath) {
|
|
186
|
+
const cached = briefingCache.get(sessionId);
|
|
187
|
+
if (cached) {
|
|
188
|
+
// Check if cache is still valid
|
|
189
|
+
const age = Date.now() - cached.generatedAt;
|
|
190
|
+
if (age < BRIEFING_CACHE_MAX_AGE_MS) {
|
|
191
|
+
return { briefing: cached.briefing, cached: true };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Cache miss or stale - generate fresh
|
|
195
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
196
|
+
return { cached: false };
|
|
197
|
+
}
|
|
198
|
+
const transcriptContent = fs.readFileSync(transcriptPath, 'utf-8');
|
|
199
|
+
const result = await generateCompactBriefing(transcriptContent);
|
|
200
|
+
if (result.success && result.briefing) {
|
|
201
|
+
const stats = fs.statSync(transcriptPath);
|
|
202
|
+
briefingCache.set(sessionId, {
|
|
203
|
+
briefing: result.briefing,
|
|
204
|
+
generatedAt: Date.now(),
|
|
205
|
+
transcriptSize: stats.size,
|
|
206
|
+
});
|
|
207
|
+
return { briefing: result.briefing, cached: false };
|
|
208
|
+
}
|
|
209
|
+
return { cached: false };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Clear briefing cache for a session (called when session ends)
|
|
213
|
+
*/
|
|
214
|
+
function clearBriefingCache(sessionId) {
|
|
215
|
+
briefingCache.delete(sessionId);
|
|
216
|
+
briefingGenerationInProgress.delete(sessionId);
|
|
217
|
+
}
|
|
218
|
+
function getDaemonPortFile() {
|
|
219
|
+
const succDir = getSuccDir();
|
|
220
|
+
const tmpDir = path.join(succDir, '.tmp');
|
|
221
|
+
if (!fs.existsSync(tmpDir)) {
|
|
222
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
223
|
+
}
|
|
224
|
+
return path.join(tmpDir, 'daemon.port');
|
|
225
|
+
}
|
|
226
|
+
function getDaemonLogFile() {
|
|
227
|
+
const succDir = getSuccDir();
|
|
228
|
+
return path.join(succDir, 'daemon.log');
|
|
229
|
+
}
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Logging
|
|
232
|
+
// ============================================================================
|
|
233
|
+
function log(message) {
|
|
234
|
+
const timestamp = new Date().toISOString();
|
|
235
|
+
const line = `[${timestamp}] ${message}\n`;
|
|
236
|
+
// Write to daemon.log
|
|
237
|
+
try {
|
|
238
|
+
fs.appendFileSync(getDaemonLogFile(), line);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
logWarn('daemon', 'Failed to write daemon log', {
|
|
242
|
+
error: err instanceof Error ? err.message : String(err),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Also write to stderr for debugging
|
|
246
|
+
process.stderr.write(line);
|
|
247
|
+
}
|
|
248
|
+
// ============================================================================
|
|
249
|
+
// Write Reflection
|
|
250
|
+
// ============================================================================
|
|
251
|
+
/**
|
|
252
|
+
* Write a human-like reflection to the brain vault
|
|
253
|
+
* Uses Claude CLI or local LLM to generate introspective text
|
|
254
|
+
*/
|
|
255
|
+
async function writeReflection(transcript, _idleConfig) {
|
|
256
|
+
const projectRoot = getProjectRoot();
|
|
257
|
+
const reflectionsDir = path.join(projectRoot, '.succ', 'brain', 'reflections');
|
|
258
|
+
// Create reflections directory if needed
|
|
259
|
+
if (!fs.existsSync(reflectionsDir)) {
|
|
260
|
+
fs.mkdirSync(reflectionsDir, { recursive: true });
|
|
261
|
+
}
|
|
262
|
+
const now = new Date();
|
|
263
|
+
const dateStr = now.toISOString().split('T')[0];
|
|
264
|
+
const timeStr = now.toTimeString().split(' ')[0].substring(0, 5);
|
|
265
|
+
const timestamp = `${dateStr} ${timeStr}`;
|
|
266
|
+
const prompt = REFLECTION_PROMPT.replace('{transcript}', transcript.substring(0, 3000));
|
|
267
|
+
let reflectionText = null;
|
|
268
|
+
// Use sleep agent for background reflection if enabled
|
|
269
|
+
try {
|
|
270
|
+
reflectionText = await callLLM(prompt, {
|
|
271
|
+
timeout: 60000,
|
|
272
|
+
useSleepAgent: true, // Use sleep_agent config if available
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
log(`[reflection] LLM call failed: ${err}`);
|
|
277
|
+
reflectionText = null;
|
|
278
|
+
}
|
|
279
|
+
if (!reflectionText || reflectionText.trim().length < 50) {
|
|
280
|
+
log(`[reflection] Reflection text too short or empty, skipping`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// Write reflection file with YAML frontmatter
|
|
284
|
+
const reflectionFile = path.join(reflectionsDir, `${timestamp}.md`);
|
|
285
|
+
const content = `---
|
|
286
|
+
date: ${dateStr}
|
|
287
|
+
time: ${timeStr}
|
|
288
|
+
trigger: idle
|
|
289
|
+
tags:
|
|
290
|
+
- reflection
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
# Reflection ${dateStr} ${timeStr}
|
|
294
|
+
|
|
295
|
+
${reflectionText.trim()}
|
|
296
|
+
`;
|
|
297
|
+
fs.writeFileSync(reflectionFile, content);
|
|
298
|
+
// Also save to memory (with dedup to prevent duplicate reflections)
|
|
299
|
+
const embedding = await getEmbedding(reflectionText.trim());
|
|
300
|
+
await saveMemory(reflectionText.trim(), embedding, ['reflection'], 'observation', {
|
|
301
|
+
qualityScore: { score: 0.6, factors: { hasContext: 1 } },
|
|
302
|
+
deduplicate: true,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// LLM functions moved to shared module: src/lib/llm.ts
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// Reflection Handler
|
|
308
|
+
// ============================================================================
|
|
309
|
+
async function handleReflection(sessionId, session) {
|
|
310
|
+
// Skip reflection for service sessions (reflection subagents, analyzers, etc.)
|
|
311
|
+
if (session.isService) {
|
|
312
|
+
log(`[reflection] Skipping service session ${sessionId}`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
log(`[reflection] Starting reflection for session ${sessionId}`);
|
|
316
|
+
const idleConfig = getIdleReflectionConfig();
|
|
317
|
+
// Only run if we have a transcript
|
|
318
|
+
if (!session.transcriptPath || !fs.existsSync(session.transcriptPath)) {
|
|
319
|
+
log(`[reflection] No transcript found for session ${sessionId}`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
// ── Change detection: skip redundant work during long AFK ──
|
|
324
|
+
let transcriptChanged = true;
|
|
325
|
+
let memoriesChanged = true;
|
|
326
|
+
try {
|
|
327
|
+
const currentSize = fs.statSync(session.transcriptPath).size;
|
|
328
|
+
if (session.lastTranscriptSize !== undefined && currentSize === session.lastTranscriptSize) {
|
|
329
|
+
transcriptChanged = false;
|
|
330
|
+
log(`[reflection] Transcript unchanged (${currentSize}b), skipping briefing`);
|
|
331
|
+
}
|
|
332
|
+
session.lastTranscriptSize = currentSize;
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
/* transcript file gone — skip size check */
|
|
336
|
+
}
|
|
337
|
+
// Check memory count for consolidation skip
|
|
338
|
+
const memStats = await getMemoryStats();
|
|
339
|
+
const currentMemCount = memStats.total;
|
|
340
|
+
if (session.lastMemoryCount !== undefined && currentMemCount === session.lastMemoryCount) {
|
|
341
|
+
memoriesChanged = false;
|
|
342
|
+
}
|
|
343
|
+
session.lastMemoryCount = currentMemCount;
|
|
344
|
+
// ── Mid-conversation observer: extract facts when enough new content ──
|
|
345
|
+
const observerConfig = getObserverConfig();
|
|
346
|
+
if (observerConfig.enabled && transcriptChanged) {
|
|
347
|
+
try {
|
|
348
|
+
const currentSize = session.lastTranscriptSize ?? 0;
|
|
349
|
+
const lastObsSize = session.lastObservationSize ?? 0;
|
|
350
|
+
const lastObsTime = session.lastObservation ?? session.registeredAt;
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
// Read new content and track real token count via budget
|
|
353
|
+
const newBytes = currentSize - lastObsSize;
|
|
354
|
+
const timeThresholdMs = observerConfig.max_minutes * 60 * 1000;
|
|
355
|
+
const enoughTime = now - lastObsTime >= timeThresholdMs;
|
|
356
|
+
// Use byte-estimated token check first (cheap), then verify with real count
|
|
357
|
+
const estimatedTokens = Math.ceil(newBytes / 3.5);
|
|
358
|
+
const enoughNewContent = estimatedTokens >= observerConfig.min_tokens;
|
|
359
|
+
if (enoughNewContent || enoughTime) {
|
|
360
|
+
const newContent = readTailTranscript(session.transcriptPath, newBytes);
|
|
361
|
+
// Track real tokens in budget
|
|
362
|
+
const realTokens = recordTranscriptTokens(sessionId, newContent);
|
|
363
|
+
log(`[observer] Triggering extraction (tokens: ~${realTokens}, time: ${Math.round((now - lastObsTime) / 60000)}min)`);
|
|
364
|
+
if (newContent.length > 200) {
|
|
365
|
+
const result = await extractSessionSummary(newContent, { verbose: false });
|
|
366
|
+
recordExtraction(sessionId, result.transcriptTokens ?? 0, result.summaryTokens ?? 0, result.factsExtracted, result.factsSaved);
|
|
367
|
+
resetTranscriptCounter(sessionId);
|
|
368
|
+
// Persist extraction metadata to session observations (append-only)
|
|
369
|
+
if (result.factsSaved > 0) {
|
|
370
|
+
appendObservations(sessionId, [
|
|
371
|
+
{
|
|
372
|
+
content: `Extracted ${result.factsExtracted} facts, saved ${result.factsSaved}`,
|
|
373
|
+
type: 'observation',
|
|
374
|
+
tags: ['mid-session'],
|
|
375
|
+
extractedAt: new Date().toISOString(),
|
|
376
|
+
source: 'mid-session-observer',
|
|
377
|
+
transcriptOffset: currentSize,
|
|
378
|
+
memoryId: null,
|
|
379
|
+
},
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
log(`[observer] Extracted ${result.factsExtracted} facts, saved ${result.factsSaved} (skipped ${result.factsSkipped})`);
|
|
383
|
+
}
|
|
384
|
+
session.lastObservation = now;
|
|
385
|
+
session.lastObservationSize = currentSize;
|
|
386
|
+
flushBudgets();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
log(`[observer] Mid-session extraction failed: ${err}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ── Generate briefing (skip if transcript unchanged) ──
|
|
394
|
+
let briefingResult = { success: false };
|
|
395
|
+
if (transcriptChanged) {
|
|
396
|
+
const transcriptContent = readTailTranscript(session.transcriptPath, 100 * 1024); // 100KB max
|
|
397
|
+
briefingResult = await generateCompactBriefing(transcriptContent, {
|
|
398
|
+
format: 'structured',
|
|
399
|
+
include_memories: true,
|
|
400
|
+
max_memories: 3,
|
|
401
|
+
});
|
|
402
|
+
if (briefingResult.success && briefingResult.briefing) {
|
|
403
|
+
appendToProgressFile(sessionId, briefingResult.briefing);
|
|
404
|
+
log(`[reflection] Appended briefing to progress file (${briefingResult.briefing.length} chars)`);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
log(`[reflection] Failed to generate briefing for ${sessionId}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// ── Parallel operations ──
|
|
411
|
+
const globalConfig = getConfig();
|
|
412
|
+
const parallelOps = [];
|
|
413
|
+
// memory_consolidation - skip if no new memories (disabled by default, opt-in only)
|
|
414
|
+
if (memoriesChanged && idleConfig.operations?.memory_consolidation === true) {
|
|
415
|
+
parallelOps.push((async () => {
|
|
416
|
+
const threshold = idleConfig.thresholds?.similarity_for_merge ?? 0.92;
|
|
417
|
+
const limit = idleConfig.max_memories_to_process ?? 50;
|
|
418
|
+
log(`[reflection] Running memory consolidation (threshold=${threshold}, limit=${limit})`);
|
|
419
|
+
const { consolidate } = await import('../commands/consolidate.js');
|
|
420
|
+
await consolidate({
|
|
421
|
+
threshold: String(threshold),
|
|
422
|
+
limit: String(limit),
|
|
423
|
+
llm: true,
|
|
424
|
+
verbose: false,
|
|
425
|
+
});
|
|
426
|
+
log(`[reflection] Memory consolidation complete`);
|
|
427
|
+
})());
|
|
428
|
+
}
|
|
429
|
+
// retention_cleanup - independent (always runs if enabled)
|
|
430
|
+
if (globalConfig.retention?.enabled && idleConfig.operations?.retention_cleanup !== false) {
|
|
431
|
+
parallelOps.push((async () => {
|
|
432
|
+
log(`[reflection] Running retention cleanup`);
|
|
433
|
+
const { retention } = await import('../commands/retention.js');
|
|
434
|
+
await retention({ apply: true, verbose: false });
|
|
435
|
+
log(`[reflection] Retention cleanup complete`);
|
|
436
|
+
})());
|
|
437
|
+
}
|
|
438
|
+
await Promise.all(parallelOps);
|
|
439
|
+
// ── Graph cleanup: prune → enrich → orphans → communities → centrality ──
|
|
440
|
+
if (idleConfig.operations?.graph_refinement !== false ||
|
|
441
|
+
idleConfig.operations?.graph_enrichment !== false) {
|
|
442
|
+
const shouldRun = memoriesChanged || session.lastLinkCount === undefined;
|
|
443
|
+
if (shouldRun) {
|
|
444
|
+
log(`[reflection] Running graph cleanup pipeline`);
|
|
445
|
+
try {
|
|
446
|
+
const { graphCleanup } = await import('../lib/graph/cleanup.js');
|
|
447
|
+
const cleanupResult = await graphCleanup({
|
|
448
|
+
skipEnrich: idleConfig.operations?.graph_enrichment === false,
|
|
449
|
+
onProgress: (step, detail) => log(`[reflection] [${step}] ${detail}`),
|
|
450
|
+
});
|
|
451
|
+
log(`[reflection] Cleanup: pruned ${cleanupResult.pruned}, enriched ${cleanupResult.enriched}, orphans ${cleanupResult.orphansConnected}, communities ${cleanupResult.communitiesDetected}, centrality ${cleanupResult.centralityUpdated}`);
|
|
452
|
+
// Proximity links from co-occurrence (not part of cleanup pipeline)
|
|
453
|
+
try {
|
|
454
|
+
const { createProximityLinks } = await import('../lib/graph/contextual-proximity.js');
|
|
455
|
+
const r = await createProximityLinks({ minCooccurrence: 2 });
|
|
456
|
+
log(`[reflection] Created ${r.created} proximity links`);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
log(`[reflection] Proximity failed: ${err}`);
|
|
460
|
+
}
|
|
461
|
+
// Synthesize patterns from community clusters (uses cleanup's community result)
|
|
462
|
+
if (cleanupResult.communityResult &&
|
|
463
|
+
cleanupResult.communityResult.communities.length > 0) {
|
|
464
|
+
try {
|
|
465
|
+
const { synthesizeFromCommunities } = await import('../lib/reflection-synthesizer.js');
|
|
466
|
+
const synthResult = await synthesizeFromCommunities(cleanupResult.communityResult, {
|
|
467
|
+
log,
|
|
468
|
+
});
|
|
469
|
+
const hasSynthActivity = synthResult.patternsCreated > 0 ||
|
|
470
|
+
synthResult.duplicatesSkipped > 0 ||
|
|
471
|
+
synthResult.reinforced > 0;
|
|
472
|
+
if (hasSynthActivity) {
|
|
473
|
+
log(`[reflection] Synthesized ${synthResult.patternsCreated} patterns from ${synthResult.clustersProcessed} clusters` +
|
|
474
|
+
(synthResult.reinforced > 0
|
|
475
|
+
? `, reinforced ${synthResult.reinforced} existing`
|
|
476
|
+
: '') +
|
|
477
|
+
(synthResult.duplicatesSkipped > 0
|
|
478
|
+
? `, skipped ${synthResult.duplicatesSkipped} duplicates`
|
|
479
|
+
: '') +
|
|
480
|
+
(synthResult.observationsMarked > 0
|
|
481
|
+
? `, marked ${synthResult.observationsMarked} as reflected`
|
|
482
|
+
: ''));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
log(`[reflection] Synthesis failed: ${err}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
session.lastLinkCount = (session.lastLinkCount ?? 0) + cleanupResult.orphansConnected;
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
log(`[reflection] Graph cleanup failed: ${err}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
log(`[reflection] Skipping graph cleanup (no changes)`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// ── Write reflection (runs last, may use LLM) ──
|
|
500
|
+
if (idleConfig.operations?.write_reflection !== false) {
|
|
501
|
+
log(`[reflection] Writing reflection for ${sessionId}`);
|
|
502
|
+
try {
|
|
503
|
+
const progressPath = getProgressFilePath(sessionId);
|
|
504
|
+
const briefingContent = fs.existsSync(progressPath)
|
|
505
|
+
? fs.readFileSync(progressPath, 'utf-8')
|
|
506
|
+
: briefingResult.briefing || '';
|
|
507
|
+
if (briefingContent.length >= 100) {
|
|
508
|
+
await writeReflection(briefingContent, idleConfig);
|
|
509
|
+
log(`[reflection] Reflection written`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
log(`[reflection] Write reflection error: ${err}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
log(`[reflection] Completed reflection for session ${sessionId}`);
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
log(`[reflection] Error for session ${sessionId}: ${err}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// ============================================================================
|
|
523
|
+
// HTTP Request Handler
|
|
524
|
+
// ============================================================================
|
|
525
|
+
async function handleRequest(req, res) {
|
|
526
|
+
const reqUrl = new URL(req.url || '/', `http://localhost`);
|
|
527
|
+
const method = req.method || 'GET';
|
|
528
|
+
// CORS headers (for potential web clients)
|
|
529
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
530
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
531
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
532
|
+
if (method === 'OPTIONS') {
|
|
533
|
+
res.writeHead(204);
|
|
534
|
+
res.end();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Parse JSON body for POST requests
|
|
538
|
+
let body = null;
|
|
539
|
+
if (method === 'POST') {
|
|
540
|
+
body = await parseBody(req);
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
// Route request
|
|
544
|
+
const result = await routeRequest(method, reqUrl.pathname, reqUrl.searchParams, body);
|
|
545
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
546
|
+
res.end(JSON.stringify(result));
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
log(`[http] Error: ${err.message}`);
|
|
550
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
551
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
export async function parseBody(req) {
|
|
555
|
+
return new Promise((resolve, reject) => {
|
|
556
|
+
let data = '';
|
|
557
|
+
req.on('data', (chunk) => (data += chunk));
|
|
558
|
+
req.on('end', () => {
|
|
559
|
+
try {
|
|
560
|
+
resolve(data ? JSON.parse(data) : {});
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
logWarn('daemon', 'Invalid JSON in HTTP request body', {
|
|
564
|
+
error: err instanceof Error ? err.message : String(err),
|
|
565
|
+
});
|
|
566
|
+
resolve({});
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
req.on('error', reject);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
/** @internal Exported for testing */
|
|
573
|
+
export async function routeRequest(method, pathname, searchParams, body) {
|
|
574
|
+
// Health check
|
|
575
|
+
if (pathname === '/health') {
|
|
576
|
+
return {
|
|
577
|
+
status: 'ok',
|
|
578
|
+
pid: process.pid,
|
|
579
|
+
uptime: Date.now() - (state?.startedAt || Date.now()),
|
|
580
|
+
activeSessions: sessionManager?.count() || 0,
|
|
581
|
+
cwd: state?.cwd || process.cwd(),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
// Session endpoints
|
|
585
|
+
if (pathname === '/api/session/register' && method === 'POST') {
|
|
586
|
+
const { session_id, transcript_path, is_service = false } = body;
|
|
587
|
+
if (!session_id) {
|
|
588
|
+
throw new ValidationError('session_id required');
|
|
589
|
+
}
|
|
590
|
+
const session = sessionManager.register(session_id, transcript_path || '', is_service);
|
|
591
|
+
log(`[session] Registered: ${session_id}${is_service ? ' (service)' : ''}`);
|
|
592
|
+
return { success: true, session };
|
|
593
|
+
}
|
|
594
|
+
if (pathname === '/api/session/unregister' && method === 'POST') {
|
|
595
|
+
const { session_id, transcript_path, run_reflection } = body;
|
|
596
|
+
if (!session_id) {
|
|
597
|
+
throw new ValidationError('session_id required');
|
|
598
|
+
}
|
|
599
|
+
const session = sessionManager.get(session_id);
|
|
600
|
+
const transcriptFile = transcript_path || session?.transcriptPath || '';
|
|
601
|
+
// Flush session counters to learning_deltas before unregister
|
|
602
|
+
try {
|
|
603
|
+
const d = await getStorageDispatcher();
|
|
604
|
+
await d.flushSessionCounters('daemon-session');
|
|
605
|
+
}
|
|
606
|
+
catch (err) {
|
|
607
|
+
log(`[session] Failed to flush session counters: ${err}`);
|
|
608
|
+
}
|
|
609
|
+
// Unregister the session immediately (don't block on processing)
|
|
610
|
+
const removed = sessionManager.unregister(session_id);
|
|
611
|
+
clearBriefingCache(session_id); // Clean up any cached briefing
|
|
612
|
+
removeBudget(session_id); // Clean up token budget
|
|
613
|
+
removeObservations(session_id); // Clean up observation JSONL
|
|
614
|
+
flushBudgets();
|
|
615
|
+
log(`[session] Unregistered: ${session_id} (removed=${removed})`);
|
|
616
|
+
// Process session asynchronously (summarize transcript, extract learnings, save to memory)
|
|
617
|
+
if (run_reflection && transcriptFile) {
|
|
618
|
+
sessionManager.incrementPendingWork();
|
|
619
|
+
log(`[session] Queuing async processing for ${session_id}`);
|
|
620
|
+
// Fire-and-forget async processing
|
|
621
|
+
(async () => {
|
|
622
|
+
try {
|
|
623
|
+
const result = await processSessionEnd(transcriptFile, session_id, log);
|
|
624
|
+
log(`[session] Processing complete for ${session_id}: summary=${result.summary.length}chars, learnings=${result.learnings.length}, saved=${result.saved}`);
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
log(`[session] Processing failed for ${session_id}: ${err}`);
|
|
628
|
+
}
|
|
629
|
+
finally {
|
|
630
|
+
sessionManager.decrementPendingWork();
|
|
631
|
+
// Check shutdown after work completes
|
|
632
|
+
checkShutdown();
|
|
633
|
+
}
|
|
634
|
+
})();
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// No processing needed, check shutdown immediately
|
|
638
|
+
checkShutdown();
|
|
639
|
+
}
|
|
640
|
+
return { success: removed, remaining_sessions: sessionManager.count() };
|
|
641
|
+
}
|
|
642
|
+
if (pathname === '/api/session/activity' && method === 'POST') {
|
|
643
|
+
const { session_id, type, transcript_path, is_service = false } = body;
|
|
644
|
+
if (!session_id || !type) {
|
|
645
|
+
throw new ValidationError('session_id and type required');
|
|
646
|
+
}
|
|
647
|
+
let session = sessionManager.activity(session_id, type);
|
|
648
|
+
if (!session) {
|
|
649
|
+
// Auto-register if session not found (with transcript_path if provided)
|
|
650
|
+
sessionManager.register(session_id, transcript_path || '', is_service);
|
|
651
|
+
session = sessionManager.activity(session_id, type);
|
|
652
|
+
log(`[session] Auto-registered and activity: ${session_id} (${type})${is_service ? ' (service)' : ''}`);
|
|
653
|
+
}
|
|
654
|
+
else if (transcript_path && !session.transcriptPath) {
|
|
655
|
+
// Update transcript path if not set
|
|
656
|
+
session.transcriptPath = transcript_path;
|
|
657
|
+
log(`[session] Activity: ${session_id} (${type}) + updated transcript`);
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
log(`[session] Activity: ${session_id} (${type})`);
|
|
661
|
+
}
|
|
662
|
+
return { success: true };
|
|
663
|
+
}
|
|
664
|
+
if (pathname === '/api/sessions' && method === 'GET') {
|
|
665
|
+
const includeService = searchParams.get('includeService') === 'true';
|
|
666
|
+
const sessions = {};
|
|
667
|
+
for (const [id, session] of sessionManager.getAll(includeService)) {
|
|
668
|
+
sessions[id] = session;
|
|
669
|
+
}
|
|
670
|
+
return { sessions, count: sessionManager.count(includeService) };
|
|
671
|
+
}
|
|
672
|
+
// Search endpoints
|
|
673
|
+
if (pathname === '/api/search' && method === 'POST') {
|
|
674
|
+
const { query, limit = 5, threshold = 0.3 } = body;
|
|
675
|
+
if (!query) {
|
|
676
|
+
throw new ValidationError('query required');
|
|
677
|
+
}
|
|
678
|
+
const queryEmbedding = await getEmbedding(query);
|
|
679
|
+
const results = await hybridSearchDocs(query, queryEmbedding, limit, threshold);
|
|
680
|
+
// Track access for returned memories
|
|
681
|
+
const accesses = results
|
|
682
|
+
.filter((r) => r.memory_id)
|
|
683
|
+
.map((r) => ({ memoryId: r.memory_id, weight: 0.5 }));
|
|
684
|
+
if (accesses.length > 0) {
|
|
685
|
+
await incrementMemoryAccessBatch(accesses);
|
|
686
|
+
}
|
|
687
|
+
return { results };
|
|
688
|
+
}
|
|
689
|
+
if (pathname === '/api/search-code' && method === 'POST') {
|
|
690
|
+
const { query, limit = 5, threshold = 0.3 } = body;
|
|
691
|
+
if (!query) {
|
|
692
|
+
throw new ValidationError('query required');
|
|
693
|
+
}
|
|
694
|
+
const queryEmbedding = await getEmbedding(query);
|
|
695
|
+
const results = await hybridSearchCode(query, queryEmbedding, limit, threshold);
|
|
696
|
+
return { results };
|
|
697
|
+
}
|
|
698
|
+
if (pathname === '/api/recall' && method === 'POST') {
|
|
699
|
+
const { query, limit = 5 } = body;
|
|
700
|
+
// Empty query returns recent memories
|
|
701
|
+
if (!query) {
|
|
702
|
+
const memories = await getRecentMemories(limit);
|
|
703
|
+
return { results: memories };
|
|
704
|
+
}
|
|
705
|
+
// Generate embedding for semantic search
|
|
706
|
+
const queryEmbedding = await getEmbedding(query);
|
|
707
|
+
const results = await hybridSearchMemories(query, queryEmbedding, limit, 0.3);
|
|
708
|
+
// Track access for returned memories
|
|
709
|
+
const accesses = results
|
|
710
|
+
.filter((r) => r.id)
|
|
711
|
+
.map((r) => ({ memoryId: r.id, weight: 1.0 }));
|
|
712
|
+
if (accesses.length > 0) {
|
|
713
|
+
await incrementMemoryAccessBatch(accesses);
|
|
714
|
+
}
|
|
715
|
+
return { results };
|
|
716
|
+
}
|
|
717
|
+
if (pathname === '/api/pinned' && method === 'GET') {
|
|
718
|
+
const pinned = await getPinnedMemories();
|
|
719
|
+
return { results: pinned };
|
|
720
|
+
}
|
|
721
|
+
if (pathname === '/api/pinned/cleanup' && method === 'POST') {
|
|
722
|
+
// Remove false invariant flags from observation-type memories
|
|
723
|
+
const pinned = await getPinnedMemories();
|
|
724
|
+
let cleaned = 0;
|
|
725
|
+
for (const mem of pinned) {
|
|
726
|
+
if (mem.type === 'observation' && mem.is_invariant) {
|
|
727
|
+
await setMemoryInvariant(mem.id, false);
|
|
728
|
+
cleaned++;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return { cleaned, total: pinned.length };
|
|
732
|
+
}
|
|
733
|
+
if (pathname === '/api/remember' && method === 'POST') {
|
|
734
|
+
const { content, tags = [], type = 'observation', source, global = false, valid_from, valid_until, } = body;
|
|
735
|
+
if (!content) {
|
|
736
|
+
throw new ValidationError('content required');
|
|
737
|
+
}
|
|
738
|
+
// In-flight dedup: if an identical request is already being processed, wait for it
|
|
739
|
+
// Prevents race condition when hooks fire twice for the same tool_use
|
|
740
|
+
const contentHash = content.slice(0, 200) + '|' + (tags || []).join(',');
|
|
741
|
+
const existing = rememberInFlight.get(contentHash);
|
|
742
|
+
if (existing) {
|
|
743
|
+
const result = await existing;
|
|
744
|
+
return { success: false, id: result.id, isDuplicate: true, reason: 'in-flight dedup' };
|
|
745
|
+
}
|
|
746
|
+
const processRemember = async () => {
|
|
747
|
+
// Check for sensitive content
|
|
748
|
+
const config = getConfig();
|
|
749
|
+
let finalContent = content;
|
|
750
|
+
if (config.sensitive_filter_enabled !== false) {
|
|
751
|
+
const scanResult = scanSensitive(content);
|
|
752
|
+
if (scanResult.hasSensitive) {
|
|
753
|
+
if (config.sensitive_auto_redact) {
|
|
754
|
+
finalContent = scanResult.redactedText;
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
throw new ValidationError('Content contains sensitive information');
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// Get embedding
|
|
762
|
+
const embedding = await getEmbedding(finalContent);
|
|
763
|
+
// Score quality
|
|
764
|
+
const qualityResult = await scoreMemory(finalContent);
|
|
765
|
+
if (!passesQualityThreshold(qualityResult)) {
|
|
766
|
+
return { success: false, reason: 'Below quality threshold', score: qualityResult.score };
|
|
767
|
+
}
|
|
768
|
+
// Save to appropriate DB
|
|
769
|
+
let result;
|
|
770
|
+
if (global) {
|
|
771
|
+
result = await saveGlobalMemory(finalContent, embedding, tags, type);
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
result = await saveMemory(finalContent, embedding, tags, source ?? type, {
|
|
775
|
+
qualityScore: { score: qualityResult.score, factors: qualityResult.factors },
|
|
776
|
+
validFrom: valid_from,
|
|
777
|
+
validUntil: valid_until,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return { success: !result.isDuplicate, id: result.id, isDuplicate: result.isDuplicate };
|
|
781
|
+
};
|
|
782
|
+
const promise = processRemember();
|
|
783
|
+
rememberInFlight.set(contentHash, promise);
|
|
784
|
+
setTimeout(() => rememberInFlight.delete(contentHash), REMEMBER_DEDUP_TTL_MS);
|
|
785
|
+
try {
|
|
786
|
+
return await promise;
|
|
787
|
+
}
|
|
788
|
+
finally {
|
|
789
|
+
rememberInFlight.delete(contentHash);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// Reflection endpoint
|
|
793
|
+
if (pathname === '/api/reflect' && method === 'POST') {
|
|
794
|
+
const { session_id } = body;
|
|
795
|
+
const watcherConfig = getIdleWatcherConfig();
|
|
796
|
+
if (session_id) {
|
|
797
|
+
const session = sessionManager.get(session_id);
|
|
798
|
+
if (!session) {
|
|
799
|
+
throw new NotFoundError('Session not found');
|
|
800
|
+
}
|
|
801
|
+
await handleReflection(session_id, session);
|
|
802
|
+
sessionManager.markReflection(session_id);
|
|
803
|
+
return { success: true, session_id };
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
// Run for all idle sessions
|
|
807
|
+
const idleSessions = sessionManager.getIdleSessions(watcherConfig.idle_minutes);
|
|
808
|
+
for (const { sessionId, session } of idleSessions) {
|
|
809
|
+
await handleReflection(sessionId, session);
|
|
810
|
+
sessionManager.markReflection(sessionId);
|
|
811
|
+
}
|
|
812
|
+
return { success: true, sessions_processed: idleSessions.length };
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
// Compact briefing endpoint (for /compact hook)
|
|
816
|
+
// Supports pre-generated cache for instant responses
|
|
817
|
+
if (pathname === '/api/briefing' && method === 'POST') {
|
|
818
|
+
const { transcript, transcript_path, session_id, format, include_learnings, include_memories, max_memories, use_cache, } = body;
|
|
819
|
+
// Try cached briefing first if session_id provided and use_cache not explicitly false
|
|
820
|
+
if (session_id && use_cache !== false) {
|
|
821
|
+
const cached = briefingCache.get(session_id);
|
|
822
|
+
if (cached) {
|
|
823
|
+
const age = Date.now() - cached.generatedAt;
|
|
824
|
+
if (age < BRIEFING_CACHE_MAX_AGE_MS) {
|
|
825
|
+
log(`[briefing] Serving cached briefing for ${session_id.slice(0, 8)} (age: ${Math.round(age / 1000)}s)`);
|
|
826
|
+
return { success: true, briefing: cached.briefing, cached: true };
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
// Either transcript content or path to transcript file
|
|
831
|
+
let transcriptContent;
|
|
832
|
+
if (transcript) {
|
|
833
|
+
transcriptContent = transcript;
|
|
834
|
+
}
|
|
835
|
+
else if (transcript_path && fs.existsSync(transcript_path)) {
|
|
836
|
+
transcriptContent = fs.readFileSync(transcript_path, 'utf-8');
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
throw new ValidationError('transcript or transcript_path required');
|
|
840
|
+
}
|
|
841
|
+
const result = await generateCompactBriefing(transcriptContent, {
|
|
842
|
+
format,
|
|
843
|
+
include_learnings,
|
|
844
|
+
include_memories,
|
|
845
|
+
max_memories,
|
|
846
|
+
});
|
|
847
|
+
// Cache the result if session_id provided
|
|
848
|
+
if (session_id && result.success && result.briefing && transcript_path) {
|
|
849
|
+
const stats = fs.existsSync(transcript_path) ? fs.statSync(transcript_path) : null;
|
|
850
|
+
briefingCache.set(session_id, {
|
|
851
|
+
briefing: result.briefing,
|
|
852
|
+
generatedAt: Date.now(),
|
|
853
|
+
transcriptSize: stats?.size || 0,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
return { ...result, cached: false };
|
|
857
|
+
}
|
|
858
|
+
// Status endpoints
|
|
859
|
+
if (pathname === '/api/status' && method === 'GET') {
|
|
860
|
+
const stats = await getStats();
|
|
861
|
+
const memStats = await getMemoryStats();
|
|
862
|
+
const watchStatus = getWatcherStatus();
|
|
863
|
+
const analyzeStatus = getAnalyzerStatus();
|
|
864
|
+
return {
|
|
865
|
+
daemon: {
|
|
866
|
+
pid: process.pid,
|
|
867
|
+
uptime: Date.now() - (state?.startedAt || Date.now()),
|
|
868
|
+
sessions: sessionManager.count(),
|
|
869
|
+
},
|
|
870
|
+
index: stats,
|
|
871
|
+
memories: memStats,
|
|
872
|
+
services: {
|
|
873
|
+
watch: watchStatus,
|
|
874
|
+
analyze: analyzeStatus,
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
// Watch service endpoints
|
|
879
|
+
if (pathname === '/api/watch/start' && method === 'POST') {
|
|
880
|
+
const { patterns, includeCode } = body;
|
|
881
|
+
const watchState = await startWatcher({ patterns, includeCode }, log);
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
active: watchState.active,
|
|
885
|
+
patterns: watchState.patterns,
|
|
886
|
+
includeCode: watchState.includeCode,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
if (pathname === '/api/watch/stop' && method === 'POST') {
|
|
890
|
+
await stopWatcher(log);
|
|
891
|
+
return { success: true };
|
|
892
|
+
}
|
|
893
|
+
if (pathname === '/api/watch/status' && method === 'GET') {
|
|
894
|
+
return getWatcherStatus();
|
|
895
|
+
}
|
|
896
|
+
if (pathname === '/api/watch/index' && method === 'POST') {
|
|
897
|
+
const { file } = body;
|
|
898
|
+
if (!file) {
|
|
899
|
+
throw new ValidationError('file required');
|
|
900
|
+
}
|
|
901
|
+
await indexFileOnDemand(file, log);
|
|
902
|
+
return { success: true, file };
|
|
903
|
+
}
|
|
904
|
+
// Analyze service endpoints
|
|
905
|
+
if (pathname === '/api/analyze/start' && method === 'POST') {
|
|
906
|
+
const { intervalMinutes, mode } = body;
|
|
907
|
+
const analyzeState = startAnalyzer({ intervalMinutes, mode }, log);
|
|
908
|
+
return {
|
|
909
|
+
success: true,
|
|
910
|
+
active: analyzeState.active,
|
|
911
|
+
runsCompleted: analyzeState.runsCompleted,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
if (pathname === '/api/analyze/stop' && method === 'POST') {
|
|
915
|
+
stopAnalyzer(log);
|
|
916
|
+
return { success: true };
|
|
917
|
+
}
|
|
918
|
+
if (pathname === '/api/analyze/status' && method === 'GET') {
|
|
919
|
+
return getAnalyzerStatus();
|
|
920
|
+
}
|
|
921
|
+
if (pathname === '/api/analyze' && method === 'POST') {
|
|
922
|
+
const { mode = 'claude' } = body;
|
|
923
|
+
await triggerAnalysis(mode, log);
|
|
924
|
+
return { success: true };
|
|
925
|
+
}
|
|
926
|
+
// Skills endpoints
|
|
927
|
+
if (pathname === '/api/skills/suggest' && method === 'POST') {
|
|
928
|
+
const { prompt, limit = 2 } = body;
|
|
929
|
+
if (!prompt) {
|
|
930
|
+
throw new ValidationError('prompt required');
|
|
931
|
+
}
|
|
932
|
+
const { suggestSkills, getSkillsConfig } = await import('../lib/skills.js');
|
|
933
|
+
const config = getSkillsConfig();
|
|
934
|
+
if (!config.enabled || !config.auto_suggest?.enabled) {
|
|
935
|
+
return { success: true, skills: [], disabled: true };
|
|
936
|
+
}
|
|
937
|
+
const suggestions = await suggestSkills(prompt, config);
|
|
938
|
+
return {
|
|
939
|
+
success: true,
|
|
940
|
+
skills: suggestions.slice(0, limit),
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (pathname === '/api/skills/index' && method === 'POST') {
|
|
944
|
+
const { indexLocalSkills } = await import('../lib/skills.js');
|
|
945
|
+
const cwd = state?.cwd || process.cwd();
|
|
946
|
+
const count = indexLocalSkills(cwd);
|
|
947
|
+
return { success: true, indexed: count };
|
|
948
|
+
}
|
|
949
|
+
if (pathname === '/api/skills/track' && method === 'POST') {
|
|
950
|
+
const { skill_name } = body;
|
|
951
|
+
if (!skill_name) {
|
|
952
|
+
throw new ValidationError('skill_name required');
|
|
953
|
+
}
|
|
954
|
+
const { trackSkillUsage } = await import('../lib/skills.js');
|
|
955
|
+
trackSkillUsage(skill_name);
|
|
956
|
+
return { success: true };
|
|
957
|
+
}
|
|
958
|
+
// Skyll status endpoint
|
|
959
|
+
if (pathname === '/api/skills/skyll' && method === 'GET') {
|
|
960
|
+
const { getSkyllStatus } = await import('../lib/skyll-client.js');
|
|
961
|
+
return getSkyllStatus();
|
|
962
|
+
}
|
|
963
|
+
// Services endpoint (list all services status)
|
|
964
|
+
if (pathname === '/api/services' && method === 'GET') {
|
|
965
|
+
return {
|
|
966
|
+
watch: getWatcherStatus(),
|
|
967
|
+
analyze: getAnalyzerStatus(),
|
|
968
|
+
idle: {
|
|
969
|
+
enabled: true,
|
|
970
|
+
sessions: sessionManager.count(),
|
|
971
|
+
},
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
throw new NotFoundError(`Unknown endpoint: ${method} ${pathname}`);
|
|
975
|
+
}
|
|
976
|
+
// ============================================================================
|
|
977
|
+
// Daemon Lifecycle
|
|
978
|
+
// ============================================================================
|
|
979
|
+
export async function startDaemon() {
|
|
980
|
+
if (state?.server) {
|
|
981
|
+
return { port: state.port, pid: process.pid };
|
|
982
|
+
}
|
|
983
|
+
// Check if another daemon is already running (prevent duplicate processes)
|
|
984
|
+
const existingPidFile = getDaemonPidFile();
|
|
985
|
+
if (fs.existsSync(existingPidFile)) {
|
|
986
|
+
try {
|
|
987
|
+
const existingPid = parseInt(fs.readFileSync(existingPidFile, 'utf8').trim(), 10);
|
|
988
|
+
if (existingPid && existingPid !== process.pid) {
|
|
989
|
+
// Check if process is actually running
|
|
990
|
+
try {
|
|
991
|
+
process.kill(existingPid, 0); // Signal 0 = check if process exists
|
|
992
|
+
// Process exists, read port and return
|
|
993
|
+
const portFile = getDaemonPortFile();
|
|
994
|
+
if (fs.existsSync(portFile)) {
|
|
995
|
+
const port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10);
|
|
996
|
+
log(`[daemon] Another daemon already running (pid=${existingPid}, port=${port})`);
|
|
997
|
+
process.exit(0); // Exit silently - another daemon is handling things
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
// Process doesn't exist, clean up stale files
|
|
1002
|
+
log(`[daemon] Cleaning up stale PID file (pid=${existingPid} not running)`);
|
|
1003
|
+
fs.unlinkSync(existingPidFile);
|
|
1004
|
+
const portFile = getDaemonPortFile();
|
|
1005
|
+
if (fs.existsSync(portFile)) {
|
|
1006
|
+
fs.unlinkSync(portFile);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
logWarn('daemon', 'Failed to read daemon PID file', {
|
|
1013
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const cwd = getProjectRoot();
|
|
1018
|
+
const watcherConfig = getIdleWatcherConfig();
|
|
1019
|
+
getIdleReflectionConfig();
|
|
1020
|
+
// Initialize storage dispatcher (routes to SQLite or PG based on config)
|
|
1021
|
+
await initStorageDispatcher();
|
|
1022
|
+
// Initialize session manager
|
|
1023
|
+
sessionManager = createSessionManager();
|
|
1024
|
+
// Load token budgets from previous daemon run
|
|
1025
|
+
loadBudgets();
|
|
1026
|
+
// Clean up stale observation files (>48h)
|
|
1027
|
+
cleanupStaleObservations();
|
|
1028
|
+
// Create HTTP server
|
|
1029
|
+
const server = http.createServer((req, res) => {
|
|
1030
|
+
handleRequest(req, res).catch((err) => {
|
|
1031
|
+
log(`[http] Unhandled error: ${err.message}`);
|
|
1032
|
+
res.writeHead(500);
|
|
1033
|
+
res.end();
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
// Find available port
|
|
1037
|
+
const portStart = DEFAULT_PORT_RANGE_START;
|
|
1038
|
+
let port = portStart;
|
|
1039
|
+
for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {
|
|
1040
|
+
await new Promise((resolve, reject) => {
|
|
1041
|
+
server.once('error', (err) => {
|
|
1042
|
+
if (err.code === 'EADDRINUSE') {
|
|
1043
|
+
port++;
|
|
1044
|
+
resolve();
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
reject(err);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
server.listen(port, '127.0.0.1', () => {
|
|
1051
|
+
resolve();
|
|
1052
|
+
});
|
|
1053
|
+
});
|
|
1054
|
+
if (server.listening) {
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (!server.listening) {
|
|
1059
|
+
throw new NetworkError(`Could not find available port in range ${portStart}-${portStart + MAX_PORT_ATTEMPTS}`);
|
|
1060
|
+
}
|
|
1061
|
+
// Save state
|
|
1062
|
+
state = {
|
|
1063
|
+
cwd,
|
|
1064
|
+
startedAt: Date.now(),
|
|
1065
|
+
port,
|
|
1066
|
+
server,
|
|
1067
|
+
};
|
|
1068
|
+
// Write PID and port files
|
|
1069
|
+
fs.writeFileSync(getDaemonPidFile(), String(process.pid));
|
|
1070
|
+
fs.writeFileSync(getDaemonPortFile(), String(port));
|
|
1071
|
+
// Start idle watcher with briefing pre-generation
|
|
1072
|
+
idleWatcher = createIdleWatcher({
|
|
1073
|
+
sessionManager,
|
|
1074
|
+
onIdle: handleReflection,
|
|
1075
|
+
onPreGenerateBriefing: preGenerateBriefing,
|
|
1076
|
+
checkIntervalSeconds: watcherConfig.check_interval,
|
|
1077
|
+
idleMinutes: watcherConfig.idle_minutes,
|
|
1078
|
+
reflectionCooldownMinutes: watcherConfig.reflection_cooldown_minutes,
|
|
1079
|
+
preGenerateIdleSeconds: 120, // Pre-generate briefing after 2 min idle
|
|
1080
|
+
log,
|
|
1081
|
+
});
|
|
1082
|
+
idleWatcher.start();
|
|
1083
|
+
log(`[daemon] Started on port ${port} (pid=${process.pid})`);
|
|
1084
|
+
// Auto-start watch service if configured
|
|
1085
|
+
const config = getConfig();
|
|
1086
|
+
if (config.daemon?.watch?.auto_start) {
|
|
1087
|
+
const watchConfig = config.daemon.watch;
|
|
1088
|
+
await startWatcher({
|
|
1089
|
+
patterns: watchConfig.patterns || ['**/*.md'],
|
|
1090
|
+
includeCode: watchConfig.include_code ?? false,
|
|
1091
|
+
debounceMs: watchConfig.debounce_ms ?? 500,
|
|
1092
|
+
}, log);
|
|
1093
|
+
log(`[daemon] Auto-started watch service`);
|
|
1094
|
+
}
|
|
1095
|
+
// Auto-start analyze service if configured
|
|
1096
|
+
if (config.daemon?.analyze?.auto_start) {
|
|
1097
|
+
const analyzeConfig = config.daemon.analyze;
|
|
1098
|
+
startAnalyzer({
|
|
1099
|
+
intervalMinutes: analyzeConfig.interval_minutes ?? 30,
|
|
1100
|
+
mode: analyzeConfig.mode ?? 'claude',
|
|
1101
|
+
}, log);
|
|
1102
|
+
log(`[daemon] Auto-started analyze service`);
|
|
1103
|
+
}
|
|
1104
|
+
// Setup graceful shutdown
|
|
1105
|
+
setupShutdownHandlers();
|
|
1106
|
+
return { port, pid: process.pid };
|
|
1107
|
+
}
|
|
1108
|
+
function setupShutdownHandlers() {
|
|
1109
|
+
const shutdown = () => shutdownDaemon();
|
|
1110
|
+
process.on('SIGTERM', shutdown);
|
|
1111
|
+
process.on('SIGINT', shutdown);
|
|
1112
|
+
// SIGHUP only exists on Unix
|
|
1113
|
+
if (process.platform !== 'win32') {
|
|
1114
|
+
process.on('SIGHUP', shutdown);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Check if daemon should shutdown (no sessions and no pending work)
|
|
1119
|
+
*/
|
|
1120
|
+
function checkShutdown() {
|
|
1121
|
+
if (sessionManager?.canShutdown()) {
|
|
1122
|
+
log(`[daemon] No more sessions and no pending work, scheduling shutdown`);
|
|
1123
|
+
setTimeout(() => {
|
|
1124
|
+
if (sessionManager?.canShutdown()) {
|
|
1125
|
+
shutdownDaemon();
|
|
1126
|
+
}
|
|
1127
|
+
}, 5000); // Give 5 seconds for new sessions to connect
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
export function shutdownDaemon() {
|
|
1131
|
+
log('[daemon] Shutting down...');
|
|
1132
|
+
// Stop idle watcher
|
|
1133
|
+
if (idleWatcher) {
|
|
1134
|
+
idleWatcher.stop();
|
|
1135
|
+
idleWatcher = null;
|
|
1136
|
+
}
|
|
1137
|
+
// Stop watch service (async, but we're shutting down so fire-and-forget)
|
|
1138
|
+
stopWatcher(log).catch((err) => log(`[shutdown] Watcher stop failed: ${err}`));
|
|
1139
|
+
// Stop analyze service
|
|
1140
|
+
stopAnalyzer(log);
|
|
1141
|
+
// Close HTTP server
|
|
1142
|
+
if (state?.server) {
|
|
1143
|
+
state.server.close();
|
|
1144
|
+
state.server = null;
|
|
1145
|
+
}
|
|
1146
|
+
// Kill all spawned child processes (claude CLI, etc.)
|
|
1147
|
+
processRegistry.killAll();
|
|
1148
|
+
// Cleanup DB connections
|
|
1149
|
+
closeStorageDispatcher().catch((err) => log(`[shutdown] Storage close failed: ${err}`));
|
|
1150
|
+
cleanupEmbeddings();
|
|
1151
|
+
cleanupQualityScoring();
|
|
1152
|
+
closeDb();
|
|
1153
|
+
closeGlobalDb();
|
|
1154
|
+
// Remove PID and port files
|
|
1155
|
+
try {
|
|
1156
|
+
fs.unlinkSync(getDaemonPidFile());
|
|
1157
|
+
}
|
|
1158
|
+
catch (err) {
|
|
1159
|
+
if (err.code !== 'ENOENT')
|
|
1160
|
+
log(`[shutdown] PID file removal failed: ${err}`);
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
fs.unlinkSync(getDaemonPortFile());
|
|
1164
|
+
}
|
|
1165
|
+
catch (err) {
|
|
1166
|
+
if (err.code !== 'ENOENT')
|
|
1167
|
+
log(`[shutdown] Port file removal failed: ${err}`);
|
|
1168
|
+
}
|
|
1169
|
+
log('[daemon] Shutdown complete');
|
|
1170
|
+
process.exit(0);
|
|
1171
|
+
}
|
|
1172
|
+
// ============================================================================
|
|
1173
|
+
// Test Helpers (exported for unit testing)
|
|
1174
|
+
// ============================================================================
|
|
1175
|
+
/** @internal Initialize module state for testing without starting HTTP server */
|
|
1176
|
+
export function _initTestState(cwd = process.cwd()) {
|
|
1177
|
+
sessionManager = createSessionManager();
|
|
1178
|
+
state = { cwd, startedAt: Date.now(), port: 0, server: null };
|
|
1179
|
+
}
|
|
1180
|
+
/** @internal Reset module state after testing */
|
|
1181
|
+
export function _resetTestState() {
|
|
1182
|
+
sessionManager = null;
|
|
1183
|
+
idleWatcher = null;
|
|
1184
|
+
state = null;
|
|
1185
|
+
briefingCache.clear();
|
|
1186
|
+
briefingGenerationInProgress.clear();
|
|
1187
|
+
}
|
|
1188
|
+
// ============================================================================
|
|
1189
|
+
// CLI Entry Point
|
|
1190
|
+
// ============================================================================
|
|
1191
|
+
// If run directly, start daemon
|
|
1192
|
+
if (process.argv[1]?.endsWith('service.js') || process.argv[1]?.endsWith('service.ts')) {
|
|
1193
|
+
startDaemon()
|
|
1194
|
+
.then(({ port, pid }) => {
|
|
1195
|
+
console.log(`Daemon started on port ${port} (pid=${pid})`);
|
|
1196
|
+
})
|
|
1197
|
+
.catch((err) => {
|
|
1198
|
+
logError('daemon', `Failed to start daemon: ${err.message}`, err);
|
|
1199
|
+
console.error('Failed to start daemon:', err.message);
|
|
1200
|
+
process.exit(1);
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
//# sourceMappingURL=service.js.map
|