claude-memory-layer 1.0.31 → 1.0.33
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 +9 -2
- package/dist/cli/index.js +1110 -72
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +414 -25
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +416 -27
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +416 -27
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +416 -27
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +416 -27
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +416 -27
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +504 -34
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +416 -27
- package/dist/index.js.map +2 -2
- package/dist/mcp/index.js +407 -32
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +850 -44
- package/dist/server/api/index.js.map +3 -3
- package/dist/server/index.js +1073 -64
- package/dist/server/index.js.map +3 -3
- package/dist/services/memory-service.js +416 -27
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/assets/js/bootstrap.js +2 -0
- package/dist/ui/assets/js/overview.js +166 -3
- package/dist/ui/assets/js/state.js +3 -0
- package/dist/ui/index.html +20 -0
- package/dist/ui/style.css +193 -0
- package/package.json +15 -2
- package/scripts/postinstall-embedding-backend.cjs +16 -12
- package/AGENTS.md +0 -71
- package/CLAUDE.md +0 -30
- package/HANDOFF.md +0 -92
- package/Memo.txt +0 -558
- package/benchmarks/replay/anonymized-real-sessions.json +0 -48
- package/config/kpi-thresholds.json +0 -7
- package/context.md +0 -636
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +0 -627
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +0 -440
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +0 -271
- package/docs/MEMORY_USEFULNESS_AUDIT.md +0 -371
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +0 -80
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +0 -333
- package/docs/MEMU_ADOPTION.md +0 -40
- package/docs/OPERATIONS.md +0 -18
- package/docs/PRODUCT_VALIDATION_MATRIX.md +0 -82
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +0 -421
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +0 -501
- package/docs/REFACTORING_PLAN_THIN_CORE.md +0 -414
- package/docs/REFERENCE_PROJECT_ANALYSES.md +0 -25
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +0 -452
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +0 -446
- package/docs/architecture/comparison-index.md +0 -47
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +0 -46
- package/plan.md +0 -1642
- package/scripts/build.ts +0 -159
- package/scripts/bump-patch-version.sh +0 -18
- package/scripts/delete-unknown-projects.js +0 -154
- package/scripts/fix-sync-gap.js +0 -32
- package/scripts/generate-session-qrels.ts +0 -126
- package/scripts/heartbeat-memory-orchestrator.sh +0 -28
- package/scripts/replay-retrieval-benchmark.ts +0 -69
- package/scripts/report-sync-gap.js +0 -26
- package/scripts/review-queue-auto-resolve.js +0 -21
- package/scripts/sync-gap-auto-heal.sh +0 -17
- package/spec.md +0 -624
- package/specs/20260207-dashboard-upgrade/context.md +0 -38
- package/specs/20260207-dashboard-upgrade/spec.md +0 -96
- package/specs/citations-system/context.md +0 -243
- package/specs/citations-system/plan.md +0 -495
- package/specs/citations-system/spec.md +0 -371
- package/specs/endless-mode/context.md +0 -305
- package/specs/endless-mode/plan.md +0 -620
- package/specs/endless-mode/spec.md +0 -455
- package/specs/entity-edge-model/context.md +0 -401
- package/specs/entity-edge-model/plan.md +0 -459
- package/specs/entity-edge-model/spec.md +0 -391
- package/specs/evidence-aligner-v2/context.md +0 -401
- package/specs/evidence-aligner-v2/plan.md +0 -303
- package/specs/evidence-aligner-v2/spec.md +0 -312
- package/specs/mcp-desktop-integration/context.md +0 -278
- package/specs/mcp-desktop-integration/plan.md +0 -550
- package/specs/mcp-desktop-integration/spec.md +0 -494
- package/specs/memory-utilization-improvements/context.md +0 -145
- package/specs/memory-utilization-improvements/plan.md +0 -361
- package/specs/memory-utilization-improvements/spec.md +0 -361
- package/specs/post-tool-use-hook/context.md +0 -319
- package/specs/post-tool-use-hook/plan.md +0 -469
- package/specs/post-tool-use-hook/spec.md +0 -364
- package/specs/private-tags/context.md +0 -288
- package/specs/private-tags/plan.md +0 -412
- package/specs/private-tags/spec.md +0 -345
- package/specs/progressive-disclosure/context.md +0 -346
- package/specs/progressive-disclosure/plan.md +0 -663
- package/specs/progressive-disclosure/spec.md +0 -415
- package/specs/selective-tool-observation/context.md +0 -100
- package/specs/selective-tool-observation/plan.md +0 -158
- package/specs/selective-tool-observation/spec.md +0 -127
- package/specs/task-entity-system/context.md +0 -297
- package/specs/task-entity-system/plan.md +0 -301
- package/specs/task-entity-system/spec.md +0 -314
- package/specs/thin-core-refactor/context.md +0 -275
- package/specs/thin-core-refactor/plan.md +0 -536
- package/specs/thin-core-refactor/spec.md +0 -465
- package/specs/vector-outbox-v2/context.md +0 -470
- package/specs/vector-outbox-v2/plan.md +0 -562
- package/specs/vector-outbox-v2/spec.md +0 -466
- package/specs/web-viewer-ui/context.md +0 -384
- package/specs/web-viewer-ui/plan.md +0 -797
- package/specs/web-viewer-ui/spec.md +0 -516
- package/src/adapters/claude/capture/index.ts +0 -3
- package/src/adapters/claude/context/index.ts +0 -3
- package/src/adapters/claude/hooks/index.ts +0 -21
- package/src/adapters/claude/hooks/post-tool-use.ts +0 -239
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +0 -104
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +0 -209
- package/src/adapters/claude/hooks/semantic-daemon.ts +0 -283
- package/src/adapters/claude/hooks/session-end.ts +0 -59
- package/src/adapters/claude/hooks/session-start.ts +0 -73
- package/src/adapters/claude/hooks/stop.ts +0 -128
- package/src/adapters/claude/hooks/user-prompt-submit.ts +0 -361
- package/src/adapters/claude/index.ts +0 -4
- package/src/adapters/claude/transcript/index.ts +0 -4
- package/src/adapters/claude/transcript/transcript-reader.ts +0 -57
- package/src/adapters/claude/transcript/turn-reconstructor.ts +0 -65
- package/src/apps/cli/claude-settings-hooks.ts +0 -138
- package/src/apps/cli/codex-import-runner.ts +0 -125
- package/src/apps/cli/codex-validation-output.ts +0 -95
- package/src/apps/cli/hermes-import-runner.ts +0 -130
- package/src/apps/cli/hermes-validation-output.ts +0 -91
- package/src/apps/cli/index.ts +0 -1735
- package/src/apps/cli/mcp-install.ts +0 -106
- package/src/apps/cli/retrieval-disclosure-output.ts +0 -196
- package/src/apps/dashboard/assets/js/bootstrap.js +0 -244
- package/src/apps/dashboard/assets/js/chat.js +0 -373
- package/src/apps/dashboard/assets/js/disclosure.js +0 -232
- package/src/apps/dashboard/assets/js/modals.js +0 -298
- package/src/apps/dashboard/assets/js/overview.js +0 -655
- package/src/apps/dashboard/assets/js/state.js +0 -72
- package/src/apps/dashboard/assets/js/views.js +0 -468
- package/src/apps/dashboard/index.html +0 -543
- package/src/apps/dashboard/index.ts +0 -3
- package/src/apps/dashboard/style.css +0 -1750
- package/src/apps/index.ts +0 -5
- package/src/apps/server/api/chat.ts +0 -244
- package/src/apps/server/api/citations.ts +0 -105
- package/src/apps/server/api/events.ts +0 -137
- package/src/apps/server/api/health.ts +0 -53
- package/src/apps/server/api/index.ts +0 -26
- package/src/apps/server/api/projects.ts +0 -74
- package/src/apps/server/api/search.ts +0 -184
- package/src/apps/server/api/sessions.ts +0 -115
- package/src/apps/server/api/stats.ts +0 -723
- package/src/apps/server/api/turns.ts +0 -143
- package/src/apps/server/api/utils.ts +0 -65
- package/src/apps/server/index.ts +0 -111
- package/src/cli/index.ts +0 -3
- package/src/cli/retrieval-disclosure-output.ts +0 -2
- package/src/compat/index.ts +0 -5
- package/src/core/canonical-key.ts +0 -186
- package/src/core/citation-generator.ts +0 -63
- package/src/core/consolidated-store.ts +0 -356
- package/src/core/consolidation-worker.ts +0 -493
- package/src/core/context-formatter.ts +0 -276
- package/src/core/continuity-manager.ts +0 -341
- package/src/core/db-wrapper.ts +0 -64
- package/src/core/derive/fact-deriver.ts +0 -170
- package/src/core/derive/index.ts +0 -2
- package/src/core/derive/summary-deriver.ts +0 -76
- package/src/core/edge-repo.ts +0 -333
- package/src/core/embedder.ts +0 -4
- package/src/core/engine/embedding-maintenance-service.ts +0 -187
- package/src/core/engine/endless-memory-services.ts +0 -4
- package/src/core/engine/index.ts +0 -19
- package/src/core/engine/memory-engine-services.ts +0 -170
- package/src/core/engine/memory-ingest-service.ts +0 -317
- package/src/core/engine/memory-query-service.ts +0 -173
- package/src/core/engine/memory-runtime-service.ts +0 -162
- package/src/core/engine/memory-service-composition.ts +0 -231
- package/src/core/engine/retrieval-analytics-service.ts +0 -181
- package/src/core/engine/retrieval-disclosure-service.ts +0 -420
- package/src/core/engine/retrieval-orchestrator.ts +0 -377
- package/src/core/engine/retrieval-services.ts +0 -176
- package/src/core/engine/shared-memory-services.ts +0 -4
- package/src/core/entity-repo.ts +0 -349
- package/src/core/event-store.ts +0 -779
- package/src/core/evidence-aligner.ts +0 -635
- package/src/core/external-market-context.ts +0 -582
- package/src/core/graduation-worker.ts +0 -171
- package/src/core/graduation.ts +0 -377
- package/src/core/index.ts +0 -64
- package/src/core/ingest-interceptor.ts +0 -80
- package/src/core/markdown-mirror.ts +0 -70
- package/src/core/matcher.ts +0 -208
- package/src/core/md-mirror.ts +0 -92
- package/src/core/metadata-extractor.ts +0 -203
- package/src/core/model/memory-fact.ts +0 -30
- package/src/core/model/memory-rule.ts +0 -14
- package/src/core/model/memory-summary.ts +0 -21
- package/src/core/model/raw-event.ts +0 -28
- package/src/core/model/retrieval-result.ts +0 -35
- package/src/core/mongo-sync-config.ts +0 -165
- package/src/core/mongo-sync-worker.ts +0 -381
- package/src/core/privacy/filter.ts +0 -190
- package/src/core/privacy/index.ts +0 -20
- package/src/core/privacy/tag-parser.ts +0 -145
- package/src/core/product-validation-matrix.ts +0 -314
- package/src/core/progressive-retriever.ts +0 -414
- package/src/core/registry/project-path.ts +0 -54
- package/src/core/registry/session-registry.ts +0 -69
- package/src/core/replay-evaluator.ts +0 -625
- package/src/core/retrieval-benchmark.ts +0 -117
- package/src/core/retrieval-quality.ts +0 -109
- package/src/core/retriever.ts +0 -800
- package/src/core/session-qrels.ts +0 -360
- package/src/core/shared-event-store.ts +0 -114
- package/src/core/shared-promoter.ts +0 -249
- package/src/core/shared-store.ts +0 -289
- package/src/core/shared-vector-store.ts +0 -203
- package/src/core/sqlite-event-store.ts +0 -1846
- package/src/core/sqlite-wrapper.ts +0 -116
- package/src/core/sync-worker.ts +0 -228
- package/src/core/tag-taxonomy.ts +0 -51
- package/src/core/task/blocker-resolver.ts +0 -333
- package/src/core/task/index.ts +0 -9
- package/src/core/task/task-matcher.ts +0 -240
- package/src/core/task/task-projector.ts +0 -358
- package/src/core/task/task-resolver.ts +0 -421
- package/src/core/turn-state.ts +0 -207
- package/src/core/types.ts +0 -952
- package/src/core/vector-outbox.ts +0 -299
- package/src/core/vector-store.ts +0 -231
- package/src/core/vector-worker.ts +0 -521
- package/src/core/working-set-store.ts +0 -257
- package/src/extensions/endless-memory/endless-memory-services.ts +0 -350
- package/src/extensions/endless-memory/index.ts +0 -1
- package/src/extensions/index.ts +0 -5
- package/src/extensions/mcp/handlers.ts +0 -960
- package/src/extensions/mcp/index.ts +0 -48
- package/src/extensions/mcp/tools.ts +0 -252
- package/src/extensions/shared-memory/index.ts +0 -1
- package/src/extensions/shared-memory/shared-memory-services.ts +0 -211
- package/src/extensions/vector/embedder.ts +0 -234
- package/src/extensions/vector/index.ts +0 -1
- package/src/hooks/post-tool-use.ts +0 -9
- package/src/hooks/semantic-daemon-client.ts +0 -1
- package/src/hooks/semantic-daemon.ts +0 -11
- package/src/hooks/session-end.ts +0 -9
- package/src/hooks/session-start.ts +0 -9
- package/src/hooks/stop.ts +0 -9
- package/src/hooks/user-prompt-submit.ts +0 -9
- package/src/index.ts +0 -13
- package/src/mcp/handlers.ts +0 -2
- package/src/mcp/index.ts +0 -4
- package/src/mcp/tools.ts +0 -2
- package/src/server/api/chat.ts +0 -2
- package/src/server/api/citations.ts +0 -2
- package/src/server/api/events.ts +0 -2
- package/src/server/api/health.ts +0 -2
- package/src/server/api/index.ts +0 -2
- package/src/server/api/projects.ts +0 -2
- package/src/server/api/search.ts +0 -2
- package/src/server/api/sessions.ts +0 -2
- package/src/server/api/stats.ts +0 -2
- package/src/server/api/turns.ts +0 -2
- package/src/server/api/utils.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/services/bootstrap-organizer.ts +0 -463
- package/src/services/codex-session-history-importer.ts +0 -966
- package/src/services/hermes-session-history-importer.ts +0 -733
- package/src/services/memory-service-config.ts +0 -36
- package/src/services/memory-service-registry.ts +0 -150
- package/src/services/memory-service.ts +0 -688
- package/src/services/session-history-importer.ts +0 -629
- package/tests/README.md +0 -23
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +0 -54
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +0 -98
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +0 -99
- package/tests/apps/app-layer-boundary.test.ts +0 -48
- package/tests/apps/claude-settings-hooks.test.ts +0 -107
- package/tests/apps/cli-disclosure-output.test.ts +0 -212
- package/tests/apps/codex-import-runner.test.ts +0 -99
- package/tests/apps/codex-validation-output.test.ts +0 -100
- package/tests/apps/hermes-import-runner.test.ts +0 -99
- package/tests/apps/mcp-install-command.test.ts +0 -59
- package/tests/apps/package-build-entrypoints.test.ts +0 -30
- package/tests/apps/postinstall-embedding-backend.test.ts +0 -185
- package/tests/apps/search-api-disclosure.test.ts +0 -162
- package/tests/apps/stats-api-lightweight.test.ts +0 -67
- package/tests/apps/ui-disclosure-output.test.ts +0 -140
- package/tests/core/bootstrap-organizer.test.ts +0 -111
- package/tests/core/canonical-key.test.ts +0 -101
- package/tests/core/codex-session-history-importer-validation.test.ts +0 -185
- package/tests/core/consolidation-worker.test.ts +0 -75
- package/tests/core/embedding-maintenance-service.test.ts +0 -282
- package/tests/core/evidence-aligner.test.ts +0 -152
- package/tests/core/external-market-context.test.ts +0 -209
- package/tests/core/fact-deriver.test.ts +0 -79
- package/tests/core/hermes-session-history-importer-validation.test.ts +0 -609
- package/tests/core/ingest-interceptor.test.ts +0 -38
- package/tests/core/markdown-mirror.test.ts +0 -85
- package/tests/core/matcher.test.ts +0 -112
- package/tests/core/md-mirror.test.ts +0 -50
- package/tests/core/memory-engine-services.test.ts +0 -240
- package/tests/core/memory-ingest-service.test.ts +0 -296
- package/tests/core/memory-query-service.test.ts +0 -129
- package/tests/core/memory-runtime-service.test.ts +0 -201
- package/tests/core/memory-service-composition.test.ts +0 -192
- package/tests/core/memory-service-config.test.ts +0 -41
- package/tests/core/memory-service-facade.test.ts +0 -30
- package/tests/core/memory-service-registry.test.ts +0 -206
- package/tests/core/product-validation-matrix.test.ts +0 -61
- package/tests/core/project-registry.test.ts +0 -78
- package/tests/core/replay-evaluator.test.ts +0 -181
- package/tests/core/retrieval-analytics-service.test.ts +0 -210
- package/tests/core/retrieval-benchmark.test.ts +0 -93
- package/tests/core/retrieval-disclosure-service.test.ts +0 -264
- package/tests/core/retrieval-orchestrator.test.ts +0 -403
- package/tests/core/retrieval-quality.test.ts +0 -31
- package/tests/core/retrieval-services.test.ts +0 -185
- package/tests/core/retriever-fallback-chain.test.ts +0 -223
- package/tests/core/retriever-strategy-scope.test.ts +0 -164
- package/tests/core/retriever.memu-adoption.test.ts +0 -122
- package/tests/core/session-history-importer-filter.test.ts +0 -78
- package/tests/core/session-qrels.test.ts +0 -250
- package/tests/core/sqlite-event-store-replication.test.ts +0 -127
- package/tests/core/summary-deriver.test.ts +0 -66
- package/tests/extensions/embedder-warning-suppression.test.ts +0 -84
- package/tests/extensions/endless-memory-extension-boundary.test.ts +0 -17
- package/tests/extensions/endless-memory-services.test.ts +0 -325
- package/tests/extensions/mcp-context-tools.test.ts +0 -905
- package/tests/extensions/mcp-extension-boundary.test.ts +0 -21
- package/tests/extensions/mcp-package-build.test.ts +0 -22
- package/tests/extensions/mcp-project-aware-tools.test.ts +0 -102
- package/tests/extensions/shared-memory-extension-boundary.test.ts +0 -24
- package/tests/extensions/shared-memory-services.test.ts +0 -309
- package/tests/extensions/vector-extension-boundary.test.ts +0 -21
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -15
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task Resolver - Process extracted task entries and emit task events
|
|
3
|
-
* AXIOMMIND: Task state via event fold, no direct updates
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { dbRun, dbAll, type Database } from '../db-wrapper.js';
|
|
7
|
-
import { randomUUID } from 'crypto';
|
|
8
|
-
import type {
|
|
9
|
-
Entity,
|
|
10
|
-
TaskStatus,
|
|
11
|
-
TaskPriority,
|
|
12
|
-
BlockerMode
|
|
13
|
-
} from '../types.js';
|
|
14
|
-
import { makeEntityCanonicalKey, makeTaskEventDedupeKey } from '../canonical-key.js';
|
|
15
|
-
import { TaskMatcher } from './task-matcher.js';
|
|
16
|
-
import { BlockerResolver } from './blocker-resolver.js';
|
|
17
|
-
|
|
18
|
-
export interface ExtractedTask {
|
|
19
|
-
title: string;
|
|
20
|
-
status?: TaskStatus;
|
|
21
|
-
priority?: TaskPriority;
|
|
22
|
-
blockedBy?: string[];
|
|
23
|
-
description?: string;
|
|
24
|
-
project?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface TaskResolverConfig {
|
|
28
|
-
sessionId: string;
|
|
29
|
-
project?: string;
|
|
30
|
-
evidenceAligned?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Valid status transitions
|
|
34
|
-
const VALID_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {
|
|
35
|
-
pending: ['in_progress', 'cancelled'],
|
|
36
|
-
in_progress: ['blocked', 'done', 'cancelled'],
|
|
37
|
-
blocked: ['in_progress', 'done', 'cancelled'],
|
|
38
|
-
done: [], // Terminal state
|
|
39
|
-
cancelled: [] // Terminal state
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export class TaskResolver {
|
|
43
|
-
private taskMatcher: TaskMatcher;
|
|
44
|
-
private blockerResolver: BlockerResolver;
|
|
45
|
-
|
|
46
|
-
constructor(
|
|
47
|
-
private db: Database,
|
|
48
|
-
private config: TaskResolverConfig
|
|
49
|
-
) {
|
|
50
|
-
this.taskMatcher = new TaskMatcher(db);
|
|
51
|
-
this.blockerResolver = new BlockerResolver(db, { project: config.project });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Process extracted task entry
|
|
56
|
-
* 1. Find or create task entity
|
|
57
|
-
* 2. Emit status/priority change events if needed
|
|
58
|
-
* 3. Process blockers
|
|
59
|
-
*/
|
|
60
|
-
async processTask(extracted: ExtractedTask, sourceEntryId?: string): Promise<{
|
|
61
|
-
taskId: string;
|
|
62
|
-
isNew: boolean;
|
|
63
|
-
events: string[];
|
|
64
|
-
}> {
|
|
65
|
-
const events: string[] = [];
|
|
66
|
-
|
|
67
|
-
// Step 1: Find existing task or create new
|
|
68
|
-
const { task, isNew, eventId: createEventId } = await this.findOrCreateTask(extracted);
|
|
69
|
-
|
|
70
|
-
if (isNew && createEventId) {
|
|
71
|
-
events.push(createEventId);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Step 2: Handle status changes
|
|
75
|
-
if (extracted.status) {
|
|
76
|
-
const statusEvent = await this.handleStatusChange(task, extracted.status);
|
|
77
|
-
if (statusEvent) {
|
|
78
|
-
events.push(statusEvent);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Step 3: Handle priority changes
|
|
83
|
-
if (extracted.priority) {
|
|
84
|
-
const priorityEvent = await this.handlePriorityChange(task, extracted.priority);
|
|
85
|
-
if (priorityEvent) {
|
|
86
|
-
events.push(priorityEvent);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Step 4: Handle blockers
|
|
91
|
-
if (extracted.blockedBy && extracted.blockedBy.length > 0) {
|
|
92
|
-
const blockerEvent = await this.handleBlockers(
|
|
93
|
-
task,
|
|
94
|
-
extracted.blockedBy,
|
|
95
|
-
sourceEntryId
|
|
96
|
-
);
|
|
97
|
-
if (blockerEvent) {
|
|
98
|
-
events.push(blockerEvent);
|
|
99
|
-
}
|
|
100
|
-
} else if (extracted.status === 'blocked') {
|
|
101
|
-
// Status is blocked but no blockers provided
|
|
102
|
-
// Create unknown placeholder
|
|
103
|
-
const blockerEvent = await this.handleUnknownBlocker(task);
|
|
104
|
-
if (blockerEvent) {
|
|
105
|
-
events.push(blockerEvent);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
taskId: task.entityId,
|
|
111
|
-
isNew,
|
|
112
|
-
events
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Find existing task or create new one
|
|
118
|
-
*/
|
|
119
|
-
private async findOrCreateTask(extracted: ExtractedTask): Promise<{
|
|
120
|
-
task: Entity;
|
|
121
|
-
isNew: boolean;
|
|
122
|
-
eventId?: string;
|
|
123
|
-
}> {
|
|
124
|
-
// Try to find existing task
|
|
125
|
-
const matchResult = await this.taskMatcher.match(extracted.title, extracted.project);
|
|
126
|
-
|
|
127
|
-
if (matchResult.confidence === 'high' && matchResult.match) {
|
|
128
|
-
return {
|
|
129
|
-
task: matchResult.match,
|
|
130
|
-
isNew: false
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Create new task
|
|
135
|
-
const taskId = randomUUID();
|
|
136
|
-
const canonicalKey = makeEntityCanonicalKey('task', extracted.title, {
|
|
137
|
-
project: extracted.project
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Correct initial status: never start as 'done'
|
|
141
|
-
let initialStatus = extracted.status ?? 'pending';
|
|
142
|
-
if (initialStatus === 'done') {
|
|
143
|
-
initialStatus = 'in_progress'; // Correct: can't start as done
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const now = new Date();
|
|
147
|
-
|
|
148
|
-
const currentJson = {
|
|
149
|
-
status: initialStatus,
|
|
150
|
-
priority: extracted.priority ?? 'medium',
|
|
151
|
-
description: extracted.description,
|
|
152
|
-
project: extracted.project ?? this.config.project
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Insert entity
|
|
156
|
-
await dbRun(
|
|
157
|
-
this.db,
|
|
158
|
-
`INSERT INTO entities (
|
|
159
|
-
entity_id, entity_type, canonical_key, title, stage, status,
|
|
160
|
-
current_json, title_norm, search_text, created_at, updated_at
|
|
161
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
162
|
-
[
|
|
163
|
-
taskId,
|
|
164
|
-
'task',
|
|
165
|
-
canonicalKey,
|
|
166
|
-
extracted.title,
|
|
167
|
-
'raw',
|
|
168
|
-
'active',
|
|
169
|
-
JSON.stringify(currentJson),
|
|
170
|
-
extracted.title.toLowerCase().trim(),
|
|
171
|
-
`${extracted.title} ${extracted.description ?? ''}`,
|
|
172
|
-
now.toISOString(),
|
|
173
|
-
now.toISOString()
|
|
174
|
-
]
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// Create alias
|
|
178
|
-
await dbRun(
|
|
179
|
-
this.db,
|
|
180
|
-
`INSERT INTO entity_aliases (entity_type, canonical_key, entity_id, is_primary)
|
|
181
|
-
VALUES (?, ?, ?, TRUE)
|
|
182
|
-
ON CONFLICT (entity_type, canonical_key) DO NOTHING`,
|
|
183
|
-
['task', canonicalKey, taskId]
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// Emit task_created event
|
|
187
|
-
const eventId = await this.emitTaskEvent('task_created', {
|
|
188
|
-
taskId,
|
|
189
|
-
title: extracted.title,
|
|
190
|
-
canonicalKey,
|
|
191
|
-
initialStatus,
|
|
192
|
-
priority: extracted.priority ?? 'medium',
|
|
193
|
-
description: extracted.description,
|
|
194
|
-
project: extracted.project ?? this.config.project
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// Return created entity
|
|
198
|
-
const task: Entity = {
|
|
199
|
-
entityId: taskId,
|
|
200
|
-
entityType: 'task',
|
|
201
|
-
canonicalKey,
|
|
202
|
-
title: extracted.title,
|
|
203
|
-
stage: 'raw',
|
|
204
|
-
status: 'active',
|
|
205
|
-
currentJson,
|
|
206
|
-
titleNorm: extracted.title.toLowerCase().trim(),
|
|
207
|
-
searchText: `${extracted.title} ${extracted.description ?? ''}`,
|
|
208
|
-
createdAt: now,
|
|
209
|
-
updatedAt: now
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return { task, isNew: true, eventId };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Handle task status change
|
|
217
|
-
*/
|
|
218
|
-
private async handleStatusChange(
|
|
219
|
-
task: Entity,
|
|
220
|
-
newStatus: TaskStatus
|
|
221
|
-
): Promise<string | null> {
|
|
222
|
-
const currentJson = task.currentJson as { status: TaskStatus };
|
|
223
|
-
const currentStatus = currentJson.status;
|
|
224
|
-
|
|
225
|
-
if (currentStatus === newStatus) {
|
|
226
|
-
return null; // No change
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Validate transition
|
|
230
|
-
const validNextStates = VALID_TRANSITIONS[currentStatus] ?? [];
|
|
231
|
-
if (!validNextStates.includes(newStatus)) {
|
|
232
|
-
// Invalid transition - emit rejection event
|
|
233
|
-
return this.emitTaskEvent('task_transition_rejected', {
|
|
234
|
-
taskId: task.entityId,
|
|
235
|
-
fromStatus: currentStatus,
|
|
236
|
-
toStatus: newStatus,
|
|
237
|
-
reason: `Invalid transition from ${currentStatus} to ${newStatus}`
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Emit status change event
|
|
242
|
-
const eventId = await this.emitTaskEvent('task_status_changed', {
|
|
243
|
-
taskId: task.entityId,
|
|
244
|
-
fromStatus: currentStatus,
|
|
245
|
-
toStatus: newStatus
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Update entity (projector will do this, but we update for immediate consistency)
|
|
249
|
-
await dbRun(
|
|
250
|
-
this.db,
|
|
251
|
-
`UPDATE entities
|
|
252
|
-
SET current_json = json_set(current_json, '$.status', ?),
|
|
253
|
-
updated_at = ?
|
|
254
|
-
WHERE entity_id = ?`,
|
|
255
|
-
[newStatus, new Date().toISOString(), task.entityId]
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
return eventId;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Handle task priority change
|
|
263
|
-
*/
|
|
264
|
-
private async handlePriorityChange(
|
|
265
|
-
task: Entity,
|
|
266
|
-
newPriority: TaskPriority
|
|
267
|
-
): Promise<string | null> {
|
|
268
|
-
const currentJson = task.currentJson as { priority?: TaskPriority };
|
|
269
|
-
const currentPriority = currentJson.priority ?? 'medium';
|
|
270
|
-
|
|
271
|
-
if (currentPriority === newPriority) {
|
|
272
|
-
return null; // No change
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Emit priority change event
|
|
276
|
-
const eventId = await this.emitTaskEvent('task_priority_changed', {
|
|
277
|
-
taskId: task.entityId,
|
|
278
|
-
fromPriority: currentPriority,
|
|
279
|
-
toPriority: newPriority
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Update entity
|
|
283
|
-
await dbRun(
|
|
284
|
-
this.db,
|
|
285
|
-
`UPDATE entities
|
|
286
|
-
SET current_json = json_set(current_json, '$.priority', ?),
|
|
287
|
-
updated_at = ?
|
|
288
|
-
WHERE entity_id = ?`,
|
|
289
|
-
[newPriority, new Date().toISOString(), task.entityId]
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
return eventId;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Handle blockers
|
|
297
|
-
*/
|
|
298
|
-
private async handleBlockers(
|
|
299
|
-
task: Entity,
|
|
300
|
-
blockedByTexts: string[],
|
|
301
|
-
sourceEntryId?: string
|
|
302
|
-
): Promise<string | null> {
|
|
303
|
-
// Resolve blocker texts to entity refs
|
|
304
|
-
const blockerRefs = await this.blockerResolver.resolveBlockers(blockedByTexts);
|
|
305
|
-
|
|
306
|
-
// Determine mode based on evidence alignment
|
|
307
|
-
const mode: BlockerMode = this.config.evidenceAligned ? 'replace' : 'suggest';
|
|
308
|
-
|
|
309
|
-
// Emit task_blockers_set event
|
|
310
|
-
const eventId = await this.emitTaskEvent('task_blockers_set', {
|
|
311
|
-
taskId: task.entityId,
|
|
312
|
-
mode,
|
|
313
|
-
blockers: blockerRefs,
|
|
314
|
-
sourceEntryId
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
return eventId;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Handle unknown blocker (status=blocked but no blockedBy)
|
|
322
|
-
*/
|
|
323
|
-
private async handleUnknownBlocker(task: Entity): Promise<string | null> {
|
|
324
|
-
const placeholderRef = await this.blockerResolver.createUnknownPlaceholder(task.title);
|
|
325
|
-
|
|
326
|
-
const eventId = await this.emitTaskEvent('task_blockers_set', {
|
|
327
|
-
taskId: task.entityId,
|
|
328
|
-
mode: 'suggest' as BlockerMode,
|
|
329
|
-
blockers: [placeholderRef]
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
return eventId;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Emit task event to events table
|
|
337
|
-
*/
|
|
338
|
-
private async emitTaskEvent(
|
|
339
|
-
eventType: string,
|
|
340
|
-
payload: Record<string, unknown>
|
|
341
|
-
): Promise<string> {
|
|
342
|
-
const eventId = randomUUID();
|
|
343
|
-
const now = new Date();
|
|
344
|
-
|
|
345
|
-
// Generate dedupe key
|
|
346
|
-
const dedupeKey = makeTaskEventDedupeKey(
|
|
347
|
-
eventType,
|
|
348
|
-
payload.taskId as string,
|
|
349
|
-
this.config.sessionId,
|
|
350
|
-
JSON.stringify(payload)
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
// Check for duplicate
|
|
354
|
-
const existing = await dbAll<{ event_id: string }>(
|
|
355
|
-
this.db,
|
|
356
|
-
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
357
|
-
[dedupeKey]
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
if (existing.length > 0) {
|
|
361
|
-
return existing[0].event_id; // Return existing event ID
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Insert event
|
|
365
|
-
await dbRun(
|
|
366
|
-
this.db,
|
|
367
|
-
`INSERT INTO events (
|
|
368
|
-
id, event_type, session_id, timestamp, content,
|
|
369
|
-
canonical_key, dedupe_key, metadata
|
|
370
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
371
|
-
[
|
|
372
|
-
eventId,
|
|
373
|
-
eventType,
|
|
374
|
-
this.config.sessionId,
|
|
375
|
-
now.toISOString(),
|
|
376
|
-
JSON.stringify(payload),
|
|
377
|
-
`task_event:${eventType}:${payload.taskId}`,
|
|
378
|
-
dedupeKey,
|
|
379
|
-
JSON.stringify({ source: 'task_resolver' })
|
|
380
|
-
]
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
// Insert dedup record
|
|
384
|
-
await dbRun(
|
|
385
|
-
this.db,
|
|
386
|
-
`INSERT INTO event_dedup (dedupe_key, event_id)
|
|
387
|
-
VALUES (?, ?)
|
|
388
|
-
ON CONFLICT DO NOTHING`,
|
|
389
|
-
[dedupeKey, eventId]
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
return eventId;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Resolve condition to task (when condition is identified as existing task)
|
|
397
|
-
*/
|
|
398
|
-
async resolveConditionToTask(
|
|
399
|
-
conditionId: string,
|
|
400
|
-
taskId: string
|
|
401
|
-
): Promise<string> {
|
|
402
|
-
const eventId = await this.emitTaskEvent('condition_resolved_to', {
|
|
403
|
-
conditionId,
|
|
404
|
-
resolvedTo: {
|
|
405
|
-
kind: 'task',
|
|
406
|
-
entityId: taskId
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// Create resolves_to edge
|
|
411
|
-
await dbRun(
|
|
412
|
-
this.db,
|
|
413
|
-
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
|
|
414
|
-
VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
|
|
415
|
-
ON CONFLICT DO NOTHING`,
|
|
416
|
-
[randomUUID(), conditionId, taskId, JSON.stringify({ resolved_at: new Date().toISOString() })]
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
return eventId;
|
|
420
|
-
}
|
|
421
|
-
}
|
package/src/core/turn-state.ts
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turn State Management
|
|
3
|
-
*
|
|
4
|
-
* Manages a per-session turn_id state file that links events within a conversation turn.
|
|
5
|
-
*
|
|
6
|
-
* Flow:
|
|
7
|
-
* 1. UserPromptSubmit generates a new turn_id and writes it to a state file
|
|
8
|
-
* 2. PostToolUse reads the current turn_id to associate tool observations with the turn
|
|
9
|
-
* 3. Stop reads the turn_id to associate agent responses, then cleans up
|
|
10
|
-
*
|
|
11
|
-
* State file location: ~/.claude-code/memory/.turn-state-{session_id}.json
|
|
12
|
-
*
|
|
13
|
-
* The file is small (just a JSON with turnId + timestamp) and uses atomic writes
|
|
14
|
-
* to prevent corruption from concurrent hook execution.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import * as fs from 'fs';
|
|
18
|
-
import * as path from 'path';
|
|
19
|
-
import * as os from 'os';
|
|
20
|
-
|
|
21
|
-
const TURN_STATE_DIR = path.join(os.homedir(), '.claude-code', 'memory');
|
|
22
|
-
|
|
23
|
-
interface TurnState {
|
|
24
|
-
turnId: string;
|
|
25
|
-
sessionId: string;
|
|
26
|
-
createdAt: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get the state file path for a session
|
|
31
|
-
*/
|
|
32
|
-
function getStatePath(sessionId: string): string {
|
|
33
|
-
return path.join(TURN_STATE_DIR, `.turn-state-${sessionId}.json`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Write a new turn state for a session.
|
|
38
|
-
* Called by UserPromptSubmit hook when a new user prompt arrives.
|
|
39
|
-
*/
|
|
40
|
-
export function writeTurnState(sessionId: string, turnId: string): void {
|
|
41
|
-
try {
|
|
42
|
-
// Ensure directory exists
|
|
43
|
-
if (!fs.existsSync(TURN_STATE_DIR)) {
|
|
44
|
-
fs.mkdirSync(TURN_STATE_DIR, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const state: TurnState = {
|
|
48
|
-
turnId,
|
|
49
|
-
sessionId,
|
|
50
|
-
createdAt: new Date().toISOString()
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const filePath = getStatePath(sessionId);
|
|
54
|
-
const tempPath = filePath + '.tmp';
|
|
55
|
-
|
|
56
|
-
// Atomic write: write to temp file then rename
|
|
57
|
-
fs.writeFileSync(tempPath, JSON.stringify(state));
|
|
58
|
-
fs.renameSync(tempPath, filePath);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
// Non-critical: if we can't write turn state, events just won't be grouped
|
|
61
|
-
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
62
|
-
console.error('Failed to write turn state:', error);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Read the current turn_id for a session.
|
|
69
|
-
* Called by PostToolUse and Stop hooks to associate events with the current turn.
|
|
70
|
-
* Returns null if no turn state exists (events won't be grouped).
|
|
71
|
-
*/
|
|
72
|
-
export function readTurnState(sessionId: string): string | null {
|
|
73
|
-
try {
|
|
74
|
-
const filePath = getStatePath(sessionId);
|
|
75
|
-
|
|
76
|
-
if (!fs.existsSync(filePath)) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const data = fs.readFileSync(filePath, 'utf-8');
|
|
81
|
-
const state: TurnState = JSON.parse(data);
|
|
82
|
-
|
|
83
|
-
// Validate the state belongs to this session
|
|
84
|
-
if (state.sessionId !== sessionId) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Check staleness: if the turn state is older than 30 minutes, ignore it
|
|
89
|
-
const createdAt = new Date(state.createdAt).getTime();
|
|
90
|
-
const now = Date.now();
|
|
91
|
-
if (now - createdAt > 30 * 60 * 1000) {
|
|
92
|
-
// Stale turn state, clean up
|
|
93
|
-
clearTurnState(sessionId);
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return state.turnId;
|
|
98
|
-
} catch (error) {
|
|
99
|
-
// Non-critical: return null if we can't read
|
|
100
|
-
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
101
|
-
console.error('Failed to read turn state:', error);
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Clear the turn state for a session.
|
|
109
|
-
* Called by Stop hook after processing agent responses.
|
|
110
|
-
*/
|
|
111
|
-
export function clearTurnState(sessionId: string): void {
|
|
112
|
-
try {
|
|
113
|
-
const filePath = getStatePath(sessionId);
|
|
114
|
-
if (fs.existsSync(filePath)) {
|
|
115
|
-
fs.unlinkSync(filePath);
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
// Non-critical
|
|
119
|
-
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
120
|
-
console.error('Failed to clear turn state:', error);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
// Last Assistant Snippet State
|
|
127
|
-
// Persists the last ~500 chars of the assistant's response so the next
|
|
128
|
-
// UserPromptSubmit can enrich the retrieval query with conversation context.
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
|
|
131
|
-
const LAST_RESPONSE_SNIPPET_CHARS = 500;
|
|
132
|
-
|
|
133
|
-
interface LastResponseState {
|
|
134
|
-
sessionId: string;
|
|
135
|
-
snippet: string;
|
|
136
|
-
createdAt: string;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function getLastResponsePath(sessionId: string): string {
|
|
140
|
-
return path.join(TURN_STATE_DIR, `.last-response-${sessionId}.json`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function writeLastAssistantSnippet(sessionId: string, text: string): void {
|
|
144
|
-
try {
|
|
145
|
-
if (!fs.existsSync(TURN_STATE_DIR)) {
|
|
146
|
-
fs.mkdirSync(TURN_STATE_DIR, { recursive: true });
|
|
147
|
-
}
|
|
148
|
-
const snippet = text.slice(0, LAST_RESPONSE_SNIPPET_CHARS);
|
|
149
|
-
const state: LastResponseState = { sessionId, snippet, createdAt: new Date().toISOString() };
|
|
150
|
-
const filePath = getLastResponsePath(sessionId);
|
|
151
|
-
const tempPath = filePath + '.tmp';
|
|
152
|
-
fs.writeFileSync(tempPath, JSON.stringify(state));
|
|
153
|
-
fs.renameSync(tempPath, filePath);
|
|
154
|
-
} catch {
|
|
155
|
-
// non-critical
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function readLastAssistantSnippet(sessionId: string): string | null {
|
|
160
|
-
try {
|
|
161
|
-
const filePath = getLastResponsePath(sessionId);
|
|
162
|
-
if (!fs.existsSync(filePath)) return null;
|
|
163
|
-
const state: LastResponseState = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
164
|
-
if (state.sessionId !== sessionId) return null;
|
|
165
|
-
// Ignore if older than 2 hours (stale session)
|
|
166
|
-
if (Date.now() - new Date(state.createdAt).getTime() > 2 * 60 * 60 * 1000) return null;
|
|
167
|
-
return state.snippet || null;
|
|
168
|
-
} catch {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Clean up stale turn state files (older than 1 hour).
|
|
175
|
-
* Can be called periodically to prevent file accumulation.
|
|
176
|
-
*/
|
|
177
|
-
export function cleanupStaleTurnStates(): number {
|
|
178
|
-
let cleaned = 0;
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
if (!fs.existsSync(TURN_STATE_DIR)) return 0;
|
|
182
|
-
|
|
183
|
-
const files = fs.readdirSync(TURN_STATE_DIR);
|
|
184
|
-
const now = Date.now();
|
|
185
|
-
|
|
186
|
-
for (const file of files) {
|
|
187
|
-
if (!file.startsWith('.turn-state-') || !file.endsWith('.json')) continue;
|
|
188
|
-
|
|
189
|
-
const filePath = path.join(TURN_STATE_DIR, file);
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const stat = fs.statSync(filePath);
|
|
193
|
-
// Remove files older than 1 hour
|
|
194
|
-
if (now - stat.mtimeMs > 60 * 60 * 1000) {
|
|
195
|
-
fs.unlinkSync(filePath);
|
|
196
|
-
cleaned++;
|
|
197
|
-
}
|
|
198
|
-
} catch {
|
|
199
|
-
// Skip files we can't stat
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
// Non-critical
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return cleaned;
|
|
207
|
-
}
|