@simonfestl/husky-cli 1.6.3 → 1.6.4

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.
@@ -3,9 +3,9 @@ export type AgentType = typeof AGENT_TYPES[number];
3
3
  export interface Memory {
4
4
  id: string;
5
5
  agent: string;
6
+ agentType?: string;
6
7
  content: string;
7
8
  tags: string[];
8
- embedding?: number[];
9
9
  createdAt: Date;
10
10
  updatedAt: Date;
11
11
  metadata?: Record<string, unknown>;
@@ -21,19 +21,17 @@ export interface AgentBrainOptions {
21
21
  }
22
22
  export declare function isValidAgentType(value: string | undefined): value is AgentType;
23
23
  export declare function getAgentType(): AgentType | undefined;
24
- export declare function getDatabaseName(agentType?: AgentType): string;
25
24
  export declare class AgentBrain {
26
- private db;
25
+ private qdrant;
27
26
  private embeddings;
28
27
  private agentId;
29
28
  private agentType?;
30
- private collectionPath;
31
- private databaseName;
32
29
  constructor(agentIdOrOptions: string | AgentBrainOptions, projectId?: string);
33
30
  getDatabaseInfo(): {
34
31
  agentType?: AgentType;
35
32
  databaseName: string;
36
33
  };
34
+ private ensureCollection;
37
35
  remember(content: string, tags?: string[], metadata?: Record<string, unknown>): Promise<string>;
38
36
  recall(query: string, limit?: number, minScore?: number): Promise<RecallResult[]>;
39
37
  recallByTags(tags: string[], limit?: number): Promise<Memory[]>;
@@ -43,6 +41,5 @@ export declare class AgentBrain {
43
41
  count: number;
44
42
  tags: Record<string, number>;
45
43
  }>;
46
- private cosineSimilarity;
47
44
  }
48
45
  export default AgentBrain;
@@ -1,19 +1,10 @@
1
- import { initializeApp, getApps } from 'firebase-admin/app';
2
- import { getFirestore, Timestamp } from 'firebase-admin/firestore';
1
+ import { QdrantClient } from './qdrant.js';
3
2
  import { EmbeddingService } from './embeddings.js';
4
3
  import { getConfig } from '../../commands/config.js';
5
- const BRAIN_COLLECTION = 'agent_brains';
6
- const DEFAULT_DATABASE = '(default)';
4
+ import { randomUUID } from 'crypto';
5
+ const MEMORIES_COLLECTION = 'agent-memories';
6
+ const VECTOR_SIZE = 768;
7
7
  export const AGENT_TYPES = ['support', 'claude', 'gotess', 'supervisor', 'worker'];
8
- const AGENT_DB_MAP = {
9
- support: 'support-brain-db',
10
- claude: 'claude-brain-db',
11
- gotess: 'gotess-brain-db',
12
- supervisor: 'supervisor-brain-db',
13
- worker: 'worker-brain-db',
14
- };
15
- const firestoreCache = new Map();
16
- let firebaseInitialized = false;
17
8
  export function isValidAgentType(value) {
18
9
  return value !== undefined && AGENT_TYPES.includes(value);
19
10
  }
@@ -29,19 +20,11 @@ export function getAgentType() {
29
20
  }
30
21
  return undefined;
31
22
  }
32
- export function getDatabaseName(agentType) {
33
- if (!agentType) {
34
- return DEFAULT_DATABASE;
35
- }
36
- return AGENT_DB_MAP[agentType];
37
- }
38
23
  export class AgentBrain {
39
- db;
24
+ qdrant;
40
25
  embeddings;
41
26
  agentId;
42
27
  agentType;
43
- collectionPath;
44
- databaseName;
45
28
  constructor(agentIdOrOptions, projectId) {
46
29
  let options;
47
30
  if (typeof agentIdOrOptions === 'string') {
@@ -53,144 +36,164 @@ export class AgentBrain {
53
36
  const config = getConfig();
54
37
  this.agentId = options.agentId;
55
38
  this.agentType = options.agentType || getAgentType();
56
- this.databaseName = getDatabaseName(this.agentType);
39
+ this.qdrant = QdrantClient.fromConfig();
57
40
  const gcpProject = options.projectId || config.gcpProjectId || process.env.GOOGLE_CLOUD_PROJECT || 'tigerv0';
58
- if (!firebaseInitialized && getApps().length === 0) {
59
- initializeApp({ projectId: gcpProject });
60
- firebaseInitialized = true;
61
- }
62
- const cacheKey = `${gcpProject}:${this.databaseName}`;
63
- let db = firestoreCache.get(cacheKey);
64
- if (!db) {
65
- if (this.databaseName === DEFAULT_DATABASE) {
66
- db = getFirestore();
67
- }
68
- else {
69
- db = getFirestore(this.databaseName);
70
- }
71
- firestoreCache.set(cacheKey, db);
72
- }
73
- this.db = db;
74
41
  this.embeddings = new EmbeddingService({
75
42
  projectId: gcpProject,
76
43
  location: config.gcpLocation || 'europe-west1'
77
44
  });
78
- this.collectionPath = `${BRAIN_COLLECTION}/${this.agentId}/memories`;
79
45
  }
80
46
  getDatabaseInfo() {
81
47
  return {
82
48
  agentType: this.agentType,
83
- databaseName: this.databaseName,
49
+ databaseName: `qdrant:${MEMORIES_COLLECTION}`,
84
50
  };
85
51
  }
52
+ async ensureCollection() {
53
+ try {
54
+ await this.qdrant.getCollection(MEMORIES_COLLECTION);
55
+ }
56
+ catch {
57
+ await this.qdrant.createCollection(MEMORIES_COLLECTION, VECTOR_SIZE);
58
+ }
59
+ }
86
60
  async remember(content, tags = [], metadata) {
61
+ await this.ensureCollection();
87
62
  const embedding = await this.embeddings.embed(content);
88
- const memory = {
63
+ const id = randomUUID();
64
+ const now = new Date().toISOString();
65
+ await this.qdrant.upsertOne(MEMORIES_COLLECTION, id, embedding, {
89
66
  agent: this.agentId,
67
+ agentType: this.agentType || 'default',
90
68
  content,
91
69
  tags,
92
- embedding,
93
70
  metadata: metadata || {},
94
- createdAt: Timestamp.now(),
95
- updatedAt: Timestamp.now(),
96
- };
97
- const ref = await this.db.collection(this.collectionPath).add(memory);
98
- return ref.id;
71
+ createdAt: now,
72
+ updatedAt: now,
73
+ });
74
+ return id;
99
75
  }
100
76
  async recall(query, limit = 5, minScore = 0.5) {
77
+ await this.ensureCollection();
101
78
  const queryEmbedding = await this.embeddings.embed(query);
102
- const snapshot = await this.db.collection(this.collectionPath).get();
103
- const results = [];
104
- for (const doc of snapshot.docs) {
105
- const data = doc.data();
106
- if (!data.embedding)
107
- continue;
108
- const score = this.cosineSimilarity(queryEmbedding, data.embedding);
109
- if (score >= minScore) {
110
- results.push({
111
- memory: {
112
- id: doc.id,
113
- agent: data.agent,
114
- content: data.content,
115
- tags: data.tags || [],
116
- createdAt: data.createdAt?.toDate() || new Date(),
117
- updatedAt: data.updatedAt?.toDate() || new Date(),
118
- metadata: data.metadata,
119
- },
120
- score,
121
- });
122
- }
79
+ const filter = {
80
+ must: [
81
+ { key: 'agent', match: { value: this.agentId } }
82
+ ]
83
+ };
84
+ if (this.agentType) {
85
+ filter.must.push({ key: 'agentType', match: { value: this.agentType } });
123
86
  }
124
- return results
125
- .sort((a, b) => b.score - a.score)
126
- .slice(0, limit);
87
+ const results = await this.qdrant.search(MEMORIES_COLLECTION, queryEmbedding, limit, {
88
+ filter,
89
+ scoreThreshold: minScore,
90
+ });
91
+ return results.map(r => ({
92
+ memory: {
93
+ id: String(r.id),
94
+ agent: String(r.payload?.agent || ''),
95
+ agentType: String(r.payload?.agentType || ''),
96
+ content: String(r.payload?.content || ''),
97
+ tags: r.payload?.tags || [],
98
+ createdAt: new Date(String(r.payload?.createdAt || new Date().toISOString())),
99
+ updatedAt: new Date(String(r.payload?.updatedAt || new Date().toISOString())),
100
+ metadata: r.payload?.metadata,
101
+ },
102
+ score: r.score,
103
+ }));
127
104
  }
128
105
  async recallByTags(tags, limit = 10) {
129
106
  if (tags.length === 0) {
130
107
  return [];
131
108
  }
132
- const snapshot = await this.db
133
- .collection(this.collectionPath)
134
- .where('tags', 'array-contains-any', tags)
135
- .orderBy('createdAt', 'desc')
136
- .limit(limit)
137
- .get();
138
- return snapshot.docs.map(doc => ({
139
- id: doc.id,
140
- agent: doc.data().agent,
141
- content: doc.data().content,
142
- tags: doc.data().tags || [],
143
- createdAt: doc.data().createdAt?.toDate() || new Date(),
144
- updatedAt: doc.data().updatedAt?.toDate() || new Date(),
145
- metadata: doc.data().metadata,
146
- }));
109
+ await this.ensureCollection();
110
+ const filter = {
111
+ must: [
112
+ { key: 'agent', match: { value: this.agentId } },
113
+ {
114
+ should: tags.map(tag => ({
115
+ key: 'tags',
116
+ match: { any: [tag] }
117
+ }))
118
+ }
119
+ ]
120
+ };
121
+ const results = await this.qdrant.scroll(MEMORIES_COLLECTION, {
122
+ filter,
123
+ limit,
124
+ with_payload: true,
125
+ });
126
+ return results
127
+ .map(r => ({
128
+ id: String(r.id),
129
+ agent: String(r.payload?.agent || ''),
130
+ agentType: String(r.payload?.agentType || ''),
131
+ content: String(r.payload?.content || ''),
132
+ tags: r.payload?.tags || [],
133
+ createdAt: new Date(String(r.payload?.createdAt || new Date().toISOString())),
134
+ updatedAt: new Date(String(r.payload?.updatedAt || new Date().toISOString())),
135
+ metadata: r.payload?.metadata,
136
+ }))
137
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
147
138
  }
148
139
  async forget(memoryId) {
149
- await this.db.collection(this.collectionPath).doc(memoryId).delete();
140
+ await this.ensureCollection();
141
+ await this.qdrant.deletePoints(MEMORIES_COLLECTION, [memoryId]);
150
142
  }
151
143
  async listMemories(limit = 20) {
152
- const snapshot = await this.db
153
- .collection(this.collectionPath)
154
- .orderBy('createdAt', 'desc')
155
- .limit(limit)
156
- .get();
157
- return snapshot.docs.map(doc => ({
158
- id: doc.id,
159
- agent: doc.data().agent,
160
- content: doc.data().content,
161
- tags: doc.data().tags || [],
162
- createdAt: doc.data().createdAt?.toDate() || new Date(),
163
- updatedAt: doc.data().updatedAt?.toDate() || new Date(),
164
- metadata: doc.data().metadata,
165
- }));
144
+ await this.ensureCollection();
145
+ const filter = {
146
+ must: [
147
+ { key: 'agent', match: { value: this.agentId } }
148
+ ]
149
+ };
150
+ if (this.agentType) {
151
+ filter.must.push({ key: 'agentType', match: { value: this.agentType } });
152
+ }
153
+ const results = await this.qdrant.scroll(MEMORIES_COLLECTION, {
154
+ filter,
155
+ limit,
156
+ with_payload: true,
157
+ });
158
+ return results
159
+ .map(r => ({
160
+ id: String(r.id),
161
+ agent: String(r.payload?.agent || ''),
162
+ agentType: String(r.payload?.agentType || ''),
163
+ content: String(r.payload?.content || ''),
164
+ tags: r.payload?.tags || [],
165
+ createdAt: new Date(String(r.payload?.createdAt || new Date().toISOString())),
166
+ updatedAt: new Date(String(r.payload?.updatedAt || new Date().toISOString())),
167
+ metadata: r.payload?.metadata,
168
+ }))
169
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
166
170
  }
167
171
  async stats() {
168
- const snapshot = await this.db.collection(this.collectionPath).get();
172
+ await this.ensureCollection();
173
+ const filter = {
174
+ must: [
175
+ { key: 'agent', match: { value: this.agentId } }
176
+ ]
177
+ };
178
+ if (this.agentType) {
179
+ filter.must.push({ key: 'agentType', match: { value: this.agentType } });
180
+ }
181
+ const results = await this.qdrant.scroll(MEMORIES_COLLECTION, {
182
+ filter,
183
+ limit: 1000,
184
+ with_payload: true,
185
+ });
169
186
  const tagCounts = {};
170
- for (const doc of snapshot.docs) {
171
- const tags = doc.data().tags || [];
187
+ for (const r of results) {
188
+ const tags = r.payload?.tags || [];
172
189
  for (const tag of tags) {
173
190
  tagCounts[tag] = (tagCounts[tag] || 0) + 1;
174
191
  }
175
192
  }
176
193
  return {
177
- count: snapshot.size,
194
+ count: results.length,
178
195
  tags: tagCounts,
179
196
  };
180
197
  }
181
- cosineSimilarity(a, b) {
182
- if (a.length !== b.length)
183
- return 0;
184
- let dotProduct = 0;
185
- let normA = 0;
186
- let normB = 0;
187
- for (let i = 0; i < a.length; i++) {
188
- dotProduct += a[i] * b[i];
189
- normA += a[i] * a[i];
190
- normB += b[i] * b[i];
191
- }
192
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
193
- return magnitude === 0 ? 0 : dotProduct / magnitude;
194
- }
195
198
  }
196
199
  export default AgentBrain;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {