claude-flow 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/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict resolution mechanisms for multi-agent coordination
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ILogger } from '../core/logger.ts';
|
|
6
|
+
import { IEventBus } from '../core/event-bus.ts';
|
|
7
|
+
import { Task, Resource } from '../utils/types.ts';
|
|
8
|
+
|
|
9
|
+
export interface ResourceConflict {
|
|
10
|
+
id: string;
|
|
11
|
+
resourceId: string;
|
|
12
|
+
agents: string[];
|
|
13
|
+
timestamp: Date;
|
|
14
|
+
resolved: boolean;
|
|
15
|
+
resolution?: ConflictResolution;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TaskConflict {
|
|
19
|
+
id: string;
|
|
20
|
+
taskId: string;
|
|
21
|
+
agents: string[];
|
|
22
|
+
type: 'assignment' | 'dependency' | 'output';
|
|
23
|
+
timestamp: Date;
|
|
24
|
+
resolved: boolean;
|
|
25
|
+
resolution?: ConflictResolution;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ConflictResolution {
|
|
29
|
+
type: 'priority' | 'timestamp' | 'vote' | 'manual' | 'retry';
|
|
30
|
+
winner?: string;
|
|
31
|
+
losers?: string[];
|
|
32
|
+
reason: string;
|
|
33
|
+
timestamp: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ConflictResolutionStrategy {
|
|
37
|
+
name: string;
|
|
38
|
+
resolve(conflict: ResourceConflict | TaskConflict, context: any): Promise<ConflictResolution>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Priority-based resolution strategy
|
|
43
|
+
*/
|
|
44
|
+
export class PriorityResolutionStrategy implements ConflictResolutionStrategy {
|
|
45
|
+
name = 'priority';
|
|
46
|
+
|
|
47
|
+
async resolve(
|
|
48
|
+
conflict: ResourceConflict | TaskConflict,
|
|
49
|
+
context: { agentPriorities: Map<string, number> },
|
|
50
|
+
): Promise<ConflictResolution> {
|
|
51
|
+
const priorities = conflict.agents.map(agentId => ({
|
|
52
|
+
agentId,
|
|
53
|
+
priority: context.agentPriorities.get(agentId) || 0,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Sort by priority (descending)
|
|
57
|
+
priorities.sort((a, b) => b.priority - a.priority);
|
|
58
|
+
|
|
59
|
+
const winner = priorities[0].agentId;
|
|
60
|
+
const losers = priorities.slice(1).map(p => p.agentId);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
type: 'priority',
|
|
64
|
+
winner,
|
|
65
|
+
losers,
|
|
66
|
+
reason: `Agent ${winner} has highest priority (${priorities[0].priority})`,
|
|
67
|
+
timestamp: new Date(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* First-come-first-served resolution strategy
|
|
74
|
+
*/
|
|
75
|
+
export class TimestampResolutionStrategy implements ConflictResolutionStrategy {
|
|
76
|
+
name = 'timestamp';
|
|
77
|
+
|
|
78
|
+
async resolve(
|
|
79
|
+
conflict: ResourceConflict | TaskConflict,
|
|
80
|
+
context: { requestTimestamps: Map<string, Date> },
|
|
81
|
+
): Promise<ConflictResolution> {
|
|
82
|
+
const timestamps = conflict.agents.map(agentId => ({
|
|
83
|
+
agentId,
|
|
84
|
+
timestamp: context.requestTimestamps.get(agentId) || new Date(),
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
// Sort by timestamp (ascending - earliest first)
|
|
88
|
+
timestamps.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
89
|
+
|
|
90
|
+
const winner = timestamps[0].agentId;
|
|
91
|
+
const losers = timestamps.slice(1).map(t => t.agentId);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
type: 'timestamp',
|
|
95
|
+
winner,
|
|
96
|
+
losers,
|
|
97
|
+
reason: `Agent ${winner} made the earliest request`,
|
|
98
|
+
timestamp: new Date(),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Voting-based resolution strategy (for multi-agent consensus)
|
|
105
|
+
*/
|
|
106
|
+
export class VotingResolutionStrategy implements ConflictResolutionStrategy {
|
|
107
|
+
name = 'vote';
|
|
108
|
+
|
|
109
|
+
async resolve(
|
|
110
|
+
conflict: ResourceConflict | TaskConflict,
|
|
111
|
+
context: { votes: Map<string, string[]> }, // agentId -> votes for that agent
|
|
112
|
+
): Promise<ConflictResolution> {
|
|
113
|
+
const voteCounts = new Map<string, number>();
|
|
114
|
+
|
|
115
|
+
// Count votes
|
|
116
|
+
for (const [agentId, voters] of context.votes) {
|
|
117
|
+
voteCounts.set(agentId, voters.length);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Find winner
|
|
121
|
+
let maxVotes = 0;
|
|
122
|
+
let winner = '';
|
|
123
|
+
const losers: string[] = [];
|
|
124
|
+
|
|
125
|
+
for (const [agentId, votes] of voteCounts) {
|
|
126
|
+
if (votes > maxVotes) {
|
|
127
|
+
if (winner) {
|
|
128
|
+
losers.push(winner);
|
|
129
|
+
}
|
|
130
|
+
maxVotes = votes;
|
|
131
|
+
winner = agentId;
|
|
132
|
+
} else {
|
|
133
|
+
losers.push(agentId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
type: 'vote',
|
|
139
|
+
winner,
|
|
140
|
+
losers,
|
|
141
|
+
reason: `Agent ${winner} received the most votes (${maxVotes})`,
|
|
142
|
+
timestamp: new Date(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Conflict resolution manager
|
|
149
|
+
*/
|
|
150
|
+
export class ConflictResolver {
|
|
151
|
+
private strategies = new Map<string, ConflictResolutionStrategy>();
|
|
152
|
+
private conflicts = new Map<string, ResourceConflict | TaskConflict>();
|
|
153
|
+
private resolutionHistory: ConflictResolution[] = [];
|
|
154
|
+
|
|
155
|
+
constructor(
|
|
156
|
+
private logger: ILogger,
|
|
157
|
+
private eventBus: IEventBus,
|
|
158
|
+
) {
|
|
159
|
+
// Register default strategies
|
|
160
|
+
this.registerStrategy(new PriorityResolutionStrategy());
|
|
161
|
+
this.registerStrategy(new TimestampResolutionStrategy());
|
|
162
|
+
this.registerStrategy(new VotingResolutionStrategy());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Register a conflict resolution strategy
|
|
167
|
+
*/
|
|
168
|
+
registerStrategy(strategy: ConflictResolutionStrategy): void {
|
|
169
|
+
this.strategies.set(strategy.name, strategy);
|
|
170
|
+
this.logger.info('Registered conflict resolution strategy', { name: strategy.name });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Report a resource conflict
|
|
175
|
+
*/
|
|
176
|
+
async reportResourceConflict(
|
|
177
|
+
resourceId: string,
|
|
178
|
+
agents: string[],
|
|
179
|
+
): Promise<ResourceConflict> {
|
|
180
|
+
const conflict: ResourceConflict = {
|
|
181
|
+
id: `conflict-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
182
|
+
resourceId,
|
|
183
|
+
agents,
|
|
184
|
+
timestamp: new Date(),
|
|
185
|
+
resolved: false,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
this.conflicts.set(conflict.id, conflict);
|
|
189
|
+
this.logger.warn('Resource conflict reported', conflict);
|
|
190
|
+
|
|
191
|
+
// Emit conflict event
|
|
192
|
+
this.eventBus.emit('conflict:resource', conflict);
|
|
193
|
+
|
|
194
|
+
return conflict;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Report a task conflict
|
|
199
|
+
*/
|
|
200
|
+
async reportTaskConflict(
|
|
201
|
+
taskId: string,
|
|
202
|
+
agents: string[],
|
|
203
|
+
type: TaskConflict['type'],
|
|
204
|
+
): Promise<TaskConflict> {
|
|
205
|
+
const conflict: TaskConflict = {
|
|
206
|
+
id: `conflict-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
207
|
+
taskId,
|
|
208
|
+
agents,
|
|
209
|
+
type,
|
|
210
|
+
timestamp: new Date(),
|
|
211
|
+
resolved: false,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
this.conflicts.set(conflict.id, conflict);
|
|
215
|
+
this.logger.warn('Task conflict reported', conflict);
|
|
216
|
+
|
|
217
|
+
// Emit conflict event
|
|
218
|
+
this.eventBus.emit('conflict:task', conflict);
|
|
219
|
+
|
|
220
|
+
return conflict;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Resolve a conflict using a specific strategy
|
|
225
|
+
*/
|
|
226
|
+
async resolveConflict(
|
|
227
|
+
conflictId: string,
|
|
228
|
+
strategyName: string,
|
|
229
|
+
context: any,
|
|
230
|
+
): Promise<ConflictResolution> {
|
|
231
|
+
const conflict = this.conflicts.get(conflictId);
|
|
232
|
+
if (!conflict) {
|
|
233
|
+
throw new Error(`Conflict not found: ${conflictId}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (conflict.resolved) {
|
|
237
|
+
throw new Error(`Conflict already resolved: ${conflictId}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const strategy = this.strategies.get(strategyName);
|
|
241
|
+
if (!strategy) {
|
|
242
|
+
throw new Error(`Strategy not found: ${strategyName}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Resolve the conflict
|
|
246
|
+
const resolution = await strategy.resolve(conflict, context);
|
|
247
|
+
|
|
248
|
+
// Update conflict
|
|
249
|
+
conflict.resolved = true;
|
|
250
|
+
conflict.resolution = resolution;
|
|
251
|
+
|
|
252
|
+
// Store in history
|
|
253
|
+
this.resolutionHistory.push(resolution);
|
|
254
|
+
|
|
255
|
+
// Emit resolution event
|
|
256
|
+
this.eventBus.emit('conflict:resolved', {
|
|
257
|
+
conflict,
|
|
258
|
+
resolution,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
this.logger.info('Conflict resolved', {
|
|
262
|
+
conflictId,
|
|
263
|
+
strategy: strategyName,
|
|
264
|
+
resolution,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return resolution;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Auto-resolve conflicts based on configuration
|
|
272
|
+
*/
|
|
273
|
+
async autoResolve(
|
|
274
|
+
conflictId: string,
|
|
275
|
+
preferredStrategy: string = 'priority',
|
|
276
|
+
): Promise<ConflictResolution> {
|
|
277
|
+
const conflict = this.conflicts.get(conflictId);
|
|
278
|
+
if (!conflict) {
|
|
279
|
+
throw new Error(`Conflict not found: ${conflictId}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Build context based on conflict type
|
|
283
|
+
let context: any = {};
|
|
284
|
+
|
|
285
|
+
if (preferredStrategy === 'priority') {
|
|
286
|
+
// In a real implementation, fetch agent priorities from configuration
|
|
287
|
+
context.agentPriorities = new Map(
|
|
288
|
+
conflict.agents.map((id, index) => [id, conflict.agents.length - index])
|
|
289
|
+
);
|
|
290
|
+
} else if (preferredStrategy === 'timestamp') {
|
|
291
|
+
// In a real implementation, fetch request timestamps
|
|
292
|
+
context.requestTimestamps = new Map(
|
|
293
|
+
conflict.agents.map((id, index) => [id, new Date(Date.now() - index * 1000)])
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return this.resolveConflict(conflictId, preferredStrategy, context);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get active conflicts
|
|
302
|
+
*/
|
|
303
|
+
getActiveConflicts(): Array<ResourceConflict | TaskConflict> {
|
|
304
|
+
return Array.from(this.conflicts.values()).filter(c => !c.resolved);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get conflict history
|
|
309
|
+
*/
|
|
310
|
+
getConflictHistory(limit?: number): ConflictResolution[] {
|
|
311
|
+
if (limit) {
|
|
312
|
+
return this.resolutionHistory.slice(-limit);
|
|
313
|
+
}
|
|
314
|
+
return [...this.resolutionHistory];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Clear resolved conflicts older than a certain age
|
|
319
|
+
*/
|
|
320
|
+
cleanupOldConflicts(maxAgeMs: number): number {
|
|
321
|
+
const now = Date.now();
|
|
322
|
+
let removed = 0;
|
|
323
|
+
|
|
324
|
+
for (const [id, conflict] of this.conflicts) {
|
|
325
|
+
if (conflict.resolved && now - conflict.timestamp.getTime() > maxAgeMs) {
|
|
326
|
+
this.conflicts.delete(id);
|
|
327
|
+
removed++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Also cleanup old history
|
|
332
|
+
const cutoffTime = now - maxAgeMs;
|
|
333
|
+
this.resolutionHistory = this.resolutionHistory.filter(
|
|
334
|
+
r => r.timestamp.getTime() > cutoffTime
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
return removed;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get conflict statistics
|
|
342
|
+
*/
|
|
343
|
+
getStats(): Record<string, unknown> {
|
|
344
|
+
const stats = {
|
|
345
|
+
totalConflicts: this.conflicts.size,
|
|
346
|
+
activeConflicts: 0,
|
|
347
|
+
resolvedConflicts: 0,
|
|
348
|
+
resolutionsByStrategy: {} as Record<string, number>,
|
|
349
|
+
conflictsByType: {
|
|
350
|
+
resource: 0,
|
|
351
|
+
task: 0,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
for (const conflict of this.conflicts.values()) {
|
|
356
|
+
if (conflict.resolved) {
|
|
357
|
+
stats.resolvedConflicts++;
|
|
358
|
+
|
|
359
|
+
if (conflict.resolution) {
|
|
360
|
+
const strategy = conflict.resolution.type;
|
|
361
|
+
stats.resolutionsByStrategy[strategy] =
|
|
362
|
+
(stats.resolutionsByStrategy[strategy] || 0) + 1;
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
stats.activeConflicts++;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if ('resourceId' in conflict) {
|
|
369
|
+
stats.conflictsByType.resource++;
|
|
370
|
+
} else {
|
|
371
|
+
stats.conflictsByType.task++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return stats;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Optimistic concurrency control for resource updates
|
|
381
|
+
*/
|
|
382
|
+
export class OptimisticLockManager {
|
|
383
|
+
private versions = new Map<string, number>();
|
|
384
|
+
private locks = new Map<string, { version: number; holder: string; timestamp: Date }>();
|
|
385
|
+
|
|
386
|
+
constructor(private logger: ILogger) {}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Acquire an optimistic lock
|
|
390
|
+
*/
|
|
391
|
+
acquireLock(resourceId: string, agentId: string): number {
|
|
392
|
+
const currentVersion = this.versions.get(resourceId) || 0;
|
|
393
|
+
|
|
394
|
+
this.locks.set(resourceId, {
|
|
395
|
+
version: currentVersion,
|
|
396
|
+
holder: agentId,
|
|
397
|
+
timestamp: new Date(),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
this.logger.debug('Optimistic lock acquired', {
|
|
401
|
+
resourceId,
|
|
402
|
+
agentId,
|
|
403
|
+
version: currentVersion,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
return currentVersion;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Validate and update with optimistic lock
|
|
411
|
+
*/
|
|
412
|
+
validateAndUpdate(
|
|
413
|
+
resourceId: string,
|
|
414
|
+
agentId: string,
|
|
415
|
+
expectedVersion: number,
|
|
416
|
+
): boolean {
|
|
417
|
+
const currentVersion = this.versions.get(resourceId) || 0;
|
|
418
|
+
const lock = this.locks.get(resourceId);
|
|
419
|
+
|
|
420
|
+
// Check if versions match
|
|
421
|
+
if (currentVersion !== expectedVersion) {
|
|
422
|
+
this.logger.warn('Optimistic lock conflict', {
|
|
423
|
+
resourceId,
|
|
424
|
+
agentId,
|
|
425
|
+
expectedVersion,
|
|
426
|
+
currentVersion,
|
|
427
|
+
});
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check if this agent holds the lock
|
|
432
|
+
if (!lock || lock.holder !== agentId) {
|
|
433
|
+
this.logger.warn('Agent does not hold lock', {
|
|
434
|
+
resourceId,
|
|
435
|
+
agentId,
|
|
436
|
+
});
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Update version
|
|
441
|
+
this.versions.set(resourceId, currentVersion + 1);
|
|
442
|
+
this.locks.delete(resourceId);
|
|
443
|
+
|
|
444
|
+
this.logger.debug('Optimistic update successful', {
|
|
445
|
+
resourceId,
|
|
446
|
+
agentId,
|
|
447
|
+
newVersion: currentVersion + 1,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Release a lock without updating
|
|
455
|
+
*/
|
|
456
|
+
releaseLock(resourceId: string, agentId: string): void {
|
|
457
|
+
const lock = this.locks.get(resourceId);
|
|
458
|
+
|
|
459
|
+
if (lock && lock.holder === agentId) {
|
|
460
|
+
this.locks.delete(resourceId);
|
|
461
|
+
this.logger.debug('Optimistic lock released', {
|
|
462
|
+
resourceId,
|
|
463
|
+
agentId,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Clean up stale locks
|
|
470
|
+
*/
|
|
471
|
+
cleanupStaleLocks(maxAgeMs: number): number {
|
|
472
|
+
const now = Date.now();
|
|
473
|
+
let removed = 0;
|
|
474
|
+
|
|
475
|
+
for (const [resourceId, lock] of this.locks) {
|
|
476
|
+
if (now - lock.timestamp.getTime() > maxAgeMs) {
|
|
477
|
+
this.locks.delete(resourceId);
|
|
478
|
+
removed++;
|
|
479
|
+
|
|
480
|
+
this.logger.warn('Removed stale lock', {
|
|
481
|
+
resourceId,
|
|
482
|
+
holder: lock.holder,
|
|
483
|
+
age: now - lock.timestamp.getTime(),
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return removed;
|
|
489
|
+
}
|
|
490
|
+
}
|