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