@sparkleideas/shared 3.0.0-alpha.7
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 +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Projections for Read Models (ADR-007)
|
|
3
|
+
*
|
|
4
|
+
* Build read models from domain events using projections.
|
|
5
|
+
* Projections listen to events and maintain queryable state.
|
|
6
|
+
*
|
|
7
|
+
* Implemented Projections:
|
|
8
|
+
* - AgentStateProjection - Current state of all agents
|
|
9
|
+
* - TaskHistoryProjection - Complete task execution history
|
|
10
|
+
* - MemoryIndexProjection - Memory access patterns and index
|
|
11
|
+
*
|
|
12
|
+
* @module v3/shared/events/projections
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { EventEmitter } from 'node:events';
|
|
16
|
+
import { DomainEvent } from './domain-events.js';
|
|
17
|
+
import { EventStore } from './event-store.js';
|
|
18
|
+
import { AgentId, TaskId, AgentStatus, TaskStatus } from '../types.js';
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Projection Base Class
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
export abstract class Projection extends EventEmitter {
|
|
25
|
+
protected initialized: boolean = false;
|
|
26
|
+
|
|
27
|
+
constructor(protected eventStore: EventStore) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the projection by replaying events
|
|
33
|
+
*/
|
|
34
|
+
async initialize(): Promise<void> {
|
|
35
|
+
if (this.initialized) return;
|
|
36
|
+
|
|
37
|
+
// Replay all events to build current state
|
|
38
|
+
for await (const event of this.eventStore.replay()) {
|
|
39
|
+
await this.handle(event);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.initialized = true;
|
|
43
|
+
this.emit('initialized');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handle a domain event
|
|
48
|
+
*/
|
|
49
|
+
abstract handle(event: DomainEvent): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reset the projection state
|
|
53
|
+
*/
|
|
54
|
+
abstract reset(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Agent State Projection
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
export interface AgentProjectionState {
|
|
62
|
+
id: AgentId;
|
|
63
|
+
role: string;
|
|
64
|
+
domain: string;
|
|
65
|
+
status: AgentStatus;
|
|
66
|
+
currentTask: TaskId | null;
|
|
67
|
+
completedTasks: TaskId[];
|
|
68
|
+
failedTasks: TaskId[];
|
|
69
|
+
totalTaskDuration: number;
|
|
70
|
+
taskCount: number;
|
|
71
|
+
errorCount: number;
|
|
72
|
+
spawnedAt: number;
|
|
73
|
+
startedAt: number | null;
|
|
74
|
+
stoppedAt: number | null;
|
|
75
|
+
lastActivityAt: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class AgentStateProjection extends Projection {
|
|
79
|
+
private agents: Map<AgentId, AgentProjectionState> = new Map();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get state for a specific agent
|
|
83
|
+
*/
|
|
84
|
+
getAgent(agentId: AgentId): AgentProjectionState | null {
|
|
85
|
+
return this.agents.get(agentId) || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all agents
|
|
90
|
+
*/
|
|
91
|
+
getAllAgents(): AgentProjectionState[] {
|
|
92
|
+
return Array.from(this.agents.values());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get agents by status
|
|
97
|
+
*/
|
|
98
|
+
getAgentsByStatus(status: AgentStatus): AgentProjectionState[] {
|
|
99
|
+
return this.getAllAgents().filter((agent) => agent.status === status);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get agents by domain
|
|
104
|
+
*/
|
|
105
|
+
getAgentsByDomain(domain: string): AgentProjectionState[] {
|
|
106
|
+
return this.getAllAgents().filter((agent) => agent.domain === domain);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get active agent count
|
|
111
|
+
*/
|
|
112
|
+
getActiveAgentCount(): number {
|
|
113
|
+
return this.getAgentsByStatus('active').length;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async handle(event: DomainEvent): Promise<void> {
|
|
117
|
+
switch (event.type) {
|
|
118
|
+
case 'agent:spawned':
|
|
119
|
+
this.handleAgentSpawned(event);
|
|
120
|
+
break;
|
|
121
|
+
case 'agent:started':
|
|
122
|
+
this.handleAgentStarted(event);
|
|
123
|
+
break;
|
|
124
|
+
case 'agent:stopped':
|
|
125
|
+
this.handleAgentStopped(event);
|
|
126
|
+
break;
|
|
127
|
+
case 'agent:failed':
|
|
128
|
+
this.handleAgentFailed(event);
|
|
129
|
+
break;
|
|
130
|
+
case 'agent:status-changed':
|
|
131
|
+
this.handleAgentStatusChanged(event);
|
|
132
|
+
break;
|
|
133
|
+
case 'agent:task-assigned':
|
|
134
|
+
this.handleAgentTaskAssigned(event);
|
|
135
|
+
break;
|
|
136
|
+
case 'agent:task-completed':
|
|
137
|
+
this.handleAgentTaskCompleted(event);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
reset(): void {
|
|
143
|
+
this.agents.clear();
|
|
144
|
+
this.emit('reset');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private handleAgentSpawned(event: DomainEvent): void {
|
|
148
|
+
const { agentId, role, domain } = event.payload;
|
|
149
|
+
|
|
150
|
+
this.agents.set(agentId as AgentId, {
|
|
151
|
+
id: agentId as AgentId,
|
|
152
|
+
role: role as string,
|
|
153
|
+
domain: domain as string,
|
|
154
|
+
status: 'idle',
|
|
155
|
+
currentTask: null,
|
|
156
|
+
completedTasks: [],
|
|
157
|
+
failedTasks: [],
|
|
158
|
+
totalTaskDuration: 0,
|
|
159
|
+
taskCount: 0,
|
|
160
|
+
errorCount: 0,
|
|
161
|
+
spawnedAt: event.timestamp,
|
|
162
|
+
startedAt: null,
|
|
163
|
+
stoppedAt: null,
|
|
164
|
+
lastActivityAt: event.timestamp,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.emit('agent:spawned', { agentId });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private handleAgentStarted(event: DomainEvent): void {
|
|
171
|
+
const { agentId } = event.payload;
|
|
172
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
173
|
+
|
|
174
|
+
if (agent) {
|
|
175
|
+
agent.status = 'active';
|
|
176
|
+
agent.startedAt = event.timestamp;
|
|
177
|
+
agent.lastActivityAt = event.timestamp;
|
|
178
|
+
this.emit('agent:started', { agentId });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private handleAgentStopped(event: DomainEvent): void {
|
|
183
|
+
const { agentId } = event.payload;
|
|
184
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
185
|
+
|
|
186
|
+
if (agent) {
|
|
187
|
+
agent.status = 'completed';
|
|
188
|
+
agent.stoppedAt = event.timestamp;
|
|
189
|
+
agent.lastActivityAt = event.timestamp;
|
|
190
|
+
this.emit('agent:stopped', { agentId });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private handleAgentFailed(event: DomainEvent): void {
|
|
195
|
+
const { agentId } = event.payload;
|
|
196
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
197
|
+
|
|
198
|
+
if (agent) {
|
|
199
|
+
agent.status = 'error';
|
|
200
|
+
agent.errorCount++;
|
|
201
|
+
agent.lastActivityAt = event.timestamp;
|
|
202
|
+
this.emit('agent:failed', { agentId });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private handleAgentStatusChanged(event: DomainEvent): void {
|
|
207
|
+
const { agentId, newStatus } = event.payload;
|
|
208
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
209
|
+
|
|
210
|
+
if (agent) {
|
|
211
|
+
agent.status = newStatus as AgentStatus;
|
|
212
|
+
agent.lastActivityAt = event.timestamp;
|
|
213
|
+
this.emit('agent:status-changed', { agentId, status: newStatus });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private handleAgentTaskAssigned(event: DomainEvent): void {
|
|
218
|
+
const { agentId, taskId } = event.payload;
|
|
219
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
220
|
+
|
|
221
|
+
if (agent) {
|
|
222
|
+
agent.currentTask = taskId as TaskId;
|
|
223
|
+
agent.status = 'active';
|
|
224
|
+
agent.lastActivityAt = event.timestamp;
|
|
225
|
+
this.emit('agent:task-assigned', { agentId, taskId });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private handleAgentTaskCompleted(event: DomainEvent): void {
|
|
230
|
+
const { agentId, taskId, duration } = event.payload;
|
|
231
|
+
const agent = this.agents.get(agentId as AgentId);
|
|
232
|
+
|
|
233
|
+
if (agent) {
|
|
234
|
+
agent.completedTasks.push(taskId as TaskId);
|
|
235
|
+
agent.currentTask = null;
|
|
236
|
+
agent.taskCount++;
|
|
237
|
+
agent.totalTaskDuration += (duration as number) || 0;
|
|
238
|
+
agent.status = 'idle';
|
|
239
|
+
agent.lastActivityAt = event.timestamp;
|
|
240
|
+
this.emit('agent:task-completed', { agentId, taskId });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// =============================================================================
|
|
246
|
+
// Task History Projection
|
|
247
|
+
// =============================================================================
|
|
248
|
+
|
|
249
|
+
export interface TaskProjectionState {
|
|
250
|
+
id: TaskId;
|
|
251
|
+
type: string;
|
|
252
|
+
title: string;
|
|
253
|
+
status: TaskStatus;
|
|
254
|
+
priority: string;
|
|
255
|
+
assignedAgent: AgentId | null;
|
|
256
|
+
dependencies: TaskId[];
|
|
257
|
+
blockedBy: TaskId[];
|
|
258
|
+
createdAt: number;
|
|
259
|
+
startedAt: number | null;
|
|
260
|
+
completedAt: number | null;
|
|
261
|
+
failedAt: number | null;
|
|
262
|
+
duration: number | null;
|
|
263
|
+
result: unknown;
|
|
264
|
+
error: string | null;
|
|
265
|
+
retryCount: number;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export class TaskHistoryProjection extends Projection {
|
|
269
|
+
private tasks: Map<TaskId, TaskProjectionState> = new Map();
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get task by ID
|
|
273
|
+
*/
|
|
274
|
+
getTask(taskId: TaskId): TaskProjectionState | null {
|
|
275
|
+
return this.tasks.get(taskId) || null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get all tasks
|
|
280
|
+
*/
|
|
281
|
+
getAllTasks(): TaskProjectionState[] {
|
|
282
|
+
return Array.from(this.tasks.values());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get tasks by status
|
|
287
|
+
*/
|
|
288
|
+
getTasksByStatus(status: TaskStatus): TaskProjectionState[] {
|
|
289
|
+
return this.getAllTasks().filter((task) => task.status === status);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get tasks by agent
|
|
294
|
+
*/
|
|
295
|
+
getTasksByAgent(agentId: AgentId): TaskProjectionState[] {
|
|
296
|
+
return this.getAllTasks().filter((task) => task.assignedAgent === agentId);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get completed task count
|
|
301
|
+
*/
|
|
302
|
+
getCompletedTaskCount(): number {
|
|
303
|
+
return this.getTasksByStatus('completed').length;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get average task duration
|
|
308
|
+
*/
|
|
309
|
+
getAverageTaskDuration(): number {
|
|
310
|
+
const completed = this.getTasksByStatus('completed').filter((t) => t.duration !== null);
|
|
311
|
+
|
|
312
|
+
if (completed.length === 0) return 0;
|
|
313
|
+
|
|
314
|
+
const total = completed.reduce((sum, task) => sum + (task.duration || 0), 0);
|
|
315
|
+
return total / completed.length;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async handle(event: DomainEvent): Promise<void> {
|
|
319
|
+
switch (event.type) {
|
|
320
|
+
case 'task:created':
|
|
321
|
+
this.handleTaskCreated(event);
|
|
322
|
+
break;
|
|
323
|
+
case 'task:queued':
|
|
324
|
+
this.handleTaskQueued(event);
|
|
325
|
+
break;
|
|
326
|
+
case 'task:started':
|
|
327
|
+
this.handleTaskStarted(event);
|
|
328
|
+
break;
|
|
329
|
+
case 'task:completed':
|
|
330
|
+
this.handleTaskCompleted(event);
|
|
331
|
+
break;
|
|
332
|
+
case 'task:failed':
|
|
333
|
+
this.handleTaskFailed(event);
|
|
334
|
+
break;
|
|
335
|
+
case 'task:blocked':
|
|
336
|
+
this.handleTaskBlocked(event);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
reset(): void {
|
|
342
|
+
this.tasks.clear();
|
|
343
|
+
this.emit('reset');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private handleTaskCreated(event: DomainEvent): void {
|
|
347
|
+
const { taskId, taskType, title, priority, dependencies } = event.payload;
|
|
348
|
+
|
|
349
|
+
this.tasks.set(taskId as TaskId, {
|
|
350
|
+
id: taskId as TaskId,
|
|
351
|
+
type: taskType as string,
|
|
352
|
+
title: title as string,
|
|
353
|
+
status: 'pending',
|
|
354
|
+
priority: priority as string,
|
|
355
|
+
assignedAgent: null,
|
|
356
|
+
dependencies: (dependencies as TaskId[]) || [],
|
|
357
|
+
blockedBy: [],
|
|
358
|
+
createdAt: event.timestamp,
|
|
359
|
+
startedAt: null,
|
|
360
|
+
completedAt: null,
|
|
361
|
+
failedAt: null,
|
|
362
|
+
duration: null,
|
|
363
|
+
result: null,
|
|
364
|
+
error: null,
|
|
365
|
+
retryCount: 0,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
this.emit('task:created', { taskId });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private handleTaskQueued(event: DomainEvent): void {
|
|
372
|
+
const { taskId } = event.payload;
|
|
373
|
+
const task = this.tasks.get(taskId as TaskId);
|
|
374
|
+
|
|
375
|
+
if (task) {
|
|
376
|
+
task.status = 'queued';
|
|
377
|
+
this.emit('task:queued', { taskId });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private handleTaskStarted(event: DomainEvent): void {
|
|
382
|
+
const { taskId, agentId } = event.payload;
|
|
383
|
+
const task = this.tasks.get(taskId as TaskId);
|
|
384
|
+
|
|
385
|
+
if (task) {
|
|
386
|
+
task.status = 'in-progress';
|
|
387
|
+
task.assignedAgent = agentId as AgentId;
|
|
388
|
+
task.startedAt = event.timestamp;
|
|
389
|
+
this.emit('task:started', { taskId, agentId });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private handleTaskCompleted(event: DomainEvent): void {
|
|
394
|
+
const { taskId, result, duration } = event.payload;
|
|
395
|
+
const task = this.tasks.get(taskId as TaskId);
|
|
396
|
+
|
|
397
|
+
if (task) {
|
|
398
|
+
task.status = 'completed';
|
|
399
|
+
task.completedAt = event.timestamp;
|
|
400
|
+
task.duration = (duration as number) || (task.startedAt ? event.timestamp - task.startedAt : null);
|
|
401
|
+
task.result = result;
|
|
402
|
+
this.emit('task:completed', { taskId });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private handleTaskFailed(event: DomainEvent): void {
|
|
407
|
+
const { taskId, error, retryCount } = event.payload;
|
|
408
|
+
const task = this.tasks.get(taskId as TaskId);
|
|
409
|
+
|
|
410
|
+
if (task) {
|
|
411
|
+
task.status = 'failed';
|
|
412
|
+
task.failedAt = event.timestamp;
|
|
413
|
+
task.error = error as string;
|
|
414
|
+
task.retryCount = retryCount as number;
|
|
415
|
+
this.emit('task:failed', { taskId });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private handleTaskBlocked(event: DomainEvent): void {
|
|
420
|
+
const { taskId, blockedBy } = event.payload;
|
|
421
|
+
const task = this.tasks.get(taskId as TaskId);
|
|
422
|
+
|
|
423
|
+
if (task) {
|
|
424
|
+
task.status = 'blocked';
|
|
425
|
+
task.blockedBy = blockedBy as TaskId[];
|
|
426
|
+
this.emit('task:blocked', { taskId, blockedBy });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// =============================================================================
|
|
432
|
+
// Memory Index Projection
|
|
433
|
+
// =============================================================================
|
|
434
|
+
|
|
435
|
+
export interface MemoryProjectionState {
|
|
436
|
+
id: string;
|
|
437
|
+
namespace: string;
|
|
438
|
+
key: string;
|
|
439
|
+
type: string;
|
|
440
|
+
size: number;
|
|
441
|
+
accessCount: number;
|
|
442
|
+
storedAt: number;
|
|
443
|
+
lastAccessedAt: number;
|
|
444
|
+
deletedAt: number | null;
|
|
445
|
+
isDeleted: boolean;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export class MemoryIndexProjection extends Projection {
|
|
449
|
+
private memories: Map<string, MemoryProjectionState> = new Map();
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get memory by ID
|
|
453
|
+
*/
|
|
454
|
+
getMemory(memoryId: string): MemoryProjectionState | null {
|
|
455
|
+
return this.memories.get(memoryId) || null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get all active memories (not deleted)
|
|
460
|
+
*/
|
|
461
|
+
getActiveMemories(): MemoryProjectionState[] {
|
|
462
|
+
return Array.from(this.memories.values()).filter((m) => !m.isDeleted);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get memories by namespace
|
|
467
|
+
*/
|
|
468
|
+
getMemoriesByNamespace(namespace: string): MemoryProjectionState[] {
|
|
469
|
+
return this.getActiveMemories().filter((m) => m.namespace === namespace);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get most accessed memories
|
|
474
|
+
*/
|
|
475
|
+
getMostAccessedMemories(limit: number = 10): MemoryProjectionState[] {
|
|
476
|
+
return this.getActiveMemories()
|
|
477
|
+
.sort((a, b) => b.accessCount - a.accessCount)
|
|
478
|
+
.slice(0, limit);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get total memory size by namespace
|
|
483
|
+
*/
|
|
484
|
+
getTotalSizeByNamespace(namespace: string): number {
|
|
485
|
+
return this.getMemoriesByNamespace(namespace).reduce((sum, m) => sum + m.size, 0);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async handle(event: DomainEvent): Promise<void> {
|
|
489
|
+
switch (event.type) {
|
|
490
|
+
case 'memory:stored':
|
|
491
|
+
this.handleMemoryStored(event);
|
|
492
|
+
break;
|
|
493
|
+
case 'memory:retrieved':
|
|
494
|
+
this.handleMemoryRetrieved(event);
|
|
495
|
+
break;
|
|
496
|
+
case 'memory:deleted':
|
|
497
|
+
this.handleMemoryDeleted(event);
|
|
498
|
+
break;
|
|
499
|
+
case 'memory:expired':
|
|
500
|
+
this.handleMemoryExpired(event);
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
reset(): void {
|
|
506
|
+
this.memories.clear();
|
|
507
|
+
this.emit('reset');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private handleMemoryStored(event: DomainEvent): void {
|
|
511
|
+
const { memoryId, namespace, key, memoryType, size } = event.payload;
|
|
512
|
+
|
|
513
|
+
this.memories.set(memoryId as string, {
|
|
514
|
+
id: memoryId as string,
|
|
515
|
+
namespace: namespace as string,
|
|
516
|
+
key: key as string,
|
|
517
|
+
type: memoryType as string,
|
|
518
|
+
size: (size as number) || 0,
|
|
519
|
+
accessCount: 0,
|
|
520
|
+
storedAt: event.timestamp,
|
|
521
|
+
lastAccessedAt: event.timestamp,
|
|
522
|
+
deletedAt: null,
|
|
523
|
+
isDeleted: false,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
this.emit('memory:stored', { memoryId });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private handleMemoryRetrieved(event: DomainEvent): void {
|
|
530
|
+
const { memoryId, accessCount } = event.payload;
|
|
531
|
+
const memory = this.memories.get(memoryId as string);
|
|
532
|
+
|
|
533
|
+
if (memory && !memory.isDeleted) {
|
|
534
|
+
memory.accessCount = accessCount as number;
|
|
535
|
+
memory.lastAccessedAt = event.timestamp;
|
|
536
|
+
this.emit('memory:retrieved', { memoryId });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private handleMemoryDeleted(event: DomainEvent): void {
|
|
541
|
+
const { memoryId } = event.payload;
|
|
542
|
+
const memory = this.memories.get(memoryId as string);
|
|
543
|
+
|
|
544
|
+
if (memory) {
|
|
545
|
+
memory.isDeleted = true;
|
|
546
|
+
memory.deletedAt = event.timestamp;
|
|
547
|
+
this.emit('memory:deleted', { memoryId });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private handleMemoryExpired(event: DomainEvent): void {
|
|
552
|
+
const { memoryId } = event.payload;
|
|
553
|
+
const memory = this.memories.get(memoryId as string);
|
|
554
|
+
|
|
555
|
+
if (memory) {
|
|
556
|
+
memory.isDeleted = true;
|
|
557
|
+
memory.deletedAt = event.timestamp;
|
|
558
|
+
this.emit('memory:expired', { memoryId });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|