claude-memory-layer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,414 @@
|
|
|
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 { Database } from 'duckdb';
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
import type {
|
|
9
|
+
Entity,
|
|
10
|
+
TaskStatus,
|
|
11
|
+
TaskPriority,
|
|
12
|
+
BlockerRef,
|
|
13
|
+
BlockerMode
|
|
14
|
+
} from '../types.js';
|
|
15
|
+
import { makeEntityCanonicalKey, makeTaskEventDedupeKey } from '../canonical-key.js';
|
|
16
|
+
import { TaskMatcher } from './task-matcher.js';
|
|
17
|
+
import { BlockerResolver } from './blocker-resolver.js';
|
|
18
|
+
|
|
19
|
+
export interface ExtractedTask {
|
|
20
|
+
title: string;
|
|
21
|
+
status?: TaskStatus;
|
|
22
|
+
priority?: TaskPriority;
|
|
23
|
+
blockedBy?: string[];
|
|
24
|
+
description?: string;
|
|
25
|
+
project?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TaskResolverConfig {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
project?: string;
|
|
31
|
+
evidenceAligned?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Valid status transitions
|
|
35
|
+
const VALID_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {
|
|
36
|
+
pending: ['in_progress', 'cancelled'],
|
|
37
|
+
in_progress: ['blocked', 'done', 'cancelled'],
|
|
38
|
+
blocked: ['in_progress', 'done', 'cancelled'],
|
|
39
|
+
done: [], // Terminal state
|
|
40
|
+
cancelled: [] // Terminal state
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export class TaskResolver {
|
|
44
|
+
private taskMatcher: TaskMatcher;
|
|
45
|
+
private blockerResolver: BlockerResolver;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private db: Database,
|
|
49
|
+
private config: TaskResolverConfig
|
|
50
|
+
) {
|
|
51
|
+
this.taskMatcher = new TaskMatcher(db);
|
|
52
|
+
this.blockerResolver = new BlockerResolver(db, { project: config.project });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Process extracted task entry
|
|
57
|
+
* 1. Find or create task entity
|
|
58
|
+
* 2. Emit status/priority change events if needed
|
|
59
|
+
* 3. Process blockers
|
|
60
|
+
*/
|
|
61
|
+
async processTask(extracted: ExtractedTask, sourceEntryId?: string): Promise<{
|
|
62
|
+
taskId: string;
|
|
63
|
+
isNew: boolean;
|
|
64
|
+
events: string[];
|
|
65
|
+
}> {
|
|
66
|
+
const events: string[] = [];
|
|
67
|
+
|
|
68
|
+
// Step 1: Find existing task or create new
|
|
69
|
+
const { task, isNew, eventId: createEventId } = await this.findOrCreateTask(extracted);
|
|
70
|
+
|
|
71
|
+
if (isNew && createEventId) {
|
|
72
|
+
events.push(createEventId);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Step 2: Handle status changes
|
|
76
|
+
if (extracted.status) {
|
|
77
|
+
const statusEvent = await this.handleStatusChange(task, extracted.status);
|
|
78
|
+
if (statusEvent) {
|
|
79
|
+
events.push(statusEvent);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Step 3: Handle priority changes
|
|
84
|
+
if (extracted.priority) {
|
|
85
|
+
const priorityEvent = await this.handlePriorityChange(task, extracted.priority);
|
|
86
|
+
if (priorityEvent) {
|
|
87
|
+
events.push(priorityEvent);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Step 4: Handle blockers
|
|
92
|
+
if (extracted.blockedBy && extracted.blockedBy.length > 0) {
|
|
93
|
+
const blockerEvent = await this.handleBlockers(
|
|
94
|
+
task,
|
|
95
|
+
extracted.blockedBy,
|
|
96
|
+
sourceEntryId
|
|
97
|
+
);
|
|
98
|
+
if (blockerEvent) {
|
|
99
|
+
events.push(blockerEvent);
|
|
100
|
+
}
|
|
101
|
+
} else if (extracted.status === 'blocked') {
|
|
102
|
+
// Status is blocked but no blockers provided
|
|
103
|
+
// Create unknown placeholder
|
|
104
|
+
const blockerEvent = await this.handleUnknownBlocker(task);
|
|
105
|
+
if (blockerEvent) {
|
|
106
|
+
events.push(blockerEvent);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
taskId: task.entityId,
|
|
112
|
+
isNew,
|
|
113
|
+
events
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find existing task or create new one
|
|
119
|
+
*/
|
|
120
|
+
private async findOrCreateTask(extracted: ExtractedTask): Promise<{
|
|
121
|
+
task: Entity;
|
|
122
|
+
isNew: boolean;
|
|
123
|
+
eventId?: string;
|
|
124
|
+
}> {
|
|
125
|
+
// Try to find existing task
|
|
126
|
+
const matchResult = await this.taskMatcher.match(extracted.title, extracted.project);
|
|
127
|
+
|
|
128
|
+
if (matchResult.confidence === 'high' && matchResult.match) {
|
|
129
|
+
return {
|
|
130
|
+
task: matchResult.match,
|
|
131
|
+
isNew: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create new task
|
|
136
|
+
const taskId = randomUUID();
|
|
137
|
+
const canonicalKey = makeEntityCanonicalKey('task', extracted.title, {
|
|
138
|
+
project: extracted.project
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Correct initial status: never start as 'done'
|
|
142
|
+
let initialStatus = extracted.status ?? 'pending';
|
|
143
|
+
if (initialStatus === 'done') {
|
|
144
|
+
initialStatus = 'in_progress'; // Correct: can't start as done
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const now = new Date();
|
|
148
|
+
|
|
149
|
+
const currentJson = {
|
|
150
|
+
status: initialStatus,
|
|
151
|
+
priority: extracted.priority ?? 'medium',
|
|
152
|
+
description: extracted.description,
|
|
153
|
+
project: extracted.project ?? this.config.project
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Insert entity
|
|
157
|
+
await this.db.run(
|
|
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 this.db.run(
|
|
179
|
+
`INSERT INTO entity_aliases (entity_type, canonical_key, entity_id, is_primary)
|
|
180
|
+
VALUES (?, ?, ?, TRUE)
|
|
181
|
+
ON CONFLICT (entity_type, canonical_key) DO NOTHING`,
|
|
182
|
+
['task', canonicalKey, taskId]
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Emit task_created event
|
|
186
|
+
const eventId = await this.emitTaskEvent('task_created', {
|
|
187
|
+
taskId,
|
|
188
|
+
title: extracted.title,
|
|
189
|
+
canonicalKey,
|
|
190
|
+
initialStatus,
|
|
191
|
+
priority: extracted.priority ?? 'medium',
|
|
192
|
+
description: extracted.description,
|
|
193
|
+
project: extracted.project ?? this.config.project
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Return created entity
|
|
197
|
+
const task: Entity = {
|
|
198
|
+
entityId: taskId,
|
|
199
|
+
entityType: 'task',
|
|
200
|
+
canonicalKey,
|
|
201
|
+
title: extracted.title,
|
|
202
|
+
stage: 'raw',
|
|
203
|
+
status: 'active',
|
|
204
|
+
currentJson,
|
|
205
|
+
titleNorm: extracted.title.toLowerCase().trim(),
|
|
206
|
+
searchText: `${extracted.title} ${extracted.description ?? ''}`,
|
|
207
|
+
createdAt: now,
|
|
208
|
+
updatedAt: now
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return { task, isNew: true, eventId };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle task status change
|
|
216
|
+
*/
|
|
217
|
+
private async handleStatusChange(
|
|
218
|
+
task: Entity,
|
|
219
|
+
newStatus: TaskStatus
|
|
220
|
+
): Promise<string | null> {
|
|
221
|
+
const currentJson = task.currentJson as { status: TaskStatus };
|
|
222
|
+
const currentStatus = currentJson.status;
|
|
223
|
+
|
|
224
|
+
if (currentStatus === newStatus) {
|
|
225
|
+
return null; // No change
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Validate transition
|
|
229
|
+
const validNextStates = VALID_TRANSITIONS[currentStatus] ?? [];
|
|
230
|
+
if (!validNextStates.includes(newStatus)) {
|
|
231
|
+
// Invalid transition - emit rejection event
|
|
232
|
+
return this.emitTaskEvent('task_transition_rejected', {
|
|
233
|
+
taskId: task.entityId,
|
|
234
|
+
fromStatus: currentStatus,
|
|
235
|
+
toStatus: newStatus,
|
|
236
|
+
reason: `Invalid transition from ${currentStatus} to ${newStatus}`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Emit status change event
|
|
241
|
+
const eventId = await this.emitTaskEvent('task_status_changed', {
|
|
242
|
+
taskId: task.entityId,
|
|
243
|
+
fromStatus: currentStatus,
|
|
244
|
+
toStatus: newStatus
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Update entity (projector will do this, but we update for immediate consistency)
|
|
248
|
+
await this.db.run(
|
|
249
|
+
`UPDATE entities
|
|
250
|
+
SET current_json = json_set(current_json, '$.status', ?),
|
|
251
|
+
updated_at = ?
|
|
252
|
+
WHERE entity_id = ?`,
|
|
253
|
+
[newStatus, new Date().toISOString(), task.entityId]
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return eventId;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Handle task priority change
|
|
261
|
+
*/
|
|
262
|
+
private async handlePriorityChange(
|
|
263
|
+
task: Entity,
|
|
264
|
+
newPriority: TaskPriority
|
|
265
|
+
): Promise<string | null> {
|
|
266
|
+
const currentJson = task.currentJson as { priority?: TaskPriority };
|
|
267
|
+
const currentPriority = currentJson.priority ?? 'medium';
|
|
268
|
+
|
|
269
|
+
if (currentPriority === newPriority) {
|
|
270
|
+
return null; // No change
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Emit priority change event
|
|
274
|
+
const eventId = await this.emitTaskEvent('task_priority_changed', {
|
|
275
|
+
taskId: task.entityId,
|
|
276
|
+
fromPriority: currentPriority,
|
|
277
|
+
toPriority: newPriority
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Update entity
|
|
281
|
+
await this.db.run(
|
|
282
|
+
`UPDATE entities
|
|
283
|
+
SET current_json = json_set(current_json, '$.priority', ?),
|
|
284
|
+
updated_at = ?
|
|
285
|
+
WHERE entity_id = ?`,
|
|
286
|
+
[newPriority, new Date().toISOString(), task.entityId]
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return eventId;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Handle blockers
|
|
294
|
+
*/
|
|
295
|
+
private async handleBlockers(
|
|
296
|
+
task: Entity,
|
|
297
|
+
blockedByTexts: string[],
|
|
298
|
+
sourceEntryId?: string
|
|
299
|
+
): Promise<string | null> {
|
|
300
|
+
// Resolve blocker texts to entity refs
|
|
301
|
+
const blockerRefs = await this.blockerResolver.resolveBlockers(blockedByTexts);
|
|
302
|
+
|
|
303
|
+
// Determine mode based on evidence alignment
|
|
304
|
+
const mode: BlockerMode = this.config.evidenceAligned ? 'replace' : 'suggest';
|
|
305
|
+
|
|
306
|
+
// Emit task_blockers_set event
|
|
307
|
+
const eventId = await this.emitTaskEvent('task_blockers_set', {
|
|
308
|
+
taskId: task.entityId,
|
|
309
|
+
mode,
|
|
310
|
+
blockers: blockerRefs,
|
|
311
|
+
sourceEntryId
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return eventId;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Handle unknown blocker (status=blocked but no blockedBy)
|
|
319
|
+
*/
|
|
320
|
+
private async handleUnknownBlocker(task: Entity): Promise<string | null> {
|
|
321
|
+
const placeholderRef = await this.blockerResolver.createUnknownPlaceholder(task.title);
|
|
322
|
+
|
|
323
|
+
const eventId = await this.emitTaskEvent('task_blockers_set', {
|
|
324
|
+
taskId: task.entityId,
|
|
325
|
+
mode: 'suggest' as BlockerMode,
|
|
326
|
+
blockers: [placeholderRef]
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return eventId;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Emit task event to events table
|
|
334
|
+
*/
|
|
335
|
+
private async emitTaskEvent(
|
|
336
|
+
eventType: string,
|
|
337
|
+
payload: Record<string, unknown>
|
|
338
|
+
): Promise<string> {
|
|
339
|
+
const eventId = randomUUID();
|
|
340
|
+
const now = new Date();
|
|
341
|
+
|
|
342
|
+
// Generate dedupe key
|
|
343
|
+
const dedupeKey = makeTaskEventDedupeKey(
|
|
344
|
+
eventType,
|
|
345
|
+
payload.taskId as string,
|
|
346
|
+
this.config.sessionId,
|
|
347
|
+
JSON.stringify(payload)
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Check for duplicate
|
|
351
|
+
const existing = await this.db.all<Array<{ event_id: string }>>(
|
|
352
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
353
|
+
[dedupeKey]
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (existing.length > 0) {
|
|
357
|
+
return existing[0].event_id; // Return existing event ID
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Insert event
|
|
361
|
+
await this.db.run(
|
|
362
|
+
`INSERT INTO events (
|
|
363
|
+
id, event_type, session_id, timestamp, content,
|
|
364
|
+
canonical_key, dedupe_key, metadata
|
|
365
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
366
|
+
[
|
|
367
|
+
eventId,
|
|
368
|
+
eventType,
|
|
369
|
+
this.config.sessionId,
|
|
370
|
+
now.toISOString(),
|
|
371
|
+
JSON.stringify(payload),
|
|
372
|
+
`task_event:${eventType}:${payload.taskId}`,
|
|
373
|
+
dedupeKey,
|
|
374
|
+
JSON.stringify({ source: 'task_resolver' })
|
|
375
|
+
]
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Insert dedup record
|
|
379
|
+
await this.db.run(
|
|
380
|
+
`INSERT INTO event_dedup (dedupe_key, event_id)
|
|
381
|
+
VALUES (?, ?)
|
|
382
|
+
ON CONFLICT DO NOTHING`,
|
|
383
|
+
[dedupeKey, eventId]
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
return eventId;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Resolve condition to task (when condition is identified as existing task)
|
|
391
|
+
*/
|
|
392
|
+
async resolveConditionToTask(
|
|
393
|
+
conditionId: string,
|
|
394
|
+
taskId: string
|
|
395
|
+
): Promise<string> {
|
|
396
|
+
const eventId = await this.emitTaskEvent('condition_resolved_to', {
|
|
397
|
+
conditionId,
|
|
398
|
+
resolvedTo: {
|
|
399
|
+
kind: 'task',
|
|
400
|
+
entityId: taskId
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Create resolves_to edge
|
|
405
|
+
await this.db.run(
|
|
406
|
+
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
|
|
407
|
+
VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
|
|
408
|
+
ON CONFLICT DO NOTHING`,
|
|
409
|
+
[randomUUID(), conditionId, taskId, JSON.stringify({ resolved_at: new Date().toISOString() })]
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return eventId;
|
|
413
|
+
}
|
|
414
|
+
}
|