agentic-flow 1.8.11 → 1.8.14
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/CHANGELOG.md +58 -0
- package/dist/agents/claudeAgentDirect.js +168 -0
- package/dist/cli/federation-cli.d.ts +53 -0
- package/dist/cli/federation-cli.js +431 -0
- package/dist/cli-proxy.js +32 -4
- package/dist/federation/EphemeralAgent.js +258 -0
- package/dist/federation/FederationHub.js +283 -0
- package/dist/federation/FederationHubClient.js +212 -0
- package/dist/federation/FederationHubServer.js +436 -0
- package/dist/federation/SecurityManager.js +191 -0
- package/dist/federation/debug/agent-debug-stream.js +474 -0
- package/dist/federation/debug/debug-stream.js +419 -0
- package/dist/federation/index.js +12 -0
- package/dist/federation/integrations/realtime-federation.js +404 -0
- package/dist/federation/integrations/supabase-adapter-debug.js +400 -0
- package/dist/federation/integrations/supabase-adapter.js +258 -0
- package/dist/utils/cli.js +5 -0
- package/docs/architecture/FEDERATION-DATA-LIFECYCLE.md +520 -0
- package/docs/federation/AGENT-DEBUG-STREAMING.md +403 -0
- package/docs/federation/DEBUG-STREAMING-COMPLETE.md +432 -0
- package/docs/federation/DEBUG-STREAMING.md +537 -0
- package/docs/federation/DEPLOYMENT-VALIDATION-SUCCESS.md +394 -0
- package/docs/federation/DOCKER-FEDERATION-DEEP-REVIEW.md +478 -0
- package/docs/issues/ISSUE-SUPABASE-INTEGRATION.md +536 -0
- package/docs/releases/RELEASE-v1.8.13.md +426 -0
- package/docs/supabase/IMPLEMENTATION-SUMMARY.md +498 -0
- package/docs/supabase/INDEX.md +358 -0
- package/docs/supabase/QUICKSTART.md +365 -0
- package/docs/supabase/README.md +318 -0
- package/docs/supabase/SUPABASE-REALTIME-FEDERATION.md +575 -0
- package/docs/supabase/TEST-REPORT.md +446 -0
- package/docs/supabase/migrations/001_create_federation_tables.sql +339 -0
- package/docs/validation/reports/REGRESSION-TEST-V1.8.11.md +456 -0
- package/package.json +4 -1
- package/wasm/reasoningbank/reasoningbank_wasm_bg.js +2 -2
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral Agent - Short-lived agent with federated memory access
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Automatic lifecycle management (spawn → execute → learn → destroy)
|
|
6
|
+
* - Federated memory sync via QUIC
|
|
7
|
+
* - Tenant isolation with JWT authentication
|
|
8
|
+
* - Memory persistence after agent destruction
|
|
9
|
+
*/
|
|
10
|
+
import Database from 'better-sqlite3';
|
|
11
|
+
import { FederationHub } from './FederationHub.js';
|
|
12
|
+
import { SecurityManager } from './SecurityManager.js';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
export class EphemeralAgent {
|
|
15
|
+
config;
|
|
16
|
+
context;
|
|
17
|
+
hub;
|
|
18
|
+
security;
|
|
19
|
+
cleanupTimer;
|
|
20
|
+
syncTimer;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = {
|
|
23
|
+
lifetime: 300, // 5 minutes default
|
|
24
|
+
syncInterval: 5000, // 5 seconds default
|
|
25
|
+
enableEncryption: true,
|
|
26
|
+
...config
|
|
27
|
+
};
|
|
28
|
+
this.security = new SecurityManager();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Spawn a new ephemeral agent with federated memory access
|
|
32
|
+
*/
|
|
33
|
+
static async spawn(config) {
|
|
34
|
+
const agent = new EphemeralAgent(config);
|
|
35
|
+
await agent.initialize();
|
|
36
|
+
return agent;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize agent: setup DB, connect to hub, start lifecycle timers
|
|
40
|
+
*/
|
|
41
|
+
async initialize() {
|
|
42
|
+
const agentId = `eph-${this.config.tenantId}-${Date.now()}`;
|
|
43
|
+
const spawnTime = Date.now();
|
|
44
|
+
const expiresAt = spawnTime + ((this.config.lifetime || 300) * 1000);
|
|
45
|
+
logger.info('Spawning ephemeral agent', {
|
|
46
|
+
agentId,
|
|
47
|
+
tenantId: this.config.tenantId,
|
|
48
|
+
lifetime: this.config.lifetime,
|
|
49
|
+
expiresAt: new Date(expiresAt).toISOString()
|
|
50
|
+
});
|
|
51
|
+
// Initialize local database instance
|
|
52
|
+
const memoryPath = this.config.memoryPath || `:memory:`;
|
|
53
|
+
// Use better-sqlite3 for now (AgentDB integration planned)
|
|
54
|
+
const db = new Database(memoryPath);
|
|
55
|
+
// Create JWT token for authentication
|
|
56
|
+
const token = await this.security.createAgentToken({
|
|
57
|
+
agentId,
|
|
58
|
+
tenantId: this.config.tenantId,
|
|
59
|
+
expiresAt
|
|
60
|
+
});
|
|
61
|
+
// Connect to federation hub if endpoint provided
|
|
62
|
+
if (this.config.hubEndpoint) {
|
|
63
|
+
this.hub = new FederationHub({
|
|
64
|
+
endpoint: this.config.hubEndpoint,
|
|
65
|
+
agentId,
|
|
66
|
+
tenantId: this.config.tenantId,
|
|
67
|
+
token
|
|
68
|
+
});
|
|
69
|
+
await this.hub.connect();
|
|
70
|
+
// Start periodic sync
|
|
71
|
+
if (this.config.syncInterval) {
|
|
72
|
+
this.syncTimer = setInterval(async () => {
|
|
73
|
+
await this.syncWithHub();
|
|
74
|
+
}, this.config.syncInterval);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Store context
|
|
78
|
+
this.context = {
|
|
79
|
+
agentId,
|
|
80
|
+
tenantId: this.config.tenantId,
|
|
81
|
+
db,
|
|
82
|
+
spawnTime,
|
|
83
|
+
expiresAt
|
|
84
|
+
};
|
|
85
|
+
// Schedule automatic cleanup at expiration
|
|
86
|
+
const timeUntilExpiry = expiresAt - Date.now();
|
|
87
|
+
this.cleanupTimer = setTimeout(async () => {
|
|
88
|
+
logger.warn('Agent lifetime expired, auto-destroying', { agentId });
|
|
89
|
+
await this.destroy();
|
|
90
|
+
}, timeUntilExpiry);
|
|
91
|
+
logger.info('Ephemeral agent spawned successfully', {
|
|
92
|
+
agentId,
|
|
93
|
+
hubConnected: !!this.hub,
|
|
94
|
+
timeUntilExpiry
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Execute a task within the agent context
|
|
99
|
+
* Automatically syncs memory before and after execution
|
|
100
|
+
*/
|
|
101
|
+
async execute(task) {
|
|
102
|
+
if (!this.context) {
|
|
103
|
+
throw new Error('Agent not initialized. Call spawn() first.');
|
|
104
|
+
}
|
|
105
|
+
const { agentId, db } = this.context;
|
|
106
|
+
// Check if agent has expired
|
|
107
|
+
if (Date.now() >= this.context.expiresAt) {
|
|
108
|
+
throw new Error(`Agent ${agentId} has expired and cannot execute tasks`);
|
|
109
|
+
}
|
|
110
|
+
logger.info('Executing task', { agentId });
|
|
111
|
+
try {
|
|
112
|
+
// Pre-execution sync: pull latest memories from hub
|
|
113
|
+
if (this.hub) {
|
|
114
|
+
await this.syncWithHub();
|
|
115
|
+
}
|
|
116
|
+
// Execute user task
|
|
117
|
+
const result = await task(db, this.context);
|
|
118
|
+
// Post-execution sync: push new memories to hub
|
|
119
|
+
if (this.hub) {
|
|
120
|
+
await this.syncWithHub();
|
|
121
|
+
}
|
|
122
|
+
logger.info('Task execution completed', { agentId });
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
logger.error('Task execution failed', {
|
|
127
|
+
agentId,
|
|
128
|
+
error: error.message
|
|
129
|
+
});
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Query memories from federated database
|
|
135
|
+
*/
|
|
136
|
+
async queryMemories(task, k = 5) {
|
|
137
|
+
if (!this.context) {
|
|
138
|
+
throw new Error('Agent not initialized');
|
|
139
|
+
}
|
|
140
|
+
const { db, tenantId } = this.context;
|
|
141
|
+
// Query using ReasoningBank pattern search
|
|
142
|
+
const patterns = await db.patternSearch({
|
|
143
|
+
task,
|
|
144
|
+
k,
|
|
145
|
+
tenantId // Apply tenant isolation
|
|
146
|
+
});
|
|
147
|
+
return patterns || [];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Store a learning episode to persistent memory
|
|
151
|
+
*/
|
|
152
|
+
async storeEpisode(episode) {
|
|
153
|
+
if (!this.context) {
|
|
154
|
+
throw new Error('Agent not initialized');
|
|
155
|
+
}
|
|
156
|
+
const { db, agentId, tenantId } = this.context;
|
|
157
|
+
// Store episode with tenant isolation
|
|
158
|
+
await db.patternStore({
|
|
159
|
+
sessionId: agentId,
|
|
160
|
+
task: episode.task,
|
|
161
|
+
input: episode.input,
|
|
162
|
+
output: episode.output,
|
|
163
|
+
reward: episode.reward,
|
|
164
|
+
critique: episode.critique || '',
|
|
165
|
+
success: episode.reward >= 0.7,
|
|
166
|
+
tokensUsed: 0,
|
|
167
|
+
latencyMs: 0,
|
|
168
|
+
tenantId // Ensure tenant isolation
|
|
169
|
+
});
|
|
170
|
+
logger.info('Episode stored', {
|
|
171
|
+
agentId,
|
|
172
|
+
task: episode.task,
|
|
173
|
+
reward: episode.reward
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Sync local memory with federation hub
|
|
178
|
+
*/
|
|
179
|
+
async syncWithHub() {
|
|
180
|
+
if (!this.hub || !this.context) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
await this.hub.sync(this.context.db);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
logger.error('Federation sync failed', {
|
|
188
|
+
agentId: this.context.agentId,
|
|
189
|
+
error: error.message
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get remaining lifetime in seconds
|
|
195
|
+
*/
|
|
196
|
+
getRemainingLifetime() {
|
|
197
|
+
if (!this.context) {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
return Math.max(0, Math.floor((this.context.expiresAt - Date.now()) / 1000));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Destroy agent and cleanup resources
|
|
204
|
+
* Memory persists in federation hub
|
|
205
|
+
*/
|
|
206
|
+
async destroy() {
|
|
207
|
+
if (!this.context) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const { agentId, db } = this.context;
|
|
211
|
+
logger.info('Destroying ephemeral agent', { agentId });
|
|
212
|
+
// Clear timers
|
|
213
|
+
if (this.cleanupTimer) {
|
|
214
|
+
clearTimeout(this.cleanupTimer);
|
|
215
|
+
}
|
|
216
|
+
if (this.syncTimer) {
|
|
217
|
+
clearInterval(this.syncTimer);
|
|
218
|
+
}
|
|
219
|
+
// Final sync to persist any pending changes
|
|
220
|
+
if (this.hub) {
|
|
221
|
+
try {
|
|
222
|
+
await this.syncWithHub();
|
|
223
|
+
await this.hub.disconnect();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
logger.error('Final sync failed', {
|
|
227
|
+
agentId,
|
|
228
|
+
error: error.message
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Close local database
|
|
233
|
+
try {
|
|
234
|
+
await db.close?.();
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
// Ignore close errors for in-memory databases
|
|
238
|
+
}
|
|
239
|
+
// Clear context
|
|
240
|
+
this.context = undefined;
|
|
241
|
+
logger.info('Ephemeral agent destroyed', { agentId });
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if agent is still alive
|
|
245
|
+
*/
|
|
246
|
+
isAlive() {
|
|
247
|
+
if (!this.context) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
return Date.now() < this.context.expiresAt;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get agent info
|
|
254
|
+
*/
|
|
255
|
+
getInfo() {
|
|
256
|
+
return this.context || null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation Hub - QUIC-based synchronization hub for ephemeral agents
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - QUIC protocol for low-latency sync (<50ms)
|
|
6
|
+
* - mTLS for transport security
|
|
7
|
+
* - Vector clocks for conflict resolution
|
|
8
|
+
* - Hub-and-spoke topology support
|
|
9
|
+
*/
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
11
|
+
export class FederationHub {
|
|
12
|
+
config;
|
|
13
|
+
connected = false;
|
|
14
|
+
vectorClock = {};
|
|
15
|
+
lastSyncTime = 0;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = {
|
|
18
|
+
enableMTLS: true,
|
|
19
|
+
...config
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Connect to federation hub with mTLS
|
|
24
|
+
*/
|
|
25
|
+
async connect() {
|
|
26
|
+
logger.info('Connecting to federation hub', {
|
|
27
|
+
endpoint: this.config.endpoint,
|
|
28
|
+
agentId: this.config.agentId,
|
|
29
|
+
mTLS: this.config.enableMTLS
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
// QUIC connection setup (placeholder - actual implementation requires quiche or similar)
|
|
33
|
+
// For now, simulate connection with WebSocket fallback
|
|
34
|
+
// Initialize vector clock for this agent
|
|
35
|
+
this.vectorClock[this.config.agentId] = 0;
|
|
36
|
+
this.connected = true;
|
|
37
|
+
this.lastSyncTime = Date.now();
|
|
38
|
+
logger.info('Connected to federation hub', {
|
|
39
|
+
agentId: this.config.agentId
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error('Failed to connect to federation hub', {
|
|
44
|
+
endpoint: this.config.endpoint,
|
|
45
|
+
error: error.message
|
|
46
|
+
});
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Synchronize local database with federation hub
|
|
52
|
+
*
|
|
53
|
+
* 1. Pull: Get updates from hub (other agents' changes)
|
|
54
|
+
* 2. Push: Send local changes to hub
|
|
55
|
+
* 3. Resolve conflicts using vector clocks
|
|
56
|
+
*/
|
|
57
|
+
async sync(db) {
|
|
58
|
+
if (!this.connected) {
|
|
59
|
+
throw new Error('Not connected to federation hub');
|
|
60
|
+
}
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
try {
|
|
63
|
+
// Increment vector clock for this sync operation
|
|
64
|
+
this.vectorClock[this.config.agentId]++;
|
|
65
|
+
// PULL: Get updates from hub
|
|
66
|
+
const pullMessage = {
|
|
67
|
+
type: 'pull',
|
|
68
|
+
agentId: this.config.agentId,
|
|
69
|
+
tenantId: this.config.tenantId,
|
|
70
|
+
vectorClock: { ...this.vectorClock },
|
|
71
|
+
timestamp: Date.now()
|
|
72
|
+
};
|
|
73
|
+
const remoteUpdates = await this.sendSyncMessage(pullMessage);
|
|
74
|
+
if (remoteUpdates && remoteUpdates.length > 0) {
|
|
75
|
+
// Merge remote updates into local database
|
|
76
|
+
await this.mergeRemoteUpdates(db, remoteUpdates);
|
|
77
|
+
logger.info('Pulled remote updates', {
|
|
78
|
+
agentId: this.config.agentId,
|
|
79
|
+
updateCount: remoteUpdates.length
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// PUSH: Send local changes to hub
|
|
83
|
+
const localChanges = await this.getLocalChanges(db);
|
|
84
|
+
if (localChanges.length > 0) {
|
|
85
|
+
const pushMessage = {
|
|
86
|
+
type: 'push',
|
|
87
|
+
agentId: this.config.agentId,
|
|
88
|
+
tenantId: this.config.tenantId,
|
|
89
|
+
vectorClock: { ...this.vectorClock },
|
|
90
|
+
data: localChanges,
|
|
91
|
+
timestamp: Date.now()
|
|
92
|
+
};
|
|
93
|
+
await this.sendSyncMessage(pushMessage);
|
|
94
|
+
logger.info('Pushed local changes', {
|
|
95
|
+
agentId: this.config.agentId,
|
|
96
|
+
changeCount: localChanges.length
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
this.lastSyncTime = Date.now();
|
|
100
|
+
const syncDuration = Date.now() - startTime;
|
|
101
|
+
logger.info('Sync completed', {
|
|
102
|
+
agentId: this.config.agentId,
|
|
103
|
+
duration: syncDuration,
|
|
104
|
+
pullCount: remoteUpdates?.length || 0,
|
|
105
|
+
pushCount: localChanges.length
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error('Sync failed', {
|
|
110
|
+
agentId: this.config.agentId,
|
|
111
|
+
error: error.message
|
|
112
|
+
});
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Send sync message to hub via QUIC
|
|
118
|
+
*/
|
|
119
|
+
async sendSyncMessage(message) {
|
|
120
|
+
// Placeholder: Actual implementation would use QUIC transport
|
|
121
|
+
// For now, simulate with HTTP/2 as fallback
|
|
122
|
+
try {
|
|
123
|
+
// Add JWT authentication header
|
|
124
|
+
const headers = {
|
|
125
|
+
'Authorization': `Bearer ${this.config.token}`,
|
|
126
|
+
'Content-Type': 'application/json'
|
|
127
|
+
};
|
|
128
|
+
// Parse endpoint (quic://host:port -> https://host:port for fallback)
|
|
129
|
+
const httpEndpoint = this.config.endpoint
|
|
130
|
+
.replace('quic://', 'https://')
|
|
131
|
+
.replace(':4433', ':8443'); // Map QUIC port to HTTPS port
|
|
132
|
+
// Send message (placeholder - actual implementation would use QUIC)
|
|
133
|
+
// For now, log the message that would be sent
|
|
134
|
+
logger.debug('Sending sync message', {
|
|
135
|
+
type: message.type,
|
|
136
|
+
agentId: message.agentId,
|
|
137
|
+
endpoint: httpEndpoint
|
|
138
|
+
});
|
|
139
|
+
// Simulate response
|
|
140
|
+
if (message.type === 'pull') {
|
|
141
|
+
return []; // No remote updates in simulation
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
logger.error('Failed to send sync message', {
|
|
147
|
+
type: message.type,
|
|
148
|
+
error: error.message
|
|
149
|
+
});
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get local changes since last sync
|
|
155
|
+
*/
|
|
156
|
+
async getLocalChanges(db) {
|
|
157
|
+
// Query changes from local database since lastSyncTime
|
|
158
|
+
// This would query a change log table in production
|
|
159
|
+
try {
|
|
160
|
+
// Placeholder: In production, this would query:
|
|
161
|
+
// SELECT * FROM change_log WHERE timestamp > lastSyncTime AND tenantId = this.config.tenantId
|
|
162
|
+
return []; // No changes in simulation
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
logger.error('Failed to get local changes', {
|
|
166
|
+
error: error.message
|
|
167
|
+
});
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Merge remote updates into local database
|
|
173
|
+
* Uses vector clocks to detect and resolve conflicts
|
|
174
|
+
*/
|
|
175
|
+
async mergeRemoteUpdates(db, updates) {
|
|
176
|
+
for (const update of updates) {
|
|
177
|
+
try {
|
|
178
|
+
// Check vector clock for conflict detection
|
|
179
|
+
const conflict = this.detectConflict(update.vectorClock);
|
|
180
|
+
if (conflict) {
|
|
181
|
+
// Resolve conflict using CRDT rules (last-write-wins by default)
|
|
182
|
+
logger.warn('Conflict detected, applying resolution', {
|
|
183
|
+
agentId: this.config.agentId,
|
|
184
|
+
updateId: update.id
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Apply update to local database
|
|
188
|
+
await this.applyUpdate(db, update);
|
|
189
|
+
// Update local vector clock
|
|
190
|
+
this.updateVectorClock(update.vectorClock);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
logger.error('Failed to merge remote update', {
|
|
194
|
+
updateId: update.id,
|
|
195
|
+
error: error.message
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Detect conflicts using vector clocks
|
|
202
|
+
*/
|
|
203
|
+
detectConflict(remoteVectorClock) {
|
|
204
|
+
// Two updates conflict if their vector clocks are concurrent
|
|
205
|
+
// (neither is causally before the other)
|
|
206
|
+
let localDominates = false;
|
|
207
|
+
let remoteDominates = false;
|
|
208
|
+
for (const agentId in remoteVectorClock) {
|
|
209
|
+
const localTs = this.vectorClock[agentId] || 0;
|
|
210
|
+
const remoteTs = remoteVectorClock[agentId];
|
|
211
|
+
if (localTs > remoteTs) {
|
|
212
|
+
localDominates = true;
|
|
213
|
+
}
|
|
214
|
+
else if (remoteTs > localTs) {
|
|
215
|
+
remoteDominates = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Conflict if both dominate (concurrent updates)
|
|
219
|
+
return localDominates && remoteDominates;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Update local vector clock with remote timestamps
|
|
223
|
+
*/
|
|
224
|
+
updateVectorClock(remoteVectorClock) {
|
|
225
|
+
for (const agentId in remoteVectorClock) {
|
|
226
|
+
const localTs = this.vectorClock[agentId] || 0;
|
|
227
|
+
const remoteTs = remoteVectorClock[agentId];
|
|
228
|
+
// Take maximum timestamp (merge rule)
|
|
229
|
+
this.vectorClock[agentId] = Math.max(localTs, remoteTs);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Apply update to local database
|
|
234
|
+
*/
|
|
235
|
+
async applyUpdate(db, update) {
|
|
236
|
+
// Apply update based on operation type
|
|
237
|
+
// This would execute the actual database operation
|
|
238
|
+
switch (update.operation) {
|
|
239
|
+
case 'insert':
|
|
240
|
+
// Insert new record
|
|
241
|
+
break;
|
|
242
|
+
case 'update':
|
|
243
|
+
// Update existing record
|
|
244
|
+
break;
|
|
245
|
+
case 'delete':
|
|
246
|
+
// Delete record
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
logger.warn('Unknown update operation', {
|
|
250
|
+
operation: update.operation
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Disconnect from federation hub
|
|
256
|
+
*/
|
|
257
|
+
async disconnect() {
|
|
258
|
+
if (!this.connected) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
logger.info('Disconnecting from federation hub', {
|
|
262
|
+
agentId: this.config.agentId
|
|
263
|
+
});
|
|
264
|
+
// Close QUIC connection (placeholder)
|
|
265
|
+
this.connected = false;
|
|
266
|
+
logger.info('Disconnected from federation hub');
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get connection status
|
|
270
|
+
*/
|
|
271
|
+
isConnected() {
|
|
272
|
+
return this.connected;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get sync statistics
|
|
276
|
+
*/
|
|
277
|
+
getSyncStats() {
|
|
278
|
+
return {
|
|
279
|
+
lastSyncTime: this.lastSyncTime,
|
|
280
|
+
vectorClock: { ...this.vectorClock }
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|