modular-studio 1.0.3 → 1.0.5
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 +101 -20
- package/dist/assets/Badge-22Ai0eyi.js +1 -0
- package/dist/assets/Input-Bgp734xs.js +1 -0
- package/dist/assets/KnowledgeTab-DABxirZh.js +4 -0
- package/dist/assets/MemoryTab-DZeYElIT.js +16 -0
- package/dist/assets/QualificationTab-Dfpy3J30.js +1 -0
- package/dist/assets/ReviewTab-SD8lQuCc.js +103 -0
- package/dist/assets/Section-DoJrmytO.js +1 -0
- package/dist/assets/TestTab-PDyMF8Fw.js +33 -0
- package/dist/assets/ToolsTab-B83qGCmG.js +1 -0
- package/dist/assets/conversationStore-CkfEU2eV.js +1 -0
- package/dist/assets/icons-C2EV-le6.js +1 -0
- package/dist/assets/index-DkpMAxX7.css +1 -0
- package/dist/assets/index-q24ug5Qs.js +143 -0
- package/dist/assets/{jszip.min-DByyLalk.js → jszip.min-wf-D3Ix_.js} +1 -1
- package/dist/assets/markdown-DWF7F0i0.js +29 -0
- package/dist/assets/services-BaKotDf0.js +343 -0
- package/dist/assets/stores-CeKWz7ou.js +1 -0
- package/dist/assets/vendor-D1h_O76p.js +9 -0
- package/dist/index.html +8 -4
- package/dist-server/bin/modular-mcp.js +0 -1
- package/dist-server/bin/modular-studio.js +0 -1
- package/dist-server/server/config.js +0 -1
- package/dist-server/server/data/mcp-tokens.json +3 -3
- package/dist-server/server/index.d.ts.map +1 -1
- package/dist-server/server/index.js +12 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/mcp/manager.js +0 -1
- package/dist-server/server/mcp/modular-server.js +0 -1
- package/dist-server/server/mcp/transport.js +0 -1
- package/dist-server/server/routes/agent-sdk.js +0 -1
- package/dist-server/server/routes/agents.d.ts +9 -5
- package/dist-server/server/routes/agents.d.ts.map +1 -1
- package/dist-server/server/routes/agents.js +81 -8
- package/dist-server/server/routes/auth-codex.js +0 -1
- package/dist-server/server/routes/capabilities.js +0 -1
- package/dist-server/server/routes/claude-config.js +0 -1
- package/dist-server/server/routes/connectors.d.ts.map +1 -1
- package/dist-server/server/routes/connectors.js +194 -1
- package/dist-server/server/routes/conversations.js +0 -1
- package/dist-server/server/routes/embeddings.d.ts.map +1 -1
- package/dist-server/server/routes/embeddings.js +16 -2
- package/dist-server/server/routes/embeddings.js.map +1 -1
- package/dist-server/server/routes/health.d.ts.map +1 -1
- package/dist-server/server/routes/health.js +10 -2
- package/dist-server/server/routes/health.js.map +1 -1
- package/dist-server/server/routes/knowledge.js +0 -1
- package/dist-server/server/routes/llm.js +0 -1
- package/dist-server/server/routes/mcp-oauth.js +0 -1
- package/dist-server/server/routes/mcp.js +0 -1
- package/dist-server/server/routes/memory.d.ts +3 -0
- package/dist-server/server/routes/memory.d.ts.map +1 -0
- package/dist-server/server/routes/memory.js +283 -0
- package/dist-server/server/routes/pipeline.js +0 -1
- package/dist-server/server/routes/providers.js +0 -1
- package/dist-server/server/routes/qualification.d.ts.map +1 -1
- package/dist-server/server/routes/qualification.js +382 -74
- package/dist-server/server/routes/repo-index.js +0 -1
- package/dist-server/server/routes/runtime.js +0 -1
- package/dist-server/server/routes/skills-search.d.ts.map +1 -1
- package/dist-server/server/routes/skills-search.js +44 -5
- package/dist-server/server/routes/skills-search.js.map +1 -1
- package/dist-server/server/routes/worktrees.js +0 -1
- package/dist-server/server/services/__tests__/embeddingService.test.js +0 -1
- package/dist-server/server/services/adapters/postgresAdapter.d.ts +29 -0
- package/dist-server/server/services/adapters/postgresAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/postgresAdapter.js +224 -0
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts +28 -0
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/sqliteAdapter.js +219 -0
- package/dist-server/server/services/adapters/storageAdapter.d.ts +22 -0
- package/dist-server/server/services/adapters/storageAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/storageAdapter.js +1 -0
- package/dist-server/server/services/agentRunner.js +0 -1
- package/dist-server/server/services/agentStore.d.ts +18 -3
- package/dist-server/server/services/agentStore.d.ts.map +1 -1
- package/dist-server/server/services/agentStore.js +116 -23
- package/dist-server/server/services/contentStore.js +0 -1
- package/dist-server/server/services/embeddingService.d.ts +2 -0
- package/dist-server/server/services/embeddingService.d.ts.map +1 -1
- package/dist-server/server/services/embeddingService.js +30 -19
- package/dist-server/server/services/factExtractor.js +0 -1
- package/dist-server/server/services/githubIndexer.js +0 -1
- package/dist-server/server/services/mcpOAuth.js +0 -1
- package/dist-server/server/services/memoryScorer.js +0 -1
- package/dist-server/server/services/repoIndexer.js +0 -1
- package/dist-server/server/services/sqliteStore.js +0 -1
- package/dist-server/server/services/teamRunner.js +0 -1
- package/dist-server/server/services/worktreeManager.js +0 -1
- package/dist-server/server/types.d.ts +5 -0
- package/dist-server/server/types.d.ts.map +1 -1
- package/dist-server/server/types.js +0 -1
- package/dist-server/server/utils/pathSecurity.js +0 -1
- package/dist-server/src/services/budgetAllocator.js +0 -1
- package/dist-server/src/services/contradictionDetector.js +0 -1
- package/dist-server/src/services/treeIndexer.js +0 -1
- package/dist-server/src/store/knowledgeBase.d.ts +10 -0
- package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
- package/dist-server/src/store/knowledgeBase.js +13 -1
- package/dist-server/src/store/memoryStore.d.ts +107 -0
- package/dist-server/src/store/memoryStore.d.ts.map +1 -0
- package/dist-server/src/store/memoryStore.js +263 -0
- package/dist-server/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +104 -97
- package/dist/assets/graphPopulator-DH0qM1Fu.js +0 -1
- package/dist/assets/index-B0RRTCSi.css +0 -1
- package/dist/assets/index-LWCi_4Hu.js +0 -662
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { textSimilarity } from '../memoryScorer.js';
|
|
3
|
+
export class PostgresAdapter {
|
|
4
|
+
pool = null;
|
|
5
|
+
connectionString;
|
|
6
|
+
lastWrite = 0;
|
|
7
|
+
constructor(connectionString) {
|
|
8
|
+
this.connectionString = connectionString;
|
|
9
|
+
}
|
|
10
|
+
async initialize() {
|
|
11
|
+
this.pool = new Pool({
|
|
12
|
+
connectionString: this.connectionString,
|
|
13
|
+
max: 10,
|
|
14
|
+
idleTimeoutMillis: 30000,
|
|
15
|
+
connectionTimeoutMillis: 2000,
|
|
16
|
+
});
|
|
17
|
+
const client = await this.pool.connect();
|
|
18
|
+
try {
|
|
19
|
+
await this.createTables(client);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
client.release();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async createTables(client) {
|
|
26
|
+
await client.query(`CREATE TABLE IF NOT EXISTS facts (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
content TEXT NOT NULL,
|
|
29
|
+
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
30
|
+
type TEXT NOT NULL,
|
|
31
|
+
timestamp BIGINT NOT NULL,
|
|
32
|
+
domain TEXT NOT NULL,
|
|
33
|
+
granularity TEXT NOT NULL,
|
|
34
|
+
embedding BYTEA,
|
|
35
|
+
owner_agent_id TEXT,
|
|
36
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
37
|
+
)`);
|
|
38
|
+
// Create indexes for better performance
|
|
39
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_domain ON facts(domain)');
|
|
40
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_timestamp ON facts(timestamp)');
|
|
41
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_type ON facts(type)');
|
|
42
|
+
// GIN index for tag queries
|
|
43
|
+
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_tags ON facts USING gin(tags)');
|
|
44
|
+
// Full-text search index
|
|
45
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_facts_content_fts
|
|
46
|
+
ON facts USING gin(to_tsvector('english', content))`);
|
|
47
|
+
}
|
|
48
|
+
async storeFact(fact) {
|
|
49
|
+
if (!this.pool)
|
|
50
|
+
await this.initialize();
|
|
51
|
+
if (!this.pool)
|
|
52
|
+
throw new Error('Pool not initialized');
|
|
53
|
+
const embedding = fact.embedding ? Buffer.from(new Float32Array(fact.embedding).buffer) : null;
|
|
54
|
+
await this.pool.query(`
|
|
55
|
+
INSERT INTO facts (id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id)
|
|
56
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
57
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
58
|
+
content = EXCLUDED.content,
|
|
59
|
+
tags = EXCLUDED.tags,
|
|
60
|
+
type = EXCLUDED.type,
|
|
61
|
+
timestamp = EXCLUDED.timestamp,
|
|
62
|
+
domain = EXCLUDED.domain,
|
|
63
|
+
granularity = EXCLUDED.granularity,
|
|
64
|
+
embedding = EXCLUDED.embedding,
|
|
65
|
+
owner_agent_id = EXCLUDED.owner_agent_id
|
|
66
|
+
`, [
|
|
67
|
+
fact.id,
|
|
68
|
+
fact.content,
|
|
69
|
+
JSON.stringify(fact.tags),
|
|
70
|
+
fact.type,
|
|
71
|
+
fact.timestamp,
|
|
72
|
+
fact.domain,
|
|
73
|
+
fact.granularity,
|
|
74
|
+
embedding,
|
|
75
|
+
fact.ownerAgentId || null
|
|
76
|
+
]);
|
|
77
|
+
this.lastWrite = Date.now();
|
|
78
|
+
}
|
|
79
|
+
async getFacts(options) {
|
|
80
|
+
if (!this.pool)
|
|
81
|
+
await this.initialize();
|
|
82
|
+
if (!this.pool)
|
|
83
|
+
return [];
|
|
84
|
+
let query = 'SELECT * FROM facts';
|
|
85
|
+
const params = [];
|
|
86
|
+
let paramCount = 0;
|
|
87
|
+
if (options?.domain) {
|
|
88
|
+
query += ` WHERE domain = $${++paramCount}`;
|
|
89
|
+
params.push(options.domain);
|
|
90
|
+
}
|
|
91
|
+
query += ' ORDER BY timestamp DESC';
|
|
92
|
+
if (options?.limit) {
|
|
93
|
+
query += ` LIMIT $${++paramCount}`;
|
|
94
|
+
params.push(options.limit);
|
|
95
|
+
if (options.offset) {
|
|
96
|
+
query += ` OFFSET $${++paramCount}`;
|
|
97
|
+
params.push(options.offset);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const result = await this.pool.query(query, params);
|
|
101
|
+
return result.rows.map(row => this.rowToFact(row));
|
|
102
|
+
}
|
|
103
|
+
rowToFact(row) {
|
|
104
|
+
const embedding = row.embedding ? new Float32Array(new Uint8Array(row.embedding).buffer) : undefined;
|
|
105
|
+
return {
|
|
106
|
+
id: row.id,
|
|
107
|
+
content: row.content,
|
|
108
|
+
tags: JSON.parse(row.tags),
|
|
109
|
+
type: row.type,
|
|
110
|
+
timestamp: parseInt(row.timestamp),
|
|
111
|
+
domain: row.domain,
|
|
112
|
+
granularity: row.granularity,
|
|
113
|
+
embedding: embedding ? Array.from(embedding) : undefined,
|
|
114
|
+
ownerAgentId: row.owner_agent_id || undefined
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async searchFacts(query, k = 5) {
|
|
118
|
+
if (!this.pool)
|
|
119
|
+
await this.initialize();
|
|
120
|
+
if (!this.pool)
|
|
121
|
+
return [];
|
|
122
|
+
// Try PostgreSQL full-text search first
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.pool.query(`
|
|
125
|
+
SELECT *, ts_rank(to_tsvector('english', content), plainto_tsquery('english', $1)) as score
|
|
126
|
+
FROM facts
|
|
127
|
+
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', $1)
|
|
128
|
+
ORDER BY score DESC
|
|
129
|
+
LIMIT $2
|
|
130
|
+
`, [query, k]);
|
|
131
|
+
if (result.rows.length > 0) {
|
|
132
|
+
return result.rows.map(row => ({
|
|
133
|
+
...this.rowToFact(row),
|
|
134
|
+
score: parseFloat(row.score)
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.warn('Full-text search failed, falling back to similarity:', error);
|
|
140
|
+
}
|
|
141
|
+
// Fallback to similarity search
|
|
142
|
+
const allFacts = await this.getFacts({ limit: 1000 });
|
|
143
|
+
const scored = allFacts
|
|
144
|
+
.map(fact => ({
|
|
145
|
+
...fact,
|
|
146
|
+
score: textSimilarity(fact.content, query)
|
|
147
|
+
}))
|
|
148
|
+
.filter(fact => fact.score > 0.1)
|
|
149
|
+
.sort((a, b) => b.score - a.score)
|
|
150
|
+
.slice(0, k);
|
|
151
|
+
return scored;
|
|
152
|
+
}
|
|
153
|
+
async deleteFact(id) {
|
|
154
|
+
if (!this.pool)
|
|
155
|
+
await this.initialize();
|
|
156
|
+
if (!this.pool)
|
|
157
|
+
return;
|
|
158
|
+
await this.pool.query('DELETE FROM facts WHERE id = $1', [id]);
|
|
159
|
+
this.lastWrite = Date.now();
|
|
160
|
+
}
|
|
161
|
+
async updateFact(id, patch) {
|
|
162
|
+
if (!this.pool)
|
|
163
|
+
await this.initialize();
|
|
164
|
+
if (!this.pool)
|
|
165
|
+
return;
|
|
166
|
+
const updates = [];
|
|
167
|
+
const params = [];
|
|
168
|
+
let paramCount = 0;
|
|
169
|
+
if (patch.content !== undefined) {
|
|
170
|
+
updates.push(`content = $${++paramCount}`);
|
|
171
|
+
params.push(patch.content);
|
|
172
|
+
}
|
|
173
|
+
if (patch.tags !== undefined) {
|
|
174
|
+
updates.push(`tags = $${++paramCount}`);
|
|
175
|
+
params.push(JSON.stringify(patch.tags));
|
|
176
|
+
}
|
|
177
|
+
if (patch.type !== undefined) {
|
|
178
|
+
updates.push(`type = $${++paramCount}`);
|
|
179
|
+
params.push(patch.type);
|
|
180
|
+
}
|
|
181
|
+
if (patch.domain !== undefined) {
|
|
182
|
+
updates.push(`domain = $${++paramCount}`);
|
|
183
|
+
params.push(patch.domain);
|
|
184
|
+
}
|
|
185
|
+
if (patch.embedding !== undefined) {
|
|
186
|
+
updates.push(`embedding = $${++paramCount}`);
|
|
187
|
+
const embedding = patch.embedding ? Buffer.from(new Float32Array(patch.embedding).buffer) : null;
|
|
188
|
+
params.push(embedding);
|
|
189
|
+
}
|
|
190
|
+
if (updates.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
params.push(id);
|
|
193
|
+
await this.pool.query(`UPDATE facts SET ${updates.join(', ')} WHERE id = $${++paramCount}`, params);
|
|
194
|
+
this.lastWrite = Date.now();
|
|
195
|
+
}
|
|
196
|
+
async getHealth() {
|
|
197
|
+
if (!this.pool)
|
|
198
|
+
await this.initialize();
|
|
199
|
+
if (!this.pool)
|
|
200
|
+
return { status: 'error', factCount: 0 };
|
|
201
|
+
try {
|
|
202
|
+
const result = await this.pool.query('SELECT COUNT(*) as count FROM facts');
|
|
203
|
+
const factCount = parseInt(result.rows[0].count);
|
|
204
|
+
return {
|
|
205
|
+
status: 'healthy',
|
|
206
|
+
factCount,
|
|
207
|
+
lastWrite: this.lastWrite || undefined
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
status: 'error',
|
|
213
|
+
factCount: 0,
|
|
214
|
+
lastWrite: this.lastWrite || undefined
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async close() {
|
|
219
|
+
if (this.pool) {
|
|
220
|
+
await this.pool.end();
|
|
221
|
+
this.pool = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Fact } from '../../../src/store/memoryStore.js';
|
|
2
|
+
import type { StorageAdapter } from './storageAdapter.js';
|
|
3
|
+
export declare class SqliteAdapter implements StorageAdapter {
|
|
4
|
+
private db;
|
|
5
|
+
private lastWrite;
|
|
6
|
+
initialize(): Promise<void>;
|
|
7
|
+
private createTables;
|
|
8
|
+
private saveDb;
|
|
9
|
+
storeFact(fact: Fact): Promise<void>;
|
|
10
|
+
getFacts(options?: {
|
|
11
|
+
domain?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
offset?: number;
|
|
14
|
+
}): Promise<Fact[]>;
|
|
15
|
+
private rowToFact;
|
|
16
|
+
searchFacts(query: string, k?: number): Promise<Array<Fact & {
|
|
17
|
+
score: number;
|
|
18
|
+
}>>;
|
|
19
|
+
deleteFact(id: string): Promise<void>;
|
|
20
|
+
updateFact(id: string, patch: Partial<Fact>): Promise<void>;
|
|
21
|
+
getHealth(): Promise<{
|
|
22
|
+
status: string;
|
|
23
|
+
factCount: number;
|
|
24
|
+
lastWrite?: number;
|
|
25
|
+
}>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=sqliteAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteAdapter.d.ts","sourceRoot":"","sources":["../../../../server/services/adapters/sqliteAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM1D,qBAAa,aAAc,YAAW,cAAc;IAClD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,SAAS,CAAa;IAExB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,YAAY;IAuCpB,OAAO,CAAC,MAAM;IAQR,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpC,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA6B/F,OAAO,CAAC,SAAS;IAeX,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAkC3E,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3D,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAc/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import initSqlJs from 'sql.js';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { textSimilarity } from '../memoryScorer.js';
|
|
6
|
+
const DB_DIR = join(homedir(), '.modular-studio');
|
|
7
|
+
const MEMORY_DB_PATH = join(DB_DIR, 'memory.db');
|
|
8
|
+
export class SqliteAdapter {
|
|
9
|
+
db = null;
|
|
10
|
+
lastWrite = 0;
|
|
11
|
+
async initialize() {
|
|
12
|
+
const SQL = await initSqlJs();
|
|
13
|
+
if (!existsSync(DB_DIR)) {
|
|
14
|
+
mkdirSync(DB_DIR, { recursive: true, mode: 0o755 });
|
|
15
|
+
}
|
|
16
|
+
if (existsSync(MEMORY_DB_PATH)) {
|
|
17
|
+
const buffer = readFileSync(MEMORY_DB_PATH);
|
|
18
|
+
this.db = new SQL.Database(buffer);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.db = new SQL.Database();
|
|
22
|
+
}
|
|
23
|
+
this.createTables();
|
|
24
|
+
}
|
|
25
|
+
createTables() {
|
|
26
|
+
if (!this.db)
|
|
27
|
+
return;
|
|
28
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS facts (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
content TEXT NOT NULL,
|
|
31
|
+
tags TEXT NOT NULL,
|
|
32
|
+
type TEXT NOT NULL,
|
|
33
|
+
timestamp INTEGER NOT NULL,
|
|
34
|
+
domain TEXT NOT NULL,
|
|
35
|
+
granularity TEXT NOT NULL,
|
|
36
|
+
embedding BLOB,
|
|
37
|
+
owner_agent_id TEXT
|
|
38
|
+
)`);
|
|
39
|
+
// Create FTS5 table for full-text search
|
|
40
|
+
this.db.run(`CREATE VIRTUAL TABLE IF NOT EXISTS facts_fts USING fts5(
|
|
41
|
+
content,
|
|
42
|
+
content='facts',
|
|
43
|
+
content_rowid='rowid'
|
|
44
|
+
)`);
|
|
45
|
+
// Triggers to keep FTS in sync
|
|
46
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ai AFTER INSERT ON facts BEGIN
|
|
47
|
+
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
48
|
+
END`);
|
|
49
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ad AFTER DELETE ON facts BEGIN
|
|
50
|
+
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
51
|
+
END`);
|
|
52
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_au AFTER UPDATE ON facts BEGIN
|
|
53
|
+
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
54
|
+
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
55
|
+
END`);
|
|
56
|
+
this.saveDb();
|
|
57
|
+
}
|
|
58
|
+
saveDb() {
|
|
59
|
+
if (!this.db)
|
|
60
|
+
return;
|
|
61
|
+
const data = this.db.export();
|
|
62
|
+
const buffer = Buffer.from(data);
|
|
63
|
+
writeFileSync(MEMORY_DB_PATH, buffer);
|
|
64
|
+
this.lastWrite = Date.now();
|
|
65
|
+
}
|
|
66
|
+
async storeFact(fact) {
|
|
67
|
+
if (!this.db)
|
|
68
|
+
await this.initialize();
|
|
69
|
+
if (!this.db)
|
|
70
|
+
throw new Error('Database not initialized');
|
|
71
|
+
const embedding = fact.embedding ? Buffer.from(new Float32Array(fact.embedding).buffer) : null;
|
|
72
|
+
this.db.run(`INSERT OR REPLACE INTO facts (
|
|
73
|
+
id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id
|
|
74
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
75
|
+
fact.id,
|
|
76
|
+
fact.content,
|
|
77
|
+
JSON.stringify(fact.tags),
|
|
78
|
+
fact.type,
|
|
79
|
+
fact.timestamp,
|
|
80
|
+
fact.domain,
|
|
81
|
+
fact.granularity,
|
|
82
|
+
embedding,
|
|
83
|
+
fact.ownerAgentId || null
|
|
84
|
+
]);
|
|
85
|
+
this.saveDb();
|
|
86
|
+
}
|
|
87
|
+
async getFacts(options) {
|
|
88
|
+
if (!this.db)
|
|
89
|
+
await this.initialize();
|
|
90
|
+
if (!this.db)
|
|
91
|
+
return [];
|
|
92
|
+
let query = 'SELECT * FROM facts';
|
|
93
|
+
const params = [];
|
|
94
|
+
if (options?.domain) {
|
|
95
|
+
query += ' WHERE domain = ?';
|
|
96
|
+
params.push(options.domain);
|
|
97
|
+
}
|
|
98
|
+
query += ' ORDER BY timestamp DESC';
|
|
99
|
+
if (options?.limit) {
|
|
100
|
+
query += ' LIMIT ?';
|
|
101
|
+
params.push(options.limit);
|
|
102
|
+
if (options.offset) {
|
|
103
|
+
query += ' OFFSET ?';
|
|
104
|
+
params.push(options.offset);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const result = this.db.exec(query, params);
|
|
108
|
+
if (result.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
return result[0].values.map(row => this.rowToFact(row));
|
|
111
|
+
}
|
|
112
|
+
rowToFact(row) {
|
|
113
|
+
const embedding = row[7] ? new Float32Array(new Uint8Array(row[7]).buffer) : undefined;
|
|
114
|
+
return {
|
|
115
|
+
id: row[0],
|
|
116
|
+
content: row[1],
|
|
117
|
+
tags: JSON.parse(row[2]),
|
|
118
|
+
type: row[3],
|
|
119
|
+
timestamp: row[4],
|
|
120
|
+
domain: row[5],
|
|
121
|
+
granularity: row[6],
|
|
122
|
+
embedding: embedding ? Array.from(embedding) : undefined,
|
|
123
|
+
ownerAgentId: row[8] || undefined
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async searchFacts(query, k = 5) {
|
|
127
|
+
if (!this.db)
|
|
128
|
+
await this.initialize();
|
|
129
|
+
if (!this.db)
|
|
130
|
+
return [];
|
|
131
|
+
// First try FTS5 full-text search
|
|
132
|
+
const ftsResult = this.db.exec(`
|
|
133
|
+
SELECT facts.*, rank FROM facts_fts
|
|
134
|
+
JOIN facts ON facts.rowid = facts_fts.rowid
|
|
135
|
+
WHERE facts_fts MATCH ?
|
|
136
|
+
ORDER BY rank
|
|
137
|
+
LIMIT ?
|
|
138
|
+
`, [query, k]);
|
|
139
|
+
if (ftsResult.length > 0) {
|
|
140
|
+
return ftsResult[0].values.map(row => ({
|
|
141
|
+
...this.rowToFact(row),
|
|
142
|
+
score: 1.0 - row[9] * 0.1 // Convert rank to score
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
// Fallback to similarity search
|
|
146
|
+
const allFacts = await this.getFacts();
|
|
147
|
+
const scored = allFacts
|
|
148
|
+
.map(fact => ({
|
|
149
|
+
...fact,
|
|
150
|
+
score: textSimilarity(fact.content, query)
|
|
151
|
+
}))
|
|
152
|
+
.filter(fact => fact.score > 0.1)
|
|
153
|
+
.sort((a, b) => b.score - a.score)
|
|
154
|
+
.slice(0, k);
|
|
155
|
+
return scored;
|
|
156
|
+
}
|
|
157
|
+
async deleteFact(id) {
|
|
158
|
+
if (!this.db)
|
|
159
|
+
await this.initialize();
|
|
160
|
+
if (!this.db)
|
|
161
|
+
return;
|
|
162
|
+
this.db.run('DELETE FROM facts WHERE id = ?', [id]);
|
|
163
|
+
this.saveDb();
|
|
164
|
+
}
|
|
165
|
+
async updateFact(id, patch) {
|
|
166
|
+
if (!this.db)
|
|
167
|
+
await this.initialize();
|
|
168
|
+
if (!this.db)
|
|
169
|
+
return;
|
|
170
|
+
const updates = [];
|
|
171
|
+
const params = [];
|
|
172
|
+
if (patch.content !== undefined) {
|
|
173
|
+
updates.push('content = ?');
|
|
174
|
+
params.push(patch.content);
|
|
175
|
+
}
|
|
176
|
+
if (patch.tags !== undefined) {
|
|
177
|
+
updates.push('tags = ?');
|
|
178
|
+
params.push(JSON.stringify(patch.tags));
|
|
179
|
+
}
|
|
180
|
+
if (patch.type !== undefined) {
|
|
181
|
+
updates.push('type = ?');
|
|
182
|
+
params.push(patch.type);
|
|
183
|
+
}
|
|
184
|
+
if (patch.domain !== undefined) {
|
|
185
|
+
updates.push('domain = ?');
|
|
186
|
+
params.push(patch.domain);
|
|
187
|
+
}
|
|
188
|
+
if (patch.embedding !== undefined) {
|
|
189
|
+
updates.push('embedding = ?');
|
|
190
|
+
const embedding = patch.embedding ? Buffer.from(new Float32Array(patch.embedding).buffer) : null;
|
|
191
|
+
params.push(embedding);
|
|
192
|
+
}
|
|
193
|
+
if (updates.length === 0)
|
|
194
|
+
return;
|
|
195
|
+
params.push(id);
|
|
196
|
+
this.db.run(`UPDATE facts SET ${updates.join(', ')} WHERE id = ?`, params);
|
|
197
|
+
this.saveDb();
|
|
198
|
+
}
|
|
199
|
+
async getHealth() {
|
|
200
|
+
if (!this.db)
|
|
201
|
+
await this.initialize();
|
|
202
|
+
if (!this.db)
|
|
203
|
+
return { status: 'error', factCount: 0 };
|
|
204
|
+
const result = this.db.exec('SELECT COUNT(*) FROM facts');
|
|
205
|
+
const factCount = result[0]?.values[0]?.[0] ?? 0;
|
|
206
|
+
return {
|
|
207
|
+
status: 'healthy',
|
|
208
|
+
factCount,
|
|
209
|
+
lastWrite: this.lastWrite || undefined
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async close() {
|
|
213
|
+
if (this.db) {
|
|
214
|
+
this.saveDb();
|
|
215
|
+
this.db.close();
|
|
216
|
+
this.db = null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Fact } from '../../../src/store/memoryStore.js';
|
|
2
|
+
export interface StorageAdapter {
|
|
3
|
+
initialize(): Promise<void>;
|
|
4
|
+
storeFact(fact: Fact): Promise<void>;
|
|
5
|
+
getFacts(options?: {
|
|
6
|
+
domain?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
}): Promise<Fact[]>;
|
|
10
|
+
searchFacts(query: string, k?: number): Promise<Array<Fact & {
|
|
11
|
+
score: number;
|
|
12
|
+
}>>;
|
|
13
|
+
deleteFact(id: string): Promise<void>;
|
|
14
|
+
updateFact(id: string, patch: Partial<Fact>): Promise<void>;
|
|
15
|
+
getHealth(): Promise<{
|
|
16
|
+
status: string;
|
|
17
|
+
factCount: number;
|
|
18
|
+
lastWrite?: number;
|
|
19
|
+
}>;
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=storageAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storageAdapter.d.ts","sourceRoot":"","sources":["../../../../server/services/adapters/storageAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1F,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACjF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent Store — Persistent agent state on disk
|
|
3
|
-
* Directory: ~/.modular-studio/agents/
|
|
4
|
-
*
|
|
2
|
+
* Agent Store — Persistent agent state on disk with versioning
|
|
3
|
+
* Directory: ~/.modular-studio/agents/{agentId}/
|
|
4
|
+
* Latest state: latest.json
|
|
5
|
+
* Versions: versions/{timestamp}-v{major}.{minor}.{patch}.json
|
|
5
6
|
*/
|
|
6
7
|
export interface SavedAgentState {
|
|
7
8
|
id: string;
|
|
@@ -27,13 +28,27 @@ export interface SavedAgentState {
|
|
|
27
28
|
outputFormats: string[];
|
|
28
29
|
tokenBudget: number;
|
|
29
30
|
prompt: string;
|
|
31
|
+
selectedModel: string;
|
|
32
|
+
}
|
|
33
|
+
export interface AgentVersion {
|
|
34
|
+
id: string;
|
|
35
|
+
version: string;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
label?: string;
|
|
38
|
+
snapshot: SavedAgentState;
|
|
30
39
|
}
|
|
31
40
|
export interface AgentSummary {
|
|
32
41
|
id: string;
|
|
33
42
|
agentMeta: SavedAgentState['agentMeta'];
|
|
34
43
|
savedAt: string;
|
|
44
|
+
currentVersion: string;
|
|
35
45
|
}
|
|
36
46
|
export declare function saveAgent(id: string, state: SavedAgentState): void;
|
|
47
|
+
export declare function createAgentVersion(id: string, version: string, label?: string): AgentVersion | null;
|
|
48
|
+
export declare function listAgentVersions(id: string): AgentVersion[];
|
|
49
|
+
export declare function getAgentVersion(id: string, version: string): AgentVersion | null;
|
|
50
|
+
export declare function restoreAgentVersion(id: string, version: string): boolean;
|
|
51
|
+
export declare function deleteAgentVersion(id: string, version: string): boolean;
|
|
37
52
|
export declare function loadAgent(id: string): SavedAgentState | null;
|
|
38
53
|
export declare function listAgents(): AgentSummary[];
|
|
39
54
|
export declare function deleteAgent(id: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentStore.d.ts","sourceRoot":"","sources":["../../../server/services/agentStore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agentStore.d.ts","sourceRoot":"","sources":["../../../server/services/agentStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4BH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAQlE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAqBnG;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,EAAE,CAiB5D;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAGhF;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMxE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAavE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQ5D;AAED,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAyB3C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA2B/C"}
|