@sesamespace/hivemind 0.10.0 → 0.11.1
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/.pnpmrc.json +1 -0
- package/AUTO-DEBUG-DESIGN.md +267 -0
- package/AUTOMATIC-MEMORY-MANAGEMENT.md +109 -0
- package/DASHBOARD-PLAN.md +206 -0
- package/MEMORY-ENHANCEMENT-PLAN.md +211 -0
- package/TOOL-USE-DESIGN.md +173 -0
- package/dist/{chunk-FBQBBAPZ.js → chunk-4C6B2AMB.js} +2 -2
- package/dist/{chunk-FK6WYXRM.js → chunk-4YXOQGQC.js} +2 -2
- package/dist/{chunk-IXBIAX76.js → chunk-K6KL2VD6.js} +2 -2
- package/dist/{chunk-IJRAVHQC.js → chunk-LWJCKTQP.js} +51 -11
- package/dist/chunk-LWJCKTQP.js.map +1 -0
- package/dist/{chunk-BHCDOHSK.js → chunk-LYL5GG2F.js} +3 -3
- package/dist/{chunk-M3A2WRXM.js → chunk-OB6OXLPC.js} +430 -2
- package/dist/chunk-OB6OXLPC.js.map +1 -0
- package/dist/{chunk-DPLCEMEC.js → chunk-ZA4NWNS6.js} +2 -2
- package/dist/commands/fleet.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/service.js +1 -1
- package/dist/commands/start.js +3 -3
- package/dist/commands/watchdog.js +3 -3
- package/dist/dashboard.html +100 -60
- package/dist/index.js +2 -2
- package/dist/main.js +7 -7
- package/dist/start.js +1 -1
- package/docs/TOOL-PARITY-PLAN.md +191 -0
- package/package.json +23 -24
- package/src/memory/dashboard-integration.ts +295 -0
- package/src/memory/index.ts +187 -0
- package/src/memory/performance-test.ts +208 -0
- package/src/memory/processors/agent-sync.ts +312 -0
- package/src/memory/processors/command-learner.ts +298 -0
- package/src/memory/processors/memory-api-client.ts +105 -0
- package/src/memory/processors/message-flow-integration.ts +168 -0
- package/src/memory/processors/research-digester.ts +204 -0
- package/test-caitlin-access.md +11 -0
- package/dist/chunk-IJRAVHQC.js.map +0 -1
- package/dist/chunk-M3A2WRXM.js.map +0 -1
- package/install.sh +0 -162
- package/packages/memory/Cargo.lock +0 -6480
- package/packages/memory/Cargo.toml +0 -21
- package/packages/memory/src/src/context.rs +0 -179
- package/packages/memory/src/src/embeddings.rs +0 -51
- package/packages/memory/src/src/main.rs +0 -887
- package/packages/memory/src/src/promotion.rs +0 -808
- package/packages/memory/src/src/scoring.rs +0 -142
- package/packages/memory/src/src/store.rs +0 -460
- package/packages/memory/src/src/tasks.rs +0 -321
- /package/dist/{chunk-FBQBBAPZ.js.map → chunk-4C6B2AMB.js.map} +0 -0
- /package/dist/{chunk-FK6WYXRM.js.map → chunk-4YXOQGQC.js.map} +0 -0
- /package/dist/{chunk-IXBIAX76.js.map → chunk-K6KL2VD6.js.map} +0 -0
- /package/dist/{chunk-BHCDOHSK.js.map → chunk-LYL5GG2F.js.map} +0 -0
- /package/dist/{chunk-DPLCEMEC.js.map → chunk-ZA4NWNS6.js.map} +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Sync - Shares knowledge between multiple Hivemind agents
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BackgroundProcessor } from './background-processor';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
|
|
9
|
+
export interface AgentKnowledge {
|
|
10
|
+
agentId: string;
|
|
11
|
+
agentName: string;
|
|
12
|
+
context: string;
|
|
13
|
+
tasks: SharedTask[];
|
|
14
|
+
insights: string[];
|
|
15
|
+
capabilities: string[];
|
|
16
|
+
lastSync: Date;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SharedTask {
|
|
20
|
+
id: string;
|
|
21
|
+
description: string;
|
|
22
|
+
status: 'active' | 'completed' | 'blocked';
|
|
23
|
+
assignedTo: string;
|
|
24
|
+
dependencies: string[];
|
|
25
|
+
progress: number;
|
|
26
|
+
notes: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SyncMessage {
|
|
30
|
+
type: 'task-update' | 'insight' | 'handoff' | 'capability';
|
|
31
|
+
fromAgent: string;
|
|
32
|
+
toAgent?: string; // Optional for broadcasts
|
|
33
|
+
payload: any;
|
|
34
|
+
timestamp: Date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class AgentSync extends BackgroundProcessor {
|
|
38
|
+
private agentId: string;
|
|
39
|
+
private agentName: string;
|
|
40
|
+
private otherAgents: Map<string, AgentKnowledge> = new Map();
|
|
41
|
+
private syncQueue: SyncMessage[] = [];
|
|
42
|
+
private sharedTasks: Map<string, SharedTask> = new Map();
|
|
43
|
+
private syncEndpoints: Map<string, string> = new Map(); // agentId -> endpoint
|
|
44
|
+
|
|
45
|
+
constructor(agentId: string, agentName: string) {
|
|
46
|
+
super('agent-sync', 30000); // Sync every 30 seconds
|
|
47
|
+
this.agentId = agentId;
|
|
48
|
+
this.agentName = agentName;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async process(): Promise<void> {
|
|
52
|
+
// Process outgoing sync messages
|
|
53
|
+
await this.processSyncQueue();
|
|
54
|
+
|
|
55
|
+
// Pull updates from other agents
|
|
56
|
+
await this.pullUpdates();
|
|
57
|
+
|
|
58
|
+
// Check for stale tasks that might need handoff
|
|
59
|
+
await this.checkHandoffs();
|
|
60
|
+
|
|
61
|
+
this.emit('sync-complete', {
|
|
62
|
+
agentsKnown: this.otherAgents.size,
|
|
63
|
+
sharedTasks: this.sharedTasks.size,
|
|
64
|
+
queuedMessages: this.syncQueue.length
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register another agent for syncing
|
|
70
|
+
*/
|
|
71
|
+
async registerAgent(agentId: string, agentName: string, endpoint: string): Promise<void> {
|
|
72
|
+
this.syncEndpoints.set(agentId, endpoint);
|
|
73
|
+
this.otherAgents.set(agentId, {
|
|
74
|
+
agentId,
|
|
75
|
+
agentName,
|
|
76
|
+
context: 'unknown',
|
|
77
|
+
tasks: [],
|
|
78
|
+
insights: [],
|
|
79
|
+
capabilities: [],
|
|
80
|
+
lastSync: new Date()
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Share a task with other agents
|
|
86
|
+
*/
|
|
87
|
+
async shareTask(task: Omit<SharedTask, 'id'>): Promise<string> {
|
|
88
|
+
const taskId = `${this.agentId}-${Date.now()}`;
|
|
89
|
+
const sharedTask: SharedTask = {
|
|
90
|
+
id: taskId,
|
|
91
|
+
...task
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.sharedTasks.set(taskId, sharedTask);
|
|
95
|
+
|
|
96
|
+
// Broadcast task to other agents
|
|
97
|
+
this.queueSync({
|
|
98
|
+
type: 'task-update',
|
|
99
|
+
fromAgent: this.agentId,
|
|
100
|
+
payload: sharedTask,
|
|
101
|
+
timestamp: new Date()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return taskId;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Share an insight learned during work
|
|
109
|
+
*/
|
|
110
|
+
async shareInsight(insight: string, context?: string): Promise<void> {
|
|
111
|
+
this.queueSync({
|
|
112
|
+
type: 'insight',
|
|
113
|
+
fromAgent: this.agentId,
|
|
114
|
+
payload: { insight, context },
|
|
115
|
+
timestamp: new Date()
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Announce a new capability
|
|
121
|
+
*/
|
|
122
|
+
async announceCapability(capability: string): Promise<void> {
|
|
123
|
+
this.queueSync({
|
|
124
|
+
type: 'capability',
|
|
125
|
+
fromAgent: this.agentId,
|
|
126
|
+
payload: { capability },
|
|
127
|
+
timestamp: new Date()
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Request handoff of a task to another agent
|
|
133
|
+
*/
|
|
134
|
+
async requestHandoff(taskId: string, toAgent: string, reason: string): Promise<void> {
|
|
135
|
+
const task = this.sharedTasks.get(taskId);
|
|
136
|
+
if (!task) return;
|
|
137
|
+
|
|
138
|
+
this.queueSync({
|
|
139
|
+
type: 'handoff',
|
|
140
|
+
fromAgent: this.agentId,
|
|
141
|
+
toAgent,
|
|
142
|
+
payload: { taskId, task, reason },
|
|
143
|
+
timestamp: new Date()
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private queueSync(message: SyncMessage): void {
|
|
148
|
+
this.syncQueue.push(message);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async processSyncQueue(): Promise<void> {
|
|
152
|
+
while (this.syncQueue.length > 0) {
|
|
153
|
+
const message = this.syncQueue.shift();
|
|
154
|
+
if (!message) continue;
|
|
155
|
+
|
|
156
|
+
if (message.toAgent) {
|
|
157
|
+
// Direct message to specific agent
|
|
158
|
+
await this.sendToAgent(message.toAgent, message);
|
|
159
|
+
} else {
|
|
160
|
+
// Broadcast to all agents
|
|
161
|
+
for (const agentId of this.syncEndpoints.keys()) {
|
|
162
|
+
if (agentId !== this.agentId) {
|
|
163
|
+
await this.sendToAgent(agentId, message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async sendToAgent(agentId: string, message: SyncMessage): Promise<void> {
|
|
171
|
+
const endpoint = this.syncEndpoints.get(agentId);
|
|
172
|
+
if (!endpoint) return;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await axios.post(`${endpoint}/sync`, message, {
|
|
176
|
+
timeout: 5000,
|
|
177
|
+
headers: {
|
|
178
|
+
'X-Agent-ID': this.agentId,
|
|
179
|
+
'X-Agent-Name': this.agentName
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
this.emit('sync-error', { agentId, error });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private async pullUpdates(): Promise<void> {
|
|
188
|
+
for (const [agentId, endpoint] of this.syncEndpoints) {
|
|
189
|
+
if (agentId === this.agentId) continue;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const response = await axios.get(`${endpoint}/sync/updates`, {
|
|
193
|
+
params: {
|
|
194
|
+
since: this.otherAgents.get(agentId)?.lastSync.toISOString()
|
|
195
|
+
},
|
|
196
|
+
timeout: 5000,
|
|
197
|
+
headers: {
|
|
198
|
+
'X-Agent-ID': this.agentId
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (response.data.updates) {
|
|
203
|
+
await this.processUpdates(agentId, response.data.updates);
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
this.emit('pull-error', { agentId, error });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async processUpdates(fromAgent: string, updates: SyncMessage[]): Promise<void> {
|
|
212
|
+
const agentKnowledge = this.otherAgents.get(fromAgent);
|
|
213
|
+
if (!agentKnowledge) return;
|
|
214
|
+
|
|
215
|
+
for (const update of updates) {
|
|
216
|
+
switch (update.type) {
|
|
217
|
+
case 'task-update':
|
|
218
|
+
const task = update.payload as SharedTask;
|
|
219
|
+
this.sharedTasks.set(task.id, task);
|
|
220
|
+
agentKnowledge.tasks = agentKnowledge.tasks.filter(t => t.id !== task.id);
|
|
221
|
+
agentKnowledge.tasks.push(task);
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'insight':
|
|
225
|
+
const { insight } = update.payload;
|
|
226
|
+
if (!agentKnowledge.insights.includes(insight)) {
|
|
227
|
+
agentKnowledge.insights.push(insight);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'capability':
|
|
232
|
+
const { capability } = update.payload;
|
|
233
|
+
if (!agentKnowledge.capabilities.includes(capability)) {
|
|
234
|
+
agentKnowledge.capabilities.push(capability);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'handoff':
|
|
239
|
+
if (update.toAgent === this.agentId) {
|
|
240
|
+
await this.handleHandoff(update.payload);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
agentKnowledge.lastSync = new Date();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private async handleHandoff(payload: any): Promise<void> {
|
|
250
|
+
const { taskId, task, reason } = payload;
|
|
251
|
+
|
|
252
|
+
// Accept the handoff
|
|
253
|
+
task.assignedTo = this.agentName;
|
|
254
|
+
this.sharedTasks.set(taskId, task);
|
|
255
|
+
|
|
256
|
+
this.emit('handoff-received', { taskId, task, reason });
|
|
257
|
+
|
|
258
|
+
// Acknowledge the handoff
|
|
259
|
+
this.queueSync({
|
|
260
|
+
type: 'task-update',
|
|
261
|
+
fromAgent: this.agentId,
|
|
262
|
+
payload: task,
|
|
263
|
+
timestamp: new Date()
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private async checkHandoffs(): Promise<void> {
|
|
268
|
+
// Check for tasks that might need handoff
|
|
269
|
+
for (const task of this.sharedTasks.values()) {
|
|
270
|
+
if (task.assignedTo === this.agentName && task.status === 'blocked') {
|
|
271
|
+
// Check if another agent might be better suited
|
|
272
|
+
const betterAgent = this.findBetterAgent(task);
|
|
273
|
+
if (betterAgent) {
|
|
274
|
+
await this.requestHandoff(
|
|
275
|
+
task.id,
|
|
276
|
+
betterAgent,
|
|
277
|
+
'Task blocked, another agent may have required capabilities'
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private findBetterAgent(task: SharedTask): string | null {
|
|
285
|
+
// Simple heuristic: find agent with relevant capabilities
|
|
286
|
+
for (const [agentId, knowledge] of this.otherAgents) {
|
|
287
|
+
// Check if agent has capabilities mentioned in task notes
|
|
288
|
+
for (const note of task.notes) {
|
|
289
|
+
for (const capability of knowledge.capabilities) {
|
|
290
|
+
if (note.toLowerCase().includes(capability.toLowerCase())) {
|
|
291
|
+
return agentId;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get knowledge about other agents
|
|
301
|
+
*/
|
|
302
|
+
async getAgentKnowledge(): Promise<AgentKnowledge[]> {
|
|
303
|
+
return Array.from(this.otherAgents.values());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get all shared tasks
|
|
308
|
+
*/
|
|
309
|
+
async getSharedTasks(): Promise<SharedTask[]> {
|
|
310
|
+
return Array.from(this.sharedTasks.values());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Learner - Tracks successful command patterns and builds a knowledge base
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BackgroundProcessor } from './background-processor';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
|
|
8
|
+
export interface CommandPattern {
|
|
9
|
+
pattern: string;
|
|
10
|
+
description: string;
|
|
11
|
+
examples: CommandExample[];
|
|
12
|
+
successRate: number;
|
|
13
|
+
category: string;
|
|
14
|
+
relatedCommands: string[];
|
|
15
|
+
lastUsed: Date;
|
|
16
|
+
usageCount: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CommandExample {
|
|
20
|
+
command: string;
|
|
21
|
+
context: string;
|
|
22
|
+
output?: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class CommandLearner extends BackgroundProcessor {
|
|
28
|
+
private patterns: Map<string, CommandPattern> = new Map();
|
|
29
|
+
private recentCommands: CommandExample[] = [];
|
|
30
|
+
private categoryIndex: Map<string, Set<string>> = new Map();
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super('command-learner', 60000); // Process every minute
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async process(): Promise<void> {
|
|
37
|
+
// Analyze recent commands to extract patterns
|
|
38
|
+
if (this.recentCommands.length >= 5) {
|
|
39
|
+
await this.extractPatterns();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Update success rates
|
|
43
|
+
this.updateSuccessRates();
|
|
44
|
+
|
|
45
|
+
// Emit current state
|
|
46
|
+
this.emit('patterns-updated', {
|
|
47
|
+
totalPatterns: this.patterns.size,
|
|
48
|
+
categories: Array.from(this.categoryIndex.keys()),
|
|
49
|
+
recentCommands: this.recentCommands.length
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Track a command execution
|
|
55
|
+
*/
|
|
56
|
+
async trackCommand(command: string, context: string, output?: string, success: boolean = true): Promise<void> {
|
|
57
|
+
const example: CommandExample = {
|
|
58
|
+
command,
|
|
59
|
+
context,
|
|
60
|
+
output: output?.substring(0, 1000), // Limit output size
|
|
61
|
+
success,
|
|
62
|
+
timestamp: new Date()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.recentCommands.push(example);
|
|
66
|
+
|
|
67
|
+
// Keep only last 100 commands
|
|
68
|
+
if (this.recentCommands.length > 100) {
|
|
69
|
+
this.recentCommands.shift();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if this matches an existing pattern
|
|
73
|
+
await this.matchToPattern(example);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async extractPatterns(): Promise<void> {
|
|
77
|
+
// Group commands by similarity
|
|
78
|
+
const groups = this.groupSimilarCommands(this.recentCommands.slice(-20));
|
|
79
|
+
|
|
80
|
+
for (const group of groups) {
|
|
81
|
+
if (group.length >= 2) {
|
|
82
|
+
const pattern = this.derivePattern(group);
|
|
83
|
+
if (pattern) {
|
|
84
|
+
this.patterns.set(pattern.pattern, pattern);
|
|
85
|
+
|
|
86
|
+
// Index by category
|
|
87
|
+
if (!this.categoryIndex.has(pattern.category)) {
|
|
88
|
+
this.categoryIndex.set(pattern.category, new Set());
|
|
89
|
+
}
|
|
90
|
+
this.categoryIndex.get(pattern.category)!.add(pattern.pattern);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private groupSimilarCommands(commands: CommandExample[]): CommandExample[][] {
|
|
97
|
+
const groups: CommandExample[][] = [];
|
|
98
|
+
const used = new Set<number>();
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < commands.length; i++) {
|
|
101
|
+
if (used.has(i)) continue;
|
|
102
|
+
|
|
103
|
+
const group = [commands[i]];
|
|
104
|
+
used.add(i);
|
|
105
|
+
|
|
106
|
+
for (let j = i + 1; j < commands.length; j++) {
|
|
107
|
+
if (used.has(j)) continue;
|
|
108
|
+
|
|
109
|
+
if (this.areSimilar(commands[i].command, commands[j].command)) {
|
|
110
|
+
group.push(commands[j]);
|
|
111
|
+
used.add(j);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (group.length > 1) {
|
|
116
|
+
groups.push(group);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return groups;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private areSimilar(cmd1: string, cmd2: string): boolean {
|
|
124
|
+
// Tokenize commands
|
|
125
|
+
const tokens1 = cmd1.split(/\s+/);
|
|
126
|
+
const tokens2 = cmd2.split(/\s+/);
|
|
127
|
+
|
|
128
|
+
// Check if base command is the same
|
|
129
|
+
if (tokens1[0] !== tokens2[0]) return false;
|
|
130
|
+
|
|
131
|
+
// Check structural similarity
|
|
132
|
+
if (Math.abs(tokens1.length - tokens2.length) > 2) return false;
|
|
133
|
+
|
|
134
|
+
// Count matching tokens (excluding file paths and specific values)
|
|
135
|
+
let matches = 0;
|
|
136
|
+
for (let i = 0; i < Math.min(tokens1.length, tokens2.length); i++) {
|
|
137
|
+
if (tokens1[i] === tokens2[i] ||
|
|
138
|
+
(this.isVariable(tokens1[i]) && this.isVariable(tokens2[i]))) {
|
|
139
|
+
matches++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return matches / Math.max(tokens1.length, tokens2.length) > 0.6;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private isVariable(token: string): boolean {
|
|
147
|
+
// Detect tokens that are likely variables (paths, URLs, IDs, etc.)
|
|
148
|
+
return token.includes('/') ||
|
|
149
|
+
token.includes('.') ||
|
|
150
|
+
token.match(/^[a-f0-9]{8,}$/i) !== null ||
|
|
151
|
+
token.match(/^\d+$/) !== null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private derivePattern(examples: CommandExample[]): CommandPattern | null {
|
|
155
|
+
if (examples.length === 0) return null;
|
|
156
|
+
|
|
157
|
+
const baseCmd = examples[0].command.split(/\s+/)[0];
|
|
158
|
+
const category = this.categorizeCommand(baseCmd);
|
|
159
|
+
|
|
160
|
+
// Find common structure
|
|
161
|
+
const tokenizedExamples = examples.map(e => e.command.split(/\s+/));
|
|
162
|
+
const patternTokens: string[] = [];
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < tokenizedExamples[0].length; i++) {
|
|
165
|
+
const tokensAtPosition = tokenizedExamples.map(t => t[i]).filter(Boolean);
|
|
166
|
+
|
|
167
|
+
if (tokensAtPosition.every(t => t === tokensAtPosition[0])) {
|
|
168
|
+
patternTokens.push(tokensAtPosition[0]);
|
|
169
|
+
} else if (tokensAtPosition.every(t => this.isVariable(t))) {
|
|
170
|
+
patternTokens.push('<variable>');
|
|
171
|
+
} else {
|
|
172
|
+
patternTokens.push('<option>');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const pattern = patternTokens.join(' ');
|
|
177
|
+
const successCount = examples.filter(e => e.success).length;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
pattern,
|
|
181
|
+
description: this.generateDescription(pattern, examples),
|
|
182
|
+
examples: examples.slice(0, 5), // Keep top 5 examples
|
|
183
|
+
successRate: successCount / examples.length,
|
|
184
|
+
category,
|
|
185
|
+
relatedCommands: this.findRelatedCommands(baseCmd),
|
|
186
|
+
lastUsed: new Date(Math.max(...examples.map(e => e.timestamp.getTime()))),
|
|
187
|
+
usageCount: examples.length
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private categorizeCommand(baseCmd: string): string {
|
|
192
|
+
const categories: Record<string, string[]> = {
|
|
193
|
+
'git': ['git', 'gh'],
|
|
194
|
+
'file': ['ls', 'cat', 'cp', 'mv', 'rm', 'find', 'grep'],
|
|
195
|
+
'process': ['ps', 'kill', 'top', 'htop'],
|
|
196
|
+
'network': ['curl', 'wget', 'ping', 'netstat'],
|
|
197
|
+
'package': ['npm', 'yarn', 'pip', 'brew'],
|
|
198
|
+
'docker': ['docker', 'docker-compose'],
|
|
199
|
+
'system': ['df', 'du', 'free', 'uname']
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const [category, commands] of Object.entries(categories)) {
|
|
203
|
+
if (commands.includes(baseCmd)) {
|
|
204
|
+
return category;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return 'other';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private generateDescription(pattern: string, examples: CommandExample[]): string {
|
|
212
|
+
const baseCmd = pattern.split(' ')[0];
|
|
213
|
+
const contexts = [...new Set(examples.map(e => e.context))];
|
|
214
|
+
|
|
215
|
+
return `${baseCmd} command used for ${contexts.join(', ')}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private findRelatedCommands(baseCmd: string): string[] {
|
|
219
|
+
const related: Record<string, string[]> = {
|
|
220
|
+
'git': ['git status', 'git log', 'git diff'],
|
|
221
|
+
'npm': ['npm install', 'npm run', 'npm test'],
|
|
222
|
+
'docker': ['docker ps', 'docker logs', 'docker exec'],
|
|
223
|
+
'curl': ['wget', 'httpie', 'fetch']
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return related[baseCmd] || [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async matchToPattern(example: CommandExample): Promise<void> {
|
|
230
|
+
for (const [patternStr, pattern] of this.patterns) {
|
|
231
|
+
if (this.matchesPattern(example.command, patternStr)) {
|
|
232
|
+
pattern.examples.push(example);
|
|
233
|
+
pattern.lastUsed = example.timestamp;
|
|
234
|
+
pattern.usageCount++;
|
|
235
|
+
|
|
236
|
+
// Keep only recent examples
|
|
237
|
+
if (pattern.examples.length > 10) {
|
|
238
|
+
pattern.examples = pattern.examples.slice(-10);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private matchesPattern(command: string, pattern: string): boolean {
|
|
247
|
+
const cmdTokens = command.split(/\s+/);
|
|
248
|
+
const patternTokens = pattern.split(/\s+/);
|
|
249
|
+
|
|
250
|
+
if (cmdTokens.length !== patternTokens.length) return false;
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < cmdTokens.length; i++) {
|
|
253
|
+
if (patternTokens[i] === '<variable>' || patternTokens[i] === '<option>') {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (cmdTokens[i] !== patternTokens[i]) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private updateSuccessRates(): void {
|
|
265
|
+
for (const pattern of this.patterns.values()) {
|
|
266
|
+
const recentExamples = pattern.examples.slice(-10);
|
|
267
|
+
const successCount = recentExamples.filter(e => e.success).length;
|
|
268
|
+
pattern.successRate = successCount / recentExamples.length;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get command suggestions for a given context
|
|
274
|
+
*/
|
|
275
|
+
async getSuggestions(context: string, category?: string): Promise<CommandPattern[]> {
|
|
276
|
+
let patterns = Array.from(this.patterns.values());
|
|
277
|
+
|
|
278
|
+
// Filter by category if specified
|
|
279
|
+
if (category && this.categoryIndex.has(category)) {
|
|
280
|
+
const categoryPatterns = this.categoryIndex.get(category)!;
|
|
281
|
+
patterns = patterns.filter(p => categoryPatterns.has(p.pattern));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Filter by context relevance
|
|
285
|
+
patterns = patterns.filter(p =>
|
|
286
|
+
p.examples.some(e => e.context.toLowerCase().includes(context.toLowerCase()))
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Sort by success rate and recency
|
|
290
|
+
return patterns
|
|
291
|
+
.sort((a, b) => {
|
|
292
|
+
const scoreA = a.successRate * 0.7 + (Date.now() - a.lastUsed.getTime() < 3600000 ? 0.3 : 0);
|
|
293
|
+
const scoreB = b.successRate * 0.7 + (Date.now() - b.lastUsed.getTime() < 3600000 ? 0.3 : 0);
|
|
294
|
+
return scoreB - scoreA;
|
|
295
|
+
})
|
|
296
|
+
.slice(0, 5);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for interacting with the Hivemind memory daemon API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
|
|
8
|
+
export interface MemorySearchResult {
|
|
9
|
+
content: string;
|
|
10
|
+
similarity: number;
|
|
11
|
+
metadata?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface L3Knowledge {
|
|
15
|
+
insight: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
sources: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MemoryStats {
|
|
21
|
+
episodes: number;
|
|
22
|
+
contexts: string[];
|
|
23
|
+
l3Entries: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class MemoryAPIClient extends EventEmitter {
|
|
27
|
+
private baseURL: string;
|
|
28
|
+
private context: string;
|
|
29
|
+
|
|
30
|
+
constructor(baseURL: string = 'http://localhost:3434', context: string = 'global') {
|
|
31
|
+
super();
|
|
32
|
+
this.baseURL = baseURL;
|
|
33
|
+
this.context = context;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async health(): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
const response = await axios.get(`${this.baseURL}/health`);
|
|
39
|
+
return response.status === 200;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.emit('error', error);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async search(query: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
47
|
+
try {
|
|
48
|
+
const response = await axios.get(`${this.baseURL}/search`, {
|
|
49
|
+
params: { q: query, context: this.context, limit }
|
|
50
|
+
});
|
|
51
|
+
return response.data.results || [];
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.emit('error', error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async crossSearch(query: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
59
|
+
try {
|
|
60
|
+
const response = await axios.get(`${this.baseURL}/cross-search`, {
|
|
61
|
+
params: { q: query, limit }
|
|
62
|
+
});
|
|
63
|
+
return response.data.results || [];
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.emit('error', error);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getL3Knowledge(): Promise<L3Knowledge[]> {
|
|
71
|
+
try {
|
|
72
|
+
const response = await axios.get(`${this.baseURL}/promotion/l3`, {
|
|
73
|
+
params: { context: this.context }
|
|
74
|
+
});
|
|
75
|
+
return response.data.knowledge || [];
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.emit('error', error);
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getStats(): Promise<MemoryStats | null> {
|
|
83
|
+
try {
|
|
84
|
+
const response = await axios.get(`${this.baseURL}/stats`);
|
|
85
|
+
return response.data;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.emit('error', error);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async store(content: string, metadata?: Record<string, any>): Promise<boolean> {
|
|
93
|
+
try {
|
|
94
|
+
const response = await axios.post(`${this.baseURL}/store`, {
|
|
95
|
+
context: this.context,
|
|
96
|
+
content,
|
|
97
|
+
metadata
|
|
98
|
+
});
|
|
99
|
+
return response.status === 200;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.emit('error', error);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|