claude-memory-layer 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/AGENTS.md +11 -0
- package/README.md +374 -49
- package/benchmarks/replay/anonymized-real-sessions.json +48 -0
- package/dist/cli/index.js +10097 -6003
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +9745 -5587
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +6545 -5270
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +6646 -5354
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +6618 -5347
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +6619 -5354
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +6614 -5325
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +6702 -5356
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +13537 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp/index.js +20770 -0
- package/dist/mcp/index.js.map +7 -0
- package/dist/server/api/index.js +6632 -5319
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +6667 -5340
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +6568 -5350
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/bootstrap.js +244 -0
- package/dist/ui/assets/js/chat.js +373 -0
- package/dist/ui/assets/js/disclosure.js +232 -0
- package/dist/ui/assets/js/modals.js +298 -0
- package/dist/ui/assets/js/overview.js +655 -0
- package/dist/ui/assets/js/state.js +72 -0
- package/dist/ui/assets/js/views.js +468 -0
- package/dist/ui/index.html +43 -1
- package/dist/ui/index.ts +3 -0
- package/dist/ui/style.css +222 -0
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
- package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
- package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
- package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
- package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
- package/docs/architecture/comparison-index.md +47 -0
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
- package/package.json +12 -5
- package/scripts/build.ts +25 -8
- package/scripts/generate-session-qrels.ts +126 -0
- package/scripts/postinstall-embedding-backend.cjs +142 -0
- package/scripts/replay-retrieval-benchmark.ts +69 -0
- package/specs/thin-core-refactor/context.md +275 -0
- package/specs/thin-core-refactor/plan.md +536 -0
- package/specs/thin-core-refactor/spec.md +465 -0
- package/src/adapters/claude/capture/index.ts +3 -0
- package/src/adapters/claude/context/index.ts +3 -0
- package/src/adapters/claude/hooks/index.ts +21 -0
- package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
- package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
- package/src/adapters/claude/hooks/session-end.ts +59 -0
- package/src/adapters/claude/hooks/session-start.ts +73 -0
- package/src/adapters/claude/hooks/stop.ts +128 -0
- package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
- package/src/adapters/claude/index.ts +4 -0
- package/src/adapters/claude/transcript/index.ts +4 -0
- package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
- package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
- package/src/apps/cli/claude-settings-hooks.ts +138 -0
- package/src/apps/cli/codex-import-runner.ts +125 -0
- package/src/apps/cli/codex-validation-output.ts +95 -0
- package/src/apps/cli/hermes-import-runner.ts +130 -0
- package/src/apps/cli/hermes-validation-output.ts +91 -0
- package/src/apps/cli/index.ts +1731 -0
- package/src/apps/cli/mcp-install.ts +106 -0
- package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
- package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
- package/src/apps/dashboard/assets/js/chat.js +373 -0
- package/src/apps/dashboard/assets/js/disclosure.js +232 -0
- package/src/apps/dashboard/assets/js/modals.js +298 -0
- package/src/apps/dashboard/assets/js/overview.js +655 -0
- package/src/apps/dashboard/assets/js/state.js +72 -0
- package/src/apps/dashboard/assets/js/views.js +468 -0
- package/src/{ui → apps/dashboard}/index.html +43 -1
- package/src/apps/dashboard/index.ts +3 -0
- package/src/{ui → apps/dashboard}/style.css +222 -0
- package/src/apps/index.ts +5 -0
- package/src/apps/server/api/chat.ts +244 -0
- package/src/apps/server/api/citations.ts +105 -0
- package/src/apps/server/api/events.ts +137 -0
- package/src/apps/server/api/health.ts +53 -0
- package/src/apps/server/api/index.ts +26 -0
- package/src/apps/server/api/projects.ts +74 -0
- package/src/apps/server/api/search.ts +184 -0
- package/src/apps/server/api/sessions.ts +115 -0
- package/src/apps/server/api/stats.ts +723 -0
- package/src/apps/server/api/turns.ts +143 -0
- package/src/apps/server/api/utils.ts +65 -0
- package/src/apps/server/index.ts +111 -0
- package/src/cli/index.ts +2 -1311
- package/src/cli/retrieval-disclosure-output.ts +2 -0
- package/src/compat/index.ts +5 -0
- package/src/core/derive/fact-deriver.ts +170 -0
- package/src/core/derive/index.ts +2 -0
- package/src/core/derive/summary-deriver.ts +76 -0
- package/src/core/embedder.ts +4 -152
- package/src/core/engine/embedding-maintenance-service.ts +187 -0
- package/src/core/engine/endless-memory-services.ts +4 -0
- package/src/core/engine/index.ts +19 -0
- package/src/core/engine/memory-engine-services.ts +170 -0
- package/src/core/engine/memory-ingest-service.ts +317 -0
- package/src/core/engine/memory-query-service.ts +173 -0
- package/src/core/engine/memory-runtime-service.ts +162 -0
- package/src/core/engine/memory-service-composition.ts +231 -0
- package/src/core/engine/retrieval-analytics-service.ts +181 -0
- package/src/core/engine/retrieval-disclosure-service.ts +420 -0
- package/src/core/engine/retrieval-orchestrator.ts +377 -0
- package/src/core/engine/retrieval-services.ts +176 -0
- package/src/core/engine/shared-memory-services.ts +4 -0
- package/src/core/entity-repo.ts +1 -3
- package/src/core/event-store.ts +3 -3
- package/src/core/evidence-aligner.ts +2 -2
- package/src/core/external-market-context.ts +582 -0
- package/src/core/graduation.ts +2 -3
- package/src/core/index.ts +21 -0
- package/src/core/matcher.ts +2 -4
- package/src/core/model/memory-fact.ts +30 -0
- package/src/core/model/memory-rule.ts +14 -0
- package/src/core/model/memory-summary.ts +21 -0
- package/src/core/model/raw-event.ts +28 -0
- package/src/core/model/retrieval-result.ts +35 -0
- package/src/core/privacy/filter.ts +21 -10
- package/src/core/product-validation-matrix.ts +314 -0
- package/src/core/progressive-retriever.ts +1 -2
- package/src/core/registry/project-path.ts +54 -0
- package/src/core/registry/session-registry.ts +69 -0
- package/src/core/replay-evaluator.ts +625 -0
- package/src/core/retrieval-benchmark.ts +117 -0
- package/src/core/retrieval-quality.ts +109 -0
- package/src/core/retriever.ts +53 -15
- package/src/core/session-qrels.ts +360 -0
- package/src/core/shared-event-store.ts +1 -1
- package/src/core/sqlite-event-store.ts +35 -11
- package/src/core/task/blocker-resolver.ts +2 -2
- package/src/core/task/task-resolver.ts +0 -1
- package/src/core/vector-outbox.ts +1 -10
- package/src/core/vector-worker.ts +1 -1
- package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
- package/src/extensions/endless-memory/index.ts +1 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/mcp/handlers.ts +960 -0
- package/src/extensions/mcp/index.ts +48 -0
- package/src/extensions/mcp/tools.ts +252 -0
- package/src/extensions/shared-memory/index.ts +1 -0
- package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
- package/src/extensions/vector/embedder.ts +197 -0
- package/src/extensions/vector/index.ts +1 -0
- package/src/hooks/post-tool-use.ts +3 -236
- package/src/hooks/semantic-daemon-client.ts +1 -208
- package/src/hooks/semantic-daemon.ts +6 -271
- package/src/hooks/session-end.ts +4 -79
- package/src/hooks/session-start.ts +4 -73
- package/src/hooks/stop.ts +3 -173
- package/src/hooks/user-prompt-submit.ts +3 -338
- package/src/index.ts +13 -0
- package/src/mcp/handlers.ts +2 -212
- package/src/mcp/index.ts +3 -46
- package/src/mcp/tools.ts +2 -78
- package/src/server/api/chat.ts +2 -244
- package/src/server/api/citations.ts +2 -105
- package/src/server/api/events.ts +2 -137
- package/src/server/api/health.ts +2 -53
- package/src/server/api/index.ts +2 -26
- package/src/server/api/projects.ts +2 -74
- package/src/server/api/search.ts +2 -102
- package/src/server/api/sessions.ts +2 -115
- package/src/server/api/stats.ts +2 -724
- package/src/server/api/turns.ts +2 -143
- package/src/server/api/utils.ts +2 -46
- package/src/server/index.ts +2 -100
- package/src/services/bootstrap-organizer.ts +46 -26
- package/src/services/codex-session-history-importer.ts +521 -29
- package/src/services/hermes-session-history-importer.ts +733 -0
- package/src/services/memory-service-config.ts +36 -0
- package/src/services/memory-service-registry.ts +150 -0
- package/src/services/memory-service.ts +211 -1325
- package/src/services/session-history-importer.ts +58 -14
- package/tests/README.md +23 -0
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
- package/tests/apps/app-layer-boundary.test.ts +48 -0
- package/tests/apps/claude-settings-hooks.test.ts +107 -0
- package/tests/apps/cli-disclosure-output.test.ts +212 -0
- package/tests/apps/codex-import-runner.test.ts +99 -0
- package/tests/apps/codex-validation-output.test.ts +100 -0
- package/tests/apps/hermes-import-runner.test.ts +99 -0
- package/tests/apps/mcp-install-command.test.ts +59 -0
- package/tests/apps/package-build-entrypoints.test.ts +30 -0
- package/tests/apps/postinstall-embedding-backend.test.ts +167 -0
- package/tests/apps/search-api-disclosure.test.ts +162 -0
- package/tests/apps/stats-api-lightweight.test.ts +67 -0
- package/tests/apps/ui-disclosure-output.test.ts +140 -0
- package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
- package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
- package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
- package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
- package/tests/core/embedding-maintenance-service.test.ts +282 -0
- package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
- package/tests/core/external-market-context.test.ts +209 -0
- package/tests/core/fact-deriver.test.ts +79 -0
- package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
- package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
- package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
- package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
- package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
- package/tests/core/memory-engine-services.test.ts +240 -0
- package/tests/core/memory-ingest-service.test.ts +296 -0
- package/tests/core/memory-query-service.test.ts +129 -0
- package/tests/core/memory-runtime-service.test.ts +201 -0
- package/tests/core/memory-service-composition.test.ts +192 -0
- package/tests/core/memory-service-config.test.ts +41 -0
- package/tests/core/memory-service-facade.test.ts +30 -0
- package/tests/core/memory-service-registry.test.ts +206 -0
- package/tests/core/product-validation-matrix.test.ts +61 -0
- package/tests/core/project-registry.test.ts +78 -0
- package/tests/core/replay-evaluator.test.ts +181 -0
- package/tests/core/retrieval-analytics-service.test.ts +210 -0
- package/tests/core/retrieval-benchmark.test.ts +93 -0
- package/tests/core/retrieval-disclosure-service.test.ts +264 -0
- package/tests/core/retrieval-orchestrator.test.ts +403 -0
- package/tests/core/retrieval-quality.test.ts +31 -0
- package/tests/core/retrieval-services.test.ts +185 -0
- package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
- package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
- package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
- package/tests/core/session-history-importer-filter.test.ts +78 -0
- package/tests/core/session-qrels.test.ts +250 -0
- package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
- package/tests/core/summary-deriver.test.ts +66 -0
- package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
- package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
- package/tests/extensions/endless-memory-services.test.ts +325 -0
- package/tests/extensions/mcp-context-tools.test.ts +905 -0
- package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
- package/tests/extensions/mcp-package-build.test.ts +22 -0
- package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
- package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
- package/tests/extensions/shared-memory-services.test.ts +309 -0
- package/tests/extensions/vector-extension-boundary.test.ts +21 -0
- package/.claude/settings.local.json +0 -25
- package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
- package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
- package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
- package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
- package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
- package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
- package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
- package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
- package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
- package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
- package/.npm-cache/_update-notifier-last-checked +0 -0
- package/bootstrap-kb/decisions/decisions.md +0 -244
- package/bootstrap-kb/glossary/glossary.md +0 -46
- package/bootstrap-kb/modules/.claude-plugin.md +0 -22
- package/bootstrap-kb/modules/agents.md.md +0 -15
- package/bootstrap-kb/modules/claude.md.md +0 -15
- package/bootstrap-kb/modules/context.md.md +0 -15
- package/bootstrap-kb/modules/docs.md +0 -18
- package/bootstrap-kb/modules/handoff.md.md +0 -15
- package/bootstrap-kb/modules/package-lock.json.md +0 -15
- package/bootstrap-kb/modules/package.json.md +0 -15
- package/bootstrap-kb/modules/plan.md.md +0 -15
- package/bootstrap-kb/modules/readme.md.md +0 -15
- package/bootstrap-kb/modules/scripts.md +0 -26
- package/bootstrap-kb/modules/spec.md.md +0 -15
- package/bootstrap-kb/modules/specs.md +0 -20
- package/bootstrap-kb/modules/src.md +0 -51
- package/bootstrap-kb/modules/tests.md +0 -42
- package/bootstrap-kb/modules/tsconfig.json.md +0 -15
- package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
- package/bootstrap-kb/overview/overview.md +0 -40
- package/bootstrap-kb/sources/manifest.json +0 -950
- package/bootstrap-kb/sources/manifest.md +0 -227
- package/bootstrap-kb/timeline/timeline.md +0 -57
- package/claude-memory-layer-1.0.14.tgz +0 -0
- package/d.sh +0 -3
- package/deploy.sh +0 -3
- package/dist/ui/app.js +0 -2101
- package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
- package/memory/_index.md +0 -419
- package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
- package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
- package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
- package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
- package/memory/default/uncategorized/2026-02-25.md +0 -4839
- package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
- package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
- package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
- package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
- package/memory/specs/citations-system/2026-02-25.md +0 -1121
- package/memory/specs/endless-mode/2026-02-25.md +0 -1392
- package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
- package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
- package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
- package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
- package/memory/specs/private-tags/2026-02-25.md +0 -1057
- package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
- package/memory/specs/task-entity-system/2026-02-25.md +0 -924
- package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
- package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
- package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
- package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
- package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
- package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
- package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
- package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
- package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
- package/specs/optional-duckdb/context.md +0 -77
- package/specs/optional-duckdb/plan.md +0 -142
- package/specs/optional-duckdb/spec.md +0 -35
- package/src/ui/app.js +0 -2101
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostToolUse Hook
|
|
3
|
+
* Called after each tool execution - stores tool observations
|
|
4
|
+
*
|
|
5
|
+
* Actual Claude Code input format:
|
|
6
|
+
* {
|
|
7
|
+
* session_id, tool_name, tool_input, tool_use_id,
|
|
8
|
+
* tool_response: { stdout?, stderr?, content?, interrupted?, isImage? },
|
|
9
|
+
* cwd, transcript_path, permission_mode, hook_event_name
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getLightweightMemoryService } from '../../../services/memory-service.js';
|
|
14
|
+
import { applyPrivacyFilter, maskSensitiveInput, truncateOutput } from '../../../core/privacy/index.js';
|
|
15
|
+
import { extractMetadata } from '../../../core/metadata-extractor.js';
|
|
16
|
+
import { readTurnState } from '../../../core/turn-state.js';
|
|
17
|
+
import type { PostToolUseInput, ToolObservationPayload, Config } from '../../../core/types.js';
|
|
18
|
+
|
|
19
|
+
// Default config
|
|
20
|
+
const DEFAULT_CONFIG: Config['toolObservation'] = {
|
|
21
|
+
enabled: true,
|
|
22
|
+
excludedTools: [
|
|
23
|
+
// Trivial meta tools
|
|
24
|
+
'TodoWrite', 'TodoRead',
|
|
25
|
+
// Reproducible query tools (no storage value)
|
|
26
|
+
'Read', 'Grep', 'Glob',
|
|
27
|
+
'ToolSearch', 'WebFetch', 'WebSearch', 'NotebookRead',
|
|
28
|
+
// Low-value system tools
|
|
29
|
+
'Skill', 'EnterPlanMode',
|
|
30
|
+
],
|
|
31
|
+
minOutputLength: parseInt(process.env.CLAUDE_MEMORY_TOOL_MIN_OUTPUT_LEN || '100'),
|
|
32
|
+
maxOutputLength: 10000,
|
|
33
|
+
maxOutputLines: 100,
|
|
34
|
+
storeOnlyOnSuccess: false
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Tools that are always stored regardless of output length
|
|
38
|
+
const ALWAYS_STORE_TOOLS = new Set([
|
|
39
|
+
'Write', 'Edit', 'MultiEdit', 'Agent', 'Task', 'ExitPlanMode'
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Keywords that indicate a Bash output is worth storing
|
|
43
|
+
const IMPORTANT_BASH_KEYWORDS = [
|
|
44
|
+
'error', 'failed', 'exception', 'traceback', 'panic',
|
|
45
|
+
'warning', 'deprecated',
|
|
46
|
+
'test passed', 'test failed', 'tests passed', 'tests failed',
|
|
47
|
+
'coverage', 'assert',
|
|
48
|
+
'published', 'deployed', 'built successfully', 'build complete',
|
|
49
|
+
'successfully installed', 'successfully created',
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* For Bash commands, only store output that is significant:
|
|
54
|
+
* - Has stderr content
|
|
55
|
+
* - Contains important keywords (errors, test results, deploy events)
|
|
56
|
+
* - Output is very long (> 2000 chars), indicating meaningful work
|
|
57
|
+
*/
|
|
58
|
+
function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {
|
|
59
|
+
if (response?.stderr && response.stderr.trim().length > 20) return true;
|
|
60
|
+
const lower = output.toLowerCase();
|
|
61
|
+
if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;
|
|
62
|
+
return output.trim().length > 2000;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Determine if a tool output is significant enough to store.
|
|
67
|
+
* Always-store tools bypass the length check.
|
|
68
|
+
* Bash uses keyword-based significance detection.
|
|
69
|
+
* Other tools require non-empty stderr or output length >= minLen.
|
|
70
|
+
*/
|
|
71
|
+
function hasSignificantOutput(
|
|
72
|
+
toolName: string,
|
|
73
|
+
output: string,
|
|
74
|
+
response: PostToolUseInput['tool_response'],
|
|
75
|
+
minLen: number
|
|
76
|
+
): boolean {
|
|
77
|
+
if (ALWAYS_STORE_TOOLS.has(toolName)) return true;
|
|
78
|
+
if (toolName === 'Bash') return isBashSignificant(output, response);
|
|
79
|
+
if (response?.stderr && response.stderr.trim().length > 0) return true;
|
|
80
|
+
return output.trim().length >= minLen;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {
|
|
84
|
+
excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],
|
|
85
|
+
anonymize: false,
|
|
86
|
+
privateTags: {
|
|
87
|
+
enabled: true,
|
|
88
|
+
marker: '[PRIVATE]',
|
|
89
|
+
preserveLineCount: false,
|
|
90
|
+
supportedFormats: ['xml']
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extract text output from tool_response object
|
|
96
|
+
*/
|
|
97
|
+
function extractToolOutput(response: PostToolUseInput['tool_response']): string {
|
|
98
|
+
if (!response) return '';
|
|
99
|
+
|
|
100
|
+
// Bash tools: stdout + stderr
|
|
101
|
+
if (response.stdout !== undefined) {
|
|
102
|
+
const parts: string[] = [];
|
|
103
|
+
if (response.stdout) parts.push(response.stdout);
|
|
104
|
+
if (response.stderr) parts.push(`[stderr] ${response.stderr}`);
|
|
105
|
+
return parts.join('\n') || '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Other tools may have content field
|
|
109
|
+
if (response.content !== undefined) {
|
|
110
|
+
return typeof response.content === 'string'
|
|
111
|
+
? response.content
|
|
112
|
+
: JSON.stringify(response.content);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Fallback: stringify the whole response
|
|
116
|
+
return JSON.stringify(response);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Determine if the tool execution was successful
|
|
121
|
+
*/
|
|
122
|
+
function isToolSuccess(response: PostToolUseInput['tool_response']): boolean {
|
|
123
|
+
if (!response) return false;
|
|
124
|
+
if (response.interrupted) return false;
|
|
125
|
+
// If stderr has content but stdout also has content, still consider success
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function main(): Promise<void> {
|
|
130
|
+
// Read input from stdin
|
|
131
|
+
const inputData = await readStdin();
|
|
132
|
+
const input: PostToolUseInput = JSON.parse(inputData);
|
|
133
|
+
|
|
134
|
+
const config = { ...DEFAULT_CONFIG };
|
|
135
|
+
const privacyConfig = DEFAULT_PRIVACY_CONFIG;
|
|
136
|
+
|
|
137
|
+
// Allow env-based blocklist override
|
|
138
|
+
const envBlocklist = process.env.CLAUDE_MEMORY_TOOL_BLOCKLIST;
|
|
139
|
+
if (envBlocklist !== undefined) {
|
|
140
|
+
config.excludedTools = envBlocklist.split(',').map((s) => s.trim()).filter(Boolean);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 1. Check if tool observation is enabled
|
|
144
|
+
if (!config.enabled) {
|
|
145
|
+
console.log(JSON.stringify({}));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 2. Check if tool is excluded
|
|
150
|
+
if (config.excludedTools?.includes(input.tool_name)) {
|
|
151
|
+
console.log(JSON.stringify({}));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. Extract output from tool_response object
|
|
156
|
+
const toolOutput = extractToolOutput(input.tool_response);
|
|
157
|
+
const success = isToolSuccess(input.tool_response);
|
|
158
|
+
|
|
159
|
+
// 4. Check success filter
|
|
160
|
+
if (!success && config.storeOnlyOnSuccess) {
|
|
161
|
+
console.log(JSON.stringify({}));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 4.5. Output-level filter: skip low-signal outputs
|
|
166
|
+
if (!hasSignificantOutput(
|
|
167
|
+
input.tool_name, toolOutput, input.tool_response,
|
|
168
|
+
config.minOutputLength ?? 100
|
|
169
|
+
)) {
|
|
170
|
+
console.log(JSON.stringify({}));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const memoryService = getLightweightMemoryService(input.session_id);
|
|
176
|
+
|
|
177
|
+
// 5. Mask sensitive data in input
|
|
178
|
+
const maskedInput = maskSensitiveInput(input.tool_input);
|
|
179
|
+
|
|
180
|
+
// 6. Apply privacy filter to output
|
|
181
|
+
const filterResult = applyPrivacyFilter(toolOutput, privacyConfig);
|
|
182
|
+
const maskedOutput = filterResult.content;
|
|
183
|
+
|
|
184
|
+
// 7. Truncate output
|
|
185
|
+
const truncatedOutput = truncateOutput(maskedOutput, {
|
|
186
|
+
maxLength: config.maxOutputLength,
|
|
187
|
+
maxLines: config.maxOutputLines
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 8. Extract metadata
|
|
191
|
+
const metadata = extractMetadata(
|
|
192
|
+
input.tool_name,
|
|
193
|
+
maskedInput,
|
|
194
|
+
toolOutput,
|
|
195
|
+
success
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// 8.5. Read current turn_id from state file
|
|
199
|
+
const turnId = readTurnState(input.session_id);
|
|
200
|
+
|
|
201
|
+
// 9. Create payload (include turnId in metadata for grouping)
|
|
202
|
+
const payload: ToolObservationPayload = {
|
|
203
|
+
toolName: input.tool_name,
|
|
204
|
+
toolInput: maskedInput,
|
|
205
|
+
toolOutput: truncatedOutput,
|
|
206
|
+
durationMs: 0, // Claude Code doesn't provide timing info
|
|
207
|
+
success,
|
|
208
|
+
errorMessage: input.tool_response?.stderr || undefined,
|
|
209
|
+
metadata: {
|
|
210
|
+
...metadata,
|
|
211
|
+
...(turnId ? { turnId } : {})
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// 10. Store observation
|
|
216
|
+
await memoryService.storeToolObservation(input.session_id, payload);
|
|
217
|
+
|
|
218
|
+
// Output empty (hook doesn't return context)
|
|
219
|
+
console.log(JSON.stringify({}));
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
222
|
+
console.error('PostToolUse hook error:', error);
|
|
223
|
+
}
|
|
224
|
+
console.log(JSON.stringify({}));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readStdin(): Promise<string> {
|
|
229
|
+
return new Promise((resolve) => {
|
|
230
|
+
let data = '';
|
|
231
|
+
process.stdin.setEncoding('utf8');
|
|
232
|
+
process.stdin.on('data', (chunk) => {
|
|
233
|
+
data += chunk;
|
|
234
|
+
});
|
|
235
|
+
process.stdin.on('end', () => {
|
|
236
|
+
resolve(data);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type HookMemorySource = 'semantic' | 'keyword' | 'unknown';
|
|
2
|
+
|
|
3
|
+
export interface HookMemoryCandidate {
|
|
4
|
+
type: string;
|
|
5
|
+
content: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
score?: number;
|
|
8
|
+
source?: HookMemorySource;
|
|
9
|
+
fallback?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HookInjectionPolicy {
|
|
13
|
+
minScore: number;
|
|
14
|
+
semanticMinScore: number;
|
|
15
|
+
keywordMinScore: number;
|
|
16
|
+
fallbackKeywordMinScore: number;
|
|
17
|
+
maxMemories: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getHookInjectionPolicy(env: NodeJS.ProcessEnv = process.env): HookInjectionPolicy {
|
|
21
|
+
const minScore = readScoreThreshold(env.CLAUDE_MEMORY_HOOK_INJECTION_MIN_SCORE, 0.65);
|
|
22
|
+
const keywordMinScore = readNumber(
|
|
23
|
+
env.CLAUDE_MEMORY_HOOK_KEYWORD_MIN_SCORE,
|
|
24
|
+
Math.max(minScore, 0.7),
|
|
25
|
+
{ min: 0, max: 1 }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
minScore,
|
|
30
|
+
semanticMinScore: readScoreThreshold(env.CLAUDE_MEMORY_HOOK_SEMANTIC_MIN_SCORE, minScore),
|
|
31
|
+
keywordMinScore,
|
|
32
|
+
fallbackKeywordMinScore: readNumber(
|
|
33
|
+
env.CLAUDE_MEMORY_HOOK_FALLBACK_KEYWORD_MIN_SCORE,
|
|
34
|
+
Math.max(keywordMinScore, 0.8),
|
|
35
|
+
{ min: 0, max: 1 }
|
|
36
|
+
),
|
|
37
|
+
maxMemories: Math.max(1, Math.floor(readNumber(env.CLAUDE_MEMORY_HOOK_MAX_INJECTED, 5)))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function filterHookInjectableMemories(
|
|
42
|
+
candidates: HookMemoryCandidate[],
|
|
43
|
+
policy: HookInjectionPolicy = getHookInjectionPolicy()
|
|
44
|
+
): HookMemoryCandidate[] {
|
|
45
|
+
return candidates
|
|
46
|
+
.map((candidate, index) => ({ candidate, index }))
|
|
47
|
+
.filter(({ candidate }) => isHookInjectableMemory(candidate, policy))
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
const scoreDelta = (b.candidate.score ?? Number.NEGATIVE_INFINITY)
|
|
50
|
+
- (a.candidate.score ?? Number.NEGATIVE_INFINITY);
|
|
51
|
+
return scoreDelta || a.index - b.index;
|
|
52
|
+
})
|
|
53
|
+
.slice(0, policy.maxMemories)
|
|
54
|
+
.map(({ candidate }) => candidate);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function summarizeHookInjectionConfidence(candidates: HookMemoryCandidate[]): 'none' | 'medium' | 'high' {
|
|
58
|
+
const scores = candidates
|
|
59
|
+
.map((candidate) => candidate.score)
|
|
60
|
+
.filter((score): score is number => typeof score === 'number' && Number.isFinite(score));
|
|
61
|
+
if (scores.length === 0) return 'none';
|
|
62
|
+
|
|
63
|
+
const maxScore = Math.max(...scores);
|
|
64
|
+
if (maxScore >= 0.8) return 'high';
|
|
65
|
+
if (maxScore >= 0.65) return 'medium';
|
|
66
|
+
return 'none';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isHookInjectableMemory(candidate: HookMemoryCandidate, policy: HookInjectionPolicy): boolean {
|
|
70
|
+
if (typeof candidate.score !== 'number' || !Number.isFinite(candidate.score)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return candidate.score >= thresholdFor(candidate, policy);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function thresholdFor(candidate: HookMemoryCandidate, policy: HookInjectionPolicy): number {
|
|
78
|
+
if (candidate.source === 'keyword') {
|
|
79
|
+
return candidate.fallback
|
|
80
|
+
? policy.fallbackKeywordMinScore
|
|
81
|
+
: policy.keywordMinScore;
|
|
82
|
+
}
|
|
83
|
+
if (candidate.source === 'semantic') {
|
|
84
|
+
return policy.semanticMinScore;
|
|
85
|
+
}
|
|
86
|
+
return policy.minScore;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readScoreThreshold(value: string | undefined, fallback: number): number {
|
|
90
|
+
return readNumber(value, fallback, { min: 0, max: 1 });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readNumber(
|
|
94
|
+
value: string | undefined,
|
|
95
|
+
fallback: number,
|
|
96
|
+
bounds?: { min?: number; max?: number }
|
|
97
|
+
): number {
|
|
98
|
+
if (value === undefined || value.trim() === '') return fallback;
|
|
99
|
+
const parsed = Number(value);
|
|
100
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
101
|
+
if (bounds?.min !== undefined && parsed < bounds.min) return fallback;
|
|
102
|
+
if (bounds?.max !== undefined && parsed > bounds.max) return fallback;
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as net from 'net';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
7
|
+
interface SemanticRequest {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
topK: number;
|
|
11
|
+
minScore: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SemanticMemory {
|
|
15
|
+
type: string;
|
|
16
|
+
content: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
score?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SemanticDaemonRequest {
|
|
22
|
+
type: 'retrieve';
|
|
23
|
+
sessionId: string;
|
|
24
|
+
prompt: string;
|
|
25
|
+
topK: number;
|
|
26
|
+
minScore: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SemanticDaemonResponse {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
memories?: SemanticMemory[];
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_SOCKET_PATH = path.join(
|
|
36
|
+
os.homedir(),
|
|
37
|
+
'.claude-code',
|
|
38
|
+
'memory',
|
|
39
|
+
'semantic-daemon.sock'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const DAEMON_SOCKET_PATH = process.env.CLAUDE_MEMORY_SEMANTIC_SOCKET || DEFAULT_SOCKET_PATH;
|
|
43
|
+
const DAEMON_START_TIMEOUT_MS = parseInt(process.env.CLAUDE_MEMORY_SEMANTIC_DAEMON_START_MS || '1500');
|
|
44
|
+
|
|
45
|
+
let daemonStartPromise: Promise<void> | null = null;
|
|
46
|
+
|
|
47
|
+
export async function retrieveSemanticMemories(
|
|
48
|
+
request: SemanticRequest,
|
|
49
|
+
timeoutMs: number
|
|
50
|
+
): Promise<SemanticMemory[]> {
|
|
51
|
+
const payload: SemanticDaemonRequest = {
|
|
52
|
+
type: 'retrieve',
|
|
53
|
+
sessionId: request.sessionId,
|
|
54
|
+
prompt: request.prompt,
|
|
55
|
+
topK: request.topK,
|
|
56
|
+
minScore: request.minScore
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return await requestFromDaemon(payload, timeoutMs);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (!isConnectionError(error)) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await ensureDaemonRunning();
|
|
67
|
+
return requestFromDaemon(payload, timeoutMs).catch((retryError) => {
|
|
68
|
+
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
69
|
+
console.error('[semantic-client] retry failed after daemon start:', retryError);
|
|
70
|
+
}
|
|
71
|
+
throw retryError;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requestFromDaemon(
|
|
77
|
+
payload: SemanticDaemonRequest,
|
|
78
|
+
timeoutMs: number
|
|
79
|
+
): Promise<SemanticMemory[]> {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const client = net.createConnection(DAEMON_SOCKET_PATH);
|
|
82
|
+
client.setEncoding('utf8');
|
|
83
|
+
|
|
84
|
+
let settled = false;
|
|
85
|
+
let responseRaw = '';
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
const timeoutError = new Error(`semantic daemon timeout (${timeoutMs}ms)`);
|
|
88
|
+
(timeoutError as NodeJS.ErrnoException).code = 'ETIMEDOUT';
|
|
89
|
+
settle(timeoutError);
|
|
90
|
+
client.destroy();
|
|
91
|
+
}, timeoutMs);
|
|
92
|
+
|
|
93
|
+
const settle = (error?: Error, memories?: SemanticMemory[]) => {
|
|
94
|
+
if (settled) return;
|
|
95
|
+
settled = true;
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
if (error) {
|
|
98
|
+
reject(error);
|
|
99
|
+
} else {
|
|
100
|
+
resolve(memories || []);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
client.on('connect', () => {
|
|
105
|
+
client.end(JSON.stringify(payload));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
client.on('data', (chunk) => {
|
|
109
|
+
responseRaw += chunk;
|
|
110
|
+
if (responseRaw.length > 4 * 1024 * 1024) {
|
|
111
|
+
settle(new Error('semantic daemon response too large'));
|
|
112
|
+
client.destroy();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
client.on('end', () => {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(responseRaw || '{}') as SemanticDaemonResponse;
|
|
119
|
+
if (!parsed.ok) {
|
|
120
|
+
settle(new Error(parsed.error || 'semantic daemon error'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
settle(undefined, parsed.memories || []);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
settle(error as Error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
client.on('error', (error) => {
|
|
130
|
+
settle(error as Error);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function ensureDaemonRunning(): Promise<void> {
|
|
136
|
+
if (daemonStartPromise) {
|
|
137
|
+
return daemonStartPromise;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
daemonStartPromise = (async () => {
|
|
141
|
+
if (await canConnect()) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const daemonScriptPath = getDaemonScriptPath();
|
|
146
|
+
if (!fs.existsSync(daemonScriptPath)) {
|
|
147
|
+
throw new Error(`semantic daemon script not found: ${daemonScriptPath}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const daemonDir = path.dirname(DAEMON_SOCKET_PATH);
|
|
151
|
+
if (!fs.existsSync(daemonDir)) {
|
|
152
|
+
fs.mkdirSync(daemonDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const child = spawn(process.execPath, [daemonScriptPath], {
|
|
156
|
+
detached: true,
|
|
157
|
+
stdio: 'ignore',
|
|
158
|
+
env: process.env
|
|
159
|
+
});
|
|
160
|
+
child.unref();
|
|
161
|
+
|
|
162
|
+
const startDeadline = Date.now() + DAEMON_START_TIMEOUT_MS;
|
|
163
|
+
while (Date.now() < startDeadline) {
|
|
164
|
+
if (await canConnect()) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await sleep(60);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
throw new Error(`semantic daemon start timeout (${DAEMON_START_TIMEOUT_MS}ms)`);
|
|
171
|
+
})();
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await daemonStartPromise;
|
|
175
|
+
} finally {
|
|
176
|
+
daemonStartPromise = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getDaemonScriptPath(): string {
|
|
181
|
+
return path.join(path.dirname(new URL(import.meta.url).pathname), 'semantic-daemon.js');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function canConnect(): Promise<boolean> {
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
let settled = false;
|
|
187
|
+
const client = net.createConnection(DAEMON_SOCKET_PATH);
|
|
188
|
+
const finalize = (ok: boolean) => {
|
|
189
|
+
if (settled) return;
|
|
190
|
+
settled = true;
|
|
191
|
+
client.destroy();
|
|
192
|
+
resolve(ok);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
client.on('connect', () => finalize(true));
|
|
196
|
+
client.on('error', () => finalize(false));
|
|
197
|
+
setTimeout(() => finalize(false), 120).unref();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isConnectionError(error: unknown): boolean {
|
|
202
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
203
|
+
return code === 'ENOENT' || code === 'ECONNREFUSED' || code === 'EPIPE' || code === 'ECONNRESET';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function sleep(ms: number): Promise<void> {
|
|
207
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
208
|
+
}
|
|
209
|
+
|