@askexenow/exe-os 0.8.0 → 0.8.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/README.md +178 -79
- package/dist/bin/backfill-responses.js +160 -8
- package/dist/bin/backfill-vectors.js +130 -1
- package/dist/bin/cleanup-stale-review-tasks.js +130 -1
- package/dist/bin/cli.js +10111 -7540
- package/dist/bin/exe-agent.js +159 -1
- package/dist/bin/exe-assign.js +235 -16
- package/dist/bin/exe-boot.js +344 -472
- package/dist/bin/exe-call.js +145 -1
- package/dist/bin/exe-cloud.js +11 -0
- package/dist/bin/exe-dispatch.js +37 -24
- package/dist/bin/exe-doctor.js +130 -1
- package/dist/bin/exe-export-behaviors.js +150 -7
- package/dist/bin/exe-forget.js +822 -665
- package/dist/bin/exe-gateway.js +470 -62
- package/dist/bin/exe-heartbeat.js +133 -2
- package/dist/bin/exe-kill.js +150 -7
- package/dist/bin/exe-launch-agent.js +150 -7
- package/dist/bin/exe-new-employee.js +756 -224
- package/dist/bin/exe-pending-messages.js +132 -2
- package/dist/bin/exe-pending-notifications.js +130 -1
- package/dist/bin/exe-pending-reviews.js +132 -2
- package/dist/bin/exe-review.js +160 -8
- package/dist/bin/exe-search.js +2473 -2008
- package/dist/bin/exe-session-cleanup.js +238 -51
- package/dist/bin/exe-settings.js +11 -0
- package/dist/bin/exe-status.js +130 -1
- package/dist/bin/exe-team.js +130 -1
- package/dist/bin/git-sweep.js +272 -16
- package/dist/bin/graph-backfill.js +150 -7
- package/dist/bin/graph-export.js +150 -7
- package/dist/bin/install.js +5 -0
- package/dist/bin/scan-tasks.js +238 -19
- package/dist/bin/setup.js +1776 -10
- package/dist/bin/shard-migrate.js +150 -7
- package/dist/bin/update.js +9 -6
- package/dist/bin/wiki-sync.js +150 -7
- package/dist/gateway/index.js +470 -62
- package/dist/hooks/bug-report-worker.js +195 -35
- package/dist/hooks/commit-complete.js +272 -16
- package/dist/hooks/error-recall.js +2313 -1847
- package/dist/hooks/exe-heartbeat-hook.js +5 -0
- package/dist/hooks/ingest-worker.js +330 -58
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/instructions-loaded.js +199 -10
- package/dist/hooks/notification.js +199 -10
- package/dist/hooks/post-compact.js +199 -10
- package/dist/hooks/pre-compact.js +199 -10
- package/dist/hooks/pre-tool-use.js +199 -10
- package/dist/hooks/prompt-ingest-worker.js +179 -14
- package/dist/hooks/prompt-submit.js +781 -285
- package/dist/hooks/response-ingest-worker.js +1900 -1405
- package/dist/hooks/session-end.js +456 -12
- package/dist/hooks/session-start.js +2188 -1724
- package/dist/hooks/stop.js +200 -10
- package/dist/hooks/subagent-stop.js +199 -10
- package/dist/hooks/summary-worker.js +604 -334
- package/dist/index.js +554 -61
- package/dist/lib/cloud-sync.js +5 -0
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +5 -0
- package/dist/lib/database.js +104 -0
- package/dist/lib/device-registry.js +109 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employee-templates.js +53 -26
- package/dist/lib/employees.js +5 -0
- package/dist/lib/exe-daemon-client.js +5 -0
- package/dist/lib/exe-daemon.js +493 -79
- package/dist/lib/file-grep.js +20 -4
- package/dist/lib/hybrid-search.js +1435 -190
- package/dist/lib/identity-templates.js +126 -5
- package/dist/lib/identity.js +5 -0
- package/dist/lib/license.js +5 -0
- package/dist/lib/messaging.js +37 -24
- package/dist/lib/schedules.js +130 -1
- package/dist/lib/skill-learning.js +11 -0
- package/dist/lib/status-brief.js +5 -0
- package/dist/lib/store.js +199 -10
- package/dist/lib/task-router.js +72 -6
- package/dist/lib/tasks.js +179 -50
- package/dist/lib/tmux-routing.js +179 -46
- package/dist/mcp/server.js +2129 -1855
- package/dist/mcp/tools/create-task.js +86 -36
- package/dist/mcp/tools/deactivate-behavior.js +5 -0
- package/dist/mcp/tools/list-tasks.js +39 -11
- package/dist/mcp/tools/send-message.js +37 -24
- package/dist/mcp/tools/update-task.js +153 -38
- package/dist/runtime/index.js +451 -59
- package/dist/tui/App.js +454 -59
- package/package.json +1 -1
|
@@ -95,6 +95,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
95
95
|
const userSL = raw.sessionLifecycle ?? {};
|
|
96
96
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
97
97
|
}
|
|
98
|
+
function normalizeAutoUpdate(raw) {
|
|
99
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
100
|
+
const userAU = raw.autoUpdate ?? {};
|
|
101
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
102
|
+
}
|
|
98
103
|
async function loadConfig() {
|
|
99
104
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
100
105
|
await mkdir(dir, { recursive: true });
|
|
@@ -117,6 +122,7 @@ async function loadConfig() {
|
|
|
117
122
|
}
|
|
118
123
|
normalizeScalingRoadmap(migratedCfg);
|
|
119
124
|
normalizeSessionLifecycle(migratedCfg);
|
|
125
|
+
normalizeAutoUpdate(migratedCfg);
|
|
120
126
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
121
127
|
if (config.dbPath.startsWith("~")) {
|
|
122
128
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -139,6 +145,7 @@ function loadConfigSync() {
|
|
|
139
145
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
140
146
|
normalizeScalingRoadmap(migratedCfg);
|
|
141
147
|
normalizeSessionLifecycle(migratedCfg);
|
|
148
|
+
normalizeAutoUpdate(migratedCfg);
|
|
142
149
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
143
150
|
} catch {
|
|
144
151
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -158,6 +165,7 @@ async function loadConfigFrom(configPath) {
|
|
|
158
165
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
159
166
|
normalizeScalingRoadmap(migratedCfg);
|
|
160
167
|
normalizeSessionLifecycle(migratedCfg);
|
|
168
|
+
normalizeAutoUpdate(migratedCfg);
|
|
161
169
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
162
170
|
} catch {
|
|
163
171
|
return { ...DEFAULT_CONFIG };
|
|
@@ -229,6 +237,11 @@ var init_config = __esm({
|
|
|
229
237
|
idleKillTicksRequired: 3,
|
|
230
238
|
idleKillIntercomAckWindowMs: 1e4,
|
|
231
239
|
maxAutoInstances: 10
|
|
240
|
+
},
|
|
241
|
+
autoUpdate: {
|
|
242
|
+
checkOnBoot: true,
|
|
243
|
+
autoInstall: false,
|
|
244
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
232
245
|
}
|
|
233
246
|
};
|
|
234
247
|
CONFIG_MIGRATIONS = [
|
|
@@ -253,63 +266,29 @@ var init_memory = __esm({
|
|
|
253
266
|
}
|
|
254
267
|
});
|
|
255
268
|
|
|
256
|
-
// src/lib/
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
getShardClient: () => getShardClient,
|
|
263
|
-
getShardsDir: () => getShardsDir,
|
|
264
|
-
initShardManager: () => initShardManager,
|
|
265
|
-
isShardingEnabled: () => isShardingEnabled,
|
|
266
|
-
listShards: () => listShards,
|
|
267
|
-
shardExists: () => shardExists
|
|
268
|
-
});
|
|
269
|
-
import path3 from "path";
|
|
270
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
271
|
-
import { createClient as createClient2 } from "@libsql/client";
|
|
272
|
-
function initShardManager(encryptionKey) {
|
|
273
|
-
_encryptionKey = encryptionKey;
|
|
274
|
-
if (!existsSync3(SHARDS_DIR)) {
|
|
275
|
-
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
276
|
-
}
|
|
277
|
-
_shardingEnabled = true;
|
|
278
|
-
}
|
|
279
|
-
function isShardingEnabled() {
|
|
280
|
-
return _shardingEnabled;
|
|
281
|
-
}
|
|
282
|
-
function getShardsDir() {
|
|
283
|
-
return SHARDS_DIR;
|
|
284
|
-
}
|
|
285
|
-
function getShardClient(projectName) {
|
|
286
|
-
if (!_encryptionKey) {
|
|
287
|
-
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
269
|
+
// src/lib/database.ts
|
|
270
|
+
import { createClient } from "@libsql/client";
|
|
271
|
+
async function initDatabase(config) {
|
|
272
|
+
if (_client) {
|
|
273
|
+
_client.close();
|
|
274
|
+
_client = null;
|
|
288
275
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
276
|
+
const opts = {
|
|
277
|
+
url: `file:${config.dbPath}`
|
|
278
|
+
};
|
|
279
|
+
if (config.encryptionKey) {
|
|
280
|
+
opts.encryptionKey = config.encryptionKey;
|
|
292
281
|
}
|
|
293
|
-
|
|
294
|
-
if (cached) return cached;
|
|
295
|
-
const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
|
|
296
|
-
const client = createClient2({
|
|
297
|
-
url: `file:${dbPath}`,
|
|
298
|
-
encryptionKey: _encryptionKey
|
|
299
|
-
});
|
|
300
|
-
_shards.set(safeName, client);
|
|
301
|
-
return client;
|
|
302
|
-
}
|
|
303
|
-
function shardExists(projectName) {
|
|
304
|
-
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
305
|
-
return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
|
|
282
|
+
_client = createClient(opts);
|
|
306
283
|
}
|
|
307
|
-
function
|
|
308
|
-
if (!
|
|
309
|
-
|
|
310
|
-
|
|
284
|
+
function getClient() {
|
|
285
|
+
if (!_client) {
|
|
286
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
287
|
+
}
|
|
288
|
+
return _client;
|
|
311
289
|
}
|
|
312
|
-
async function
|
|
290
|
+
async function ensureSchema() {
|
|
291
|
+
const client = getClient();
|
|
313
292
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
314
293
|
await client.execute("PRAGMA busy_timeout = 5000");
|
|
315
294
|
try {
|
|
@@ -331,9 +310,26 @@ async function ensureShardSchema(client) {
|
|
|
331
310
|
version INTEGER NOT NULL DEFAULT 0
|
|
332
311
|
);
|
|
333
312
|
|
|
334
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
335
|
-
|
|
336
|
-
|
|
313
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
314
|
+
ON memories(agent_id);
|
|
315
|
+
|
|
316
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
317
|
+
ON memories(timestamp);
|
|
318
|
+
|
|
319
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
320
|
+
ON memories(session_id);
|
|
321
|
+
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
323
|
+
ON memories(project_name);
|
|
324
|
+
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
326
|
+
ON memories(tool_name);
|
|
327
|
+
|
|
328
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
329
|
+
ON memories(version);
|
|
330
|
+
|
|
331
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
332
|
+
ON memories(agent_id, project_name);
|
|
337
333
|
`);
|
|
338
334
|
await client.executeMultiple(`
|
|
339
335
|
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
@@ -355,1481 +351,972 @@ async function ensureShardSchema(client) {
|
|
|
355
351
|
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
356
352
|
END;
|
|
357
353
|
`);
|
|
358
|
-
for (const col of [
|
|
359
|
-
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
360
|
-
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
361
|
-
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
362
|
-
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
363
|
-
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
364
|
-
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
365
|
-
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
366
|
-
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
367
|
-
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
368
|
-
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
369
|
-
// Wiki linkage columns (must match database.ts)
|
|
370
|
-
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
371
|
-
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
372
|
-
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
373
|
-
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
374
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
375
|
-
]) {
|
|
376
|
-
try {
|
|
377
|
-
await client.execute(col);
|
|
378
|
-
} catch {
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
383
|
-
} catch {
|
|
384
|
-
}
|
|
385
|
-
for (const idx of [
|
|
386
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
|
|
387
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
|
|
388
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
|
|
389
|
-
]) {
|
|
390
|
-
try {
|
|
391
|
-
await client.execute(idx);
|
|
392
|
-
} catch {
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
354
|
await client.executeMultiple(`
|
|
396
|
-
CREATE TABLE IF NOT EXISTS
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
type TEXT NOT NULL,
|
|
400
|
-
first_seen TEXT NOT NULL,
|
|
401
|
-
last_seen TEXT NOT NULL,
|
|
402
|
-
properties TEXT DEFAULT '{}',
|
|
403
|
-
UNIQUE(name, type)
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
CREATE TABLE IF NOT EXISTS relationships (
|
|
407
|
-
id TEXT PRIMARY KEY,
|
|
408
|
-
source_entity_id TEXT NOT NULL,
|
|
409
|
-
target_entity_id TEXT NOT NULL,
|
|
410
|
-
type TEXT NOT NULL,
|
|
411
|
-
weight REAL DEFAULT 1.0,
|
|
412
|
-
timestamp TEXT NOT NULL,
|
|
413
|
-
properties TEXT DEFAULT '{}',
|
|
414
|
-
UNIQUE(source_entity_id, target_entity_id, type)
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
418
|
-
entity_id TEXT NOT NULL,
|
|
419
|
-
memory_id TEXT NOT NULL,
|
|
420
|
-
PRIMARY KEY (entity_id, memory_id)
|
|
355
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
356
|
+
key TEXT PRIMARY KEY,
|
|
357
|
+
value TEXT NOT NULL
|
|
421
358
|
);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
359
|
+
`);
|
|
360
|
+
await client.executeMultiple(`
|
|
361
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
362
|
+
id TEXT PRIMARY KEY,
|
|
363
|
+
title TEXT NOT NULL,
|
|
364
|
+
assigned_to TEXT NOT NULL,
|
|
365
|
+
assigned_by TEXT NOT NULL,
|
|
366
|
+
project_name TEXT NOT NULL,
|
|
367
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
368
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
369
|
+
task_file TEXT,
|
|
370
|
+
created_at TEXT NOT NULL,
|
|
371
|
+
updated_at TEXT NOT NULL
|
|
427
372
|
);
|
|
428
373
|
|
|
429
|
-
CREATE INDEX IF NOT EXISTS
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
CREATE
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
374
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
375
|
+
ON tasks(assigned_to, status);
|
|
376
|
+
`);
|
|
377
|
+
await client.executeMultiple(`
|
|
378
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
379
|
+
id TEXT PRIMARY KEY,
|
|
380
|
+
agent_id TEXT NOT NULL,
|
|
381
|
+
project_name TEXT,
|
|
382
|
+
domain TEXT,
|
|
383
|
+
content TEXT NOT NULL,
|
|
384
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
385
|
+
created_at TEXT NOT NULL,
|
|
386
|
+
updated_at TEXT NOT NULL
|
|
441
387
|
);
|
|
442
388
|
|
|
443
|
-
CREATE
|
|
444
|
-
|
|
445
|
-
entity_id TEXT NOT NULL,
|
|
446
|
-
PRIMARY KEY (hyperedge_id, entity_id)
|
|
447
|
-
);
|
|
389
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
390
|
+
ON behaviors(agent_id, active);
|
|
448
391
|
`);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
392
|
+
try {
|
|
393
|
+
const existing = await client.execute({
|
|
394
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
|
|
395
|
+
args: []
|
|
396
|
+
});
|
|
397
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
398
|
+
await client.executeMultiple(`
|
|
399
|
+
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
400
|
+
VALUES
|
|
401
|
+
(hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
402
|
+
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
403
|
+
VALUES
|
|
404
|
+
(hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
405
|
+
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
406
|
+
VALUES
|
|
407
|
+
(hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
408
|
+
`);
|
|
456
409
|
}
|
|
410
|
+
} catch {
|
|
457
411
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
function disposeShards() {
|
|
465
|
-
for (const [, client] of _shards) {
|
|
466
|
-
client.close();
|
|
467
|
-
}
|
|
468
|
-
_shards.clear();
|
|
469
|
-
_shardingEnabled = false;
|
|
470
|
-
_encryptionKey = null;
|
|
471
|
-
}
|
|
472
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
473
|
-
var init_shard_manager = __esm({
|
|
474
|
-
"src/lib/shard-manager.ts"() {
|
|
475
|
-
"use strict";
|
|
476
|
-
init_config();
|
|
477
|
-
SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
|
|
478
|
-
_shards = /* @__PURE__ */ new Map();
|
|
479
|
-
_encryptionKey = null;
|
|
480
|
-
_shardingEnabled = false;
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// src/lib/self-query-router.ts
|
|
485
|
-
var self_query_router_exports = {};
|
|
486
|
-
__export(self_query_router_exports, {
|
|
487
|
-
routeQuery: () => routeQuery
|
|
488
|
-
});
|
|
489
|
-
async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
490
|
-
if (query.length < 10) {
|
|
491
|
-
return {
|
|
492
|
-
semanticQuery: query,
|
|
493
|
-
projectFilter: null,
|
|
494
|
-
roleFilter: null,
|
|
495
|
-
timeFilter: null,
|
|
496
|
-
isBroadQuery: false
|
|
497
|
-
};
|
|
412
|
+
try {
|
|
413
|
+
await client.execute({
|
|
414
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
415
|
+
args: []
|
|
416
|
+
});
|
|
417
|
+
} catch {
|
|
498
418
|
}
|
|
499
419
|
try {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const response = await client.messages.create({
|
|
504
|
-
model,
|
|
505
|
-
max_tokens: 256,
|
|
506
|
-
system: `You are a search query router. Extract metadata filters from the user's memory search query. Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
|
|
507
|
-
messages: [{ role: "user", content: query }],
|
|
508
|
-
tools: [EXTRACT_TOOL],
|
|
509
|
-
tool_choice: { type: "tool", name: "extract_search_filters" }
|
|
420
|
+
await client.execute({
|
|
421
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
422
|
+
args: []
|
|
510
423
|
});
|
|
511
|
-
|
|
512
|
-
if (toolBlock && toolBlock.type === "tool_use") {
|
|
513
|
-
const input2 = toolBlock.input;
|
|
514
|
-
return {
|
|
515
|
-
semanticQuery: input2.semantic_query || query,
|
|
516
|
-
projectFilter: input2.project_filter || null,
|
|
517
|
-
roleFilter: input2.role_filter || null,
|
|
518
|
-
timeFilter: input2.time_filter || null,
|
|
519
|
-
isBroadQuery: Boolean(input2.is_broad_query)
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
} catch (err) {
|
|
523
|
-
process.stderr.write(
|
|
524
|
-
`[self-query-router] LLM extraction failed, using passthrough: ${err instanceof Error ? err.message : String(err)}
|
|
525
|
-
`
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
return {
|
|
529
|
-
semanticQuery: query,
|
|
530
|
-
projectFilter: null,
|
|
531
|
-
roleFilter: null,
|
|
532
|
-
timeFilter: null,
|
|
533
|
-
isBroadQuery: false
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
var EXTRACT_TOOL;
|
|
537
|
-
var init_self_query_router = __esm({
|
|
538
|
-
"src/lib/self-query-router.ts"() {
|
|
539
|
-
"use strict";
|
|
540
|
-
EXTRACT_TOOL = {
|
|
541
|
-
name: "extract_search_filters",
|
|
542
|
-
description: "Extract metadata filters from a memory search query to improve retrieval precision.",
|
|
543
|
-
input_schema: {
|
|
544
|
-
type: "object",
|
|
545
|
-
properties: {
|
|
546
|
-
semantic_query: {
|
|
547
|
-
type: "string",
|
|
548
|
-
description: "The core semantic meaning of the query, stripped of metadata references. This is used for embedding search."
|
|
549
|
-
},
|
|
550
|
-
project_filter: {
|
|
551
|
-
type: ["string", "null"],
|
|
552
|
-
description: "Project name if the query references a specific project (e.g., 'exe-os', 'exe-create'). Null if no project specified."
|
|
553
|
-
},
|
|
554
|
-
role_filter: {
|
|
555
|
-
type: ["string", "null"],
|
|
556
|
-
description: "Agent role if the query targets a specific role (e.g., 'CTO', 'CMO'). Null if no role specified."
|
|
557
|
-
},
|
|
558
|
-
time_filter: {
|
|
559
|
-
type: ["string", "null"],
|
|
560
|
-
description: "ISO 8601 timestamp lower bound if the query references a time period (e.g., 'last week', 'yesterday'). Null if no time reference."
|
|
561
|
-
},
|
|
562
|
-
is_broad_query: {
|
|
563
|
-
type: "boolean",
|
|
564
|
-
description: "True if the query is exploratory/broad (e.g., 'what has yoshi been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
|
|
565
|
-
}
|
|
566
|
-
},
|
|
567
|
-
required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
// src/lib/exe-daemon-client.ts
|
|
574
|
-
import net from "net";
|
|
575
|
-
import { spawn } from "child_process";
|
|
576
|
-
import { randomUUID } from "crypto";
|
|
577
|
-
import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
|
|
578
|
-
import path4 from "path";
|
|
579
|
-
import { fileURLToPath } from "url";
|
|
580
|
-
function handleData(chunk) {
|
|
581
|
-
_buffer += chunk.toString();
|
|
582
|
-
let newlineIdx;
|
|
583
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
584
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
585
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
586
|
-
if (!line) continue;
|
|
587
|
-
try {
|
|
588
|
-
const response = JSON.parse(line);
|
|
589
|
-
const entry = _pending.get(response.id);
|
|
590
|
-
if (entry) {
|
|
591
|
-
clearTimeout(entry.timer);
|
|
592
|
-
_pending.delete(response.id);
|
|
593
|
-
entry.resolve(response);
|
|
594
|
-
}
|
|
595
|
-
} catch {
|
|
596
|
-
}
|
|
424
|
+
} catch {
|
|
597
425
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
try {
|
|
605
|
-
process.kill(pid, 0);
|
|
606
|
-
return;
|
|
607
|
-
} catch {
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
} catch {
|
|
611
|
-
}
|
|
612
|
-
try {
|
|
613
|
-
unlinkSync(PID_PATH);
|
|
614
|
-
} catch {
|
|
615
|
-
}
|
|
616
|
-
try {
|
|
617
|
-
unlinkSync(SOCKET_PATH);
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
426
|
+
try {
|
|
427
|
+
await client.execute({
|
|
428
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
429
|
+
args: []
|
|
430
|
+
});
|
|
431
|
+
} catch {
|
|
620
432
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
433
|
+
try {
|
|
434
|
+
await client.execute({
|
|
435
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
436
|
+
ON tasks(parent_task_id)
|
|
437
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
438
|
+
args: []
|
|
439
|
+
});
|
|
440
|
+
} catch {
|
|
628
441
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return;
|
|
442
|
+
try {
|
|
443
|
+
await client.execute({
|
|
444
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
445
|
+
args: []
|
|
446
|
+
});
|
|
447
|
+
} catch {
|
|
636
448
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
449
|
+
try {
|
|
450
|
+
await client.execute({
|
|
451
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
452
|
+
args: []
|
|
453
|
+
});
|
|
454
|
+
} catch {
|
|
642
455
|
}
|
|
643
|
-
const resolvedPath = daemonPath;
|
|
644
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
645
|
-
`);
|
|
646
|
-
const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
|
|
647
|
-
let stderrFd = "ignore";
|
|
648
456
|
try {
|
|
649
|
-
|
|
457
|
+
await client.execute({
|
|
458
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
459
|
+
args: []
|
|
460
|
+
});
|
|
650
461
|
} catch {
|
|
651
462
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
EXE_DAEMON_PID: PID_PATH
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
child.unref();
|
|
662
|
-
if (typeof stderrFd === "number") {
|
|
663
|
-
try {
|
|
664
|
-
closeSync(stderrFd);
|
|
665
|
-
} catch {
|
|
666
|
-
}
|
|
463
|
+
try {
|
|
464
|
+
await client.execute({
|
|
465
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
466
|
+
args: []
|
|
467
|
+
});
|
|
468
|
+
} catch {
|
|
667
469
|
}
|
|
668
|
-
}
|
|
669
|
-
function acquireSpawnLock() {
|
|
670
470
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
471
|
+
await client.execute({
|
|
472
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
473
|
+
args: []
|
|
474
|
+
});
|
|
674
475
|
} catch {
|
|
675
|
-
try {
|
|
676
|
-
const stat = statSync(SPAWN_LOCK_PATH);
|
|
677
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
678
|
-
try {
|
|
679
|
-
unlinkSync(SPAWN_LOCK_PATH);
|
|
680
|
-
} catch {
|
|
681
|
-
}
|
|
682
|
-
try {
|
|
683
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
684
|
-
closeSync(fd);
|
|
685
|
-
return true;
|
|
686
|
-
} catch {
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
} catch {
|
|
690
|
-
}
|
|
691
|
-
return false;
|
|
692
476
|
}
|
|
693
|
-
}
|
|
694
|
-
function releaseSpawnLock() {
|
|
695
477
|
try {
|
|
696
|
-
|
|
478
|
+
await client.execute({
|
|
479
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
480
|
+
args: []
|
|
481
|
+
});
|
|
697
482
|
} catch {
|
|
698
483
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
resolve(true);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
707
|
-
const connectTimeout = setTimeout(() => {
|
|
708
|
-
socket.destroy();
|
|
709
|
-
resolve(false);
|
|
710
|
-
}, 2e3);
|
|
711
|
-
socket.on("connect", () => {
|
|
712
|
-
clearTimeout(connectTimeout);
|
|
713
|
-
_socket = socket;
|
|
714
|
-
_connected = true;
|
|
715
|
-
_buffer = "";
|
|
716
|
-
socket.on("data", handleData);
|
|
717
|
-
socket.on("close", () => {
|
|
718
|
-
_connected = false;
|
|
719
|
-
_socket = null;
|
|
720
|
-
for (const [id, entry] of _pending) {
|
|
721
|
-
clearTimeout(entry.timer);
|
|
722
|
-
_pending.delete(id);
|
|
723
|
-
entry.resolve({ error: "Connection closed" });
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
socket.on("error", () => {
|
|
727
|
-
_connected = false;
|
|
728
|
-
_socket = null;
|
|
729
|
-
});
|
|
730
|
-
resolve(true);
|
|
731
|
-
});
|
|
732
|
-
socket.on("error", () => {
|
|
733
|
-
clearTimeout(connectTimeout);
|
|
734
|
-
resolve(false);
|
|
484
|
+
try {
|
|
485
|
+
await client.execute({
|
|
486
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
487
|
+
args: []
|
|
735
488
|
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
async function connectEmbedDaemon() {
|
|
739
|
-
if (_socket && _connected) return true;
|
|
740
|
-
if (await connectToSocket()) return true;
|
|
741
|
-
if (acquireSpawnLock()) {
|
|
742
|
-
try {
|
|
743
|
-
cleanupStaleFiles();
|
|
744
|
-
spawnDaemon();
|
|
745
|
-
} finally {
|
|
746
|
-
releaseSpawnLock();
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
const start = Date.now();
|
|
750
|
-
let delay = 100;
|
|
751
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
752
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
753
|
-
if (await connectToSocket()) return true;
|
|
754
|
-
delay = Math.min(delay * 2, 3e3);
|
|
489
|
+
} catch {
|
|
755
490
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (!_socket || !_connected) {
|
|
761
|
-
resolve({ error: "Not connected" });
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
const id = randomUUID();
|
|
765
|
-
const timer = setTimeout(() => {
|
|
766
|
-
_pending.delete(id);
|
|
767
|
-
resolve({ error: "Request timeout" });
|
|
768
|
-
}, REQUEST_TIMEOUT_MS);
|
|
769
|
-
_pending.set(id, { resolve, timer });
|
|
770
|
-
try {
|
|
771
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
772
|
-
} catch {
|
|
773
|
-
clearTimeout(timer);
|
|
774
|
-
_pending.delete(id);
|
|
775
|
-
resolve({ error: "Write failed" });
|
|
776
|
-
}
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
async function pingDaemon() {
|
|
780
|
-
if (!_socket || !_connected) return null;
|
|
781
|
-
return new Promise((resolve) => {
|
|
782
|
-
const id = randomUUID();
|
|
783
|
-
const timer = setTimeout(() => {
|
|
784
|
-
_pending.delete(id);
|
|
785
|
-
resolve(null);
|
|
786
|
-
}, 5e3);
|
|
787
|
-
_pending.set(id, {
|
|
788
|
-
resolve: (data) => {
|
|
789
|
-
if (data.health) {
|
|
790
|
-
resolve(data.health);
|
|
791
|
-
} else {
|
|
792
|
-
resolve(null);
|
|
793
|
-
}
|
|
794
|
-
},
|
|
795
|
-
timer
|
|
491
|
+
try {
|
|
492
|
+
await client.execute({
|
|
493
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
494
|
+
args: []
|
|
796
495
|
});
|
|
797
|
-
|
|
798
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
799
|
-
} catch {
|
|
800
|
-
clearTimeout(timer);
|
|
801
|
-
_pending.delete(id);
|
|
802
|
-
resolve(null);
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
function killAndRespawnDaemon() {
|
|
807
|
-
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
808
|
-
if (existsSync4(PID_PATH)) {
|
|
809
|
-
try {
|
|
810
|
-
const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
|
|
811
|
-
if (pid > 0) {
|
|
812
|
-
try {
|
|
813
|
-
process.kill(pid, "SIGKILL");
|
|
814
|
-
} catch {
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
} catch {
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
if (_socket) {
|
|
821
|
-
_socket.destroy();
|
|
822
|
-
_socket = null;
|
|
496
|
+
} catch {
|
|
823
497
|
}
|
|
824
|
-
_connected = false;
|
|
825
|
-
_buffer = "";
|
|
826
498
|
try {
|
|
827
|
-
|
|
499
|
+
await client.execute({
|
|
500
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
501
|
+
args: []
|
|
502
|
+
});
|
|
828
503
|
} catch {
|
|
829
504
|
}
|
|
830
505
|
try {
|
|
831
|
-
|
|
506
|
+
await client.execute({
|
|
507
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
508
|
+
args: []
|
|
509
|
+
});
|
|
832
510
|
} catch {
|
|
833
511
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const health = await pingDaemon();
|
|
841
|
-
if (!health) {
|
|
842
|
-
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
843
|
-
`);
|
|
844
|
-
killAndRespawnDaemon();
|
|
845
|
-
const start = Date.now();
|
|
846
|
-
let delay = 200;
|
|
847
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
848
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
849
|
-
if (await connectToSocket()) break;
|
|
850
|
-
delay = Math.min(delay * 2, 3e3);
|
|
851
|
-
}
|
|
852
|
-
if (!_connected) return null;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
const result = await sendRequest([text], priority);
|
|
856
|
-
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
857
|
-
if (result.error) {
|
|
858
|
-
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
859
|
-
`);
|
|
860
|
-
killAndRespawnDaemon();
|
|
861
|
-
const start = Date.now();
|
|
862
|
-
let delay = 200;
|
|
863
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
864
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
865
|
-
if (await connectToSocket()) break;
|
|
866
|
-
delay = Math.min(delay * 2, 3e3);
|
|
867
|
-
}
|
|
868
|
-
if (!_connected) return null;
|
|
869
|
-
const retry = await sendRequest([text], priority);
|
|
870
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
871
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
872
|
-
`);
|
|
873
|
-
}
|
|
874
|
-
return null;
|
|
875
|
-
}
|
|
876
|
-
function disconnectClient() {
|
|
877
|
-
if (_socket) {
|
|
878
|
-
_socket.destroy();
|
|
879
|
-
_socket = null;
|
|
880
|
-
}
|
|
881
|
-
_connected = false;
|
|
882
|
-
_buffer = "";
|
|
883
|
-
for (const [id, entry] of _pending) {
|
|
884
|
-
clearTimeout(entry.timer);
|
|
885
|
-
_pending.delete(id);
|
|
886
|
-
entry.resolve({ error: "Client disconnected" });
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
|
|
890
|
-
var init_exe_daemon_client = __esm({
|
|
891
|
-
"src/lib/exe-daemon-client.ts"() {
|
|
892
|
-
"use strict";
|
|
893
|
-
init_config();
|
|
894
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
|
|
895
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
|
|
896
|
-
SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
897
|
-
SPAWN_LOCK_STALE_MS = 3e4;
|
|
898
|
-
CONNECT_TIMEOUT_MS = 15e3;
|
|
899
|
-
REQUEST_TIMEOUT_MS = 3e4;
|
|
900
|
-
_socket = null;
|
|
901
|
-
_connected = false;
|
|
902
|
-
_buffer = "";
|
|
903
|
-
_requestCount = 0;
|
|
904
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
905
|
-
_pending = /* @__PURE__ */ new Map();
|
|
512
|
+
try {
|
|
513
|
+
await client.execute({
|
|
514
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
515
|
+
args: []
|
|
516
|
+
});
|
|
517
|
+
} catch {
|
|
906
518
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
embed: () => embed,
|
|
914
|
-
embedDirect: () => embedDirect,
|
|
915
|
-
getEmbedder: () => getEmbedder
|
|
916
|
-
});
|
|
917
|
-
async function getEmbedder() {
|
|
918
|
-
const ok = await connectEmbedDaemon();
|
|
919
|
-
if (!ok) {
|
|
920
|
-
throw new Error(
|
|
921
|
-
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
922
|
-
);
|
|
519
|
+
try {
|
|
520
|
+
await client.execute({
|
|
521
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
522
|
+
args: []
|
|
523
|
+
});
|
|
524
|
+
} catch {
|
|
923
525
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
526
|
+
await client.executeMultiple(`
|
|
527
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
528
|
+
id TEXT PRIMARY KEY,
|
|
529
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
530
|
+
source_memory_id TEXT NOT NULL,
|
|
531
|
+
created_at TEXT NOT NULL
|
|
931
532
|
);
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
533
|
+
|
|
534
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
535
|
+
ON consolidations(source_memory_id);
|
|
536
|
+
|
|
537
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
538
|
+
ON consolidations(consolidated_memory_id);
|
|
539
|
+
`);
|
|
540
|
+
await client.executeMultiple(`
|
|
541
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
542
|
+
id TEXT PRIMARY KEY,
|
|
543
|
+
text TEXT NOT NULL,
|
|
544
|
+
created_at TEXT NOT NULL,
|
|
545
|
+
due_date TEXT,
|
|
546
|
+
completed_at TEXT
|
|
936
547
|
);
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
548
|
+
`);
|
|
549
|
+
await client.executeMultiple(`
|
|
550
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
551
|
+
id TEXT PRIMARY KEY,
|
|
552
|
+
agent_id TEXT NOT NULL,
|
|
553
|
+
agent_role TEXT NOT NULL,
|
|
554
|
+
event TEXT NOT NULL,
|
|
555
|
+
project TEXT NOT NULL,
|
|
556
|
+
summary TEXT NOT NULL,
|
|
557
|
+
task_file TEXT,
|
|
558
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
559
|
+
created_at TEXT NOT NULL
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
563
|
+
ON notifications(read);
|
|
564
|
+
|
|
565
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
566
|
+
ON notifications(agent_id);
|
|
567
|
+
|
|
568
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
569
|
+
ON notifications(task_file);
|
|
570
|
+
`);
|
|
571
|
+
await client.executeMultiple(`
|
|
572
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
573
|
+
id TEXT PRIMARY KEY,
|
|
574
|
+
cron TEXT NOT NULL,
|
|
575
|
+
description TEXT NOT NULL,
|
|
576
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
577
|
+
prompt TEXT,
|
|
578
|
+
assigned_to TEXT,
|
|
579
|
+
project_name TEXT,
|
|
580
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
581
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
582
|
+
created_at TEXT NOT NULL
|
|
583
|
+
);
|
|
584
|
+
`);
|
|
585
|
+
await client.executeMultiple(`
|
|
586
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
587
|
+
device_id TEXT PRIMARY KEY,
|
|
588
|
+
friendly_name TEXT NOT NULL,
|
|
589
|
+
hostname TEXT NOT NULL,
|
|
590
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
591
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
592
|
+
connected INTEGER DEFAULT 0,
|
|
593
|
+
last_seen TEXT NOT NULL
|
|
594
|
+
);
|
|
595
|
+
`);
|
|
596
|
+
await client.executeMultiple(`
|
|
597
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
598
|
+
id TEXT PRIMARY KEY,
|
|
599
|
+
from_agent TEXT NOT NULL,
|
|
600
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
601
|
+
target_agent TEXT NOT NULL,
|
|
602
|
+
target_project TEXT,
|
|
603
|
+
target_device TEXT NOT NULL DEFAULT 'local',
|
|
604
|
+
content TEXT NOT NULL,
|
|
605
|
+
priority TEXT DEFAULT 'normal',
|
|
606
|
+
status TEXT DEFAULT 'pending',
|
|
607
|
+
server_seq INTEGER,
|
|
608
|
+
retry_count INTEGER DEFAULT 0,
|
|
609
|
+
created_at TEXT NOT NULL,
|
|
610
|
+
delivered_at TEXT,
|
|
611
|
+
processed_at TEXT,
|
|
612
|
+
failed_at TEXT,
|
|
613
|
+
failure_reason TEXT
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
617
|
+
ON messages(target_agent, status);
|
|
618
|
+
|
|
619
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
620
|
+
ON messages(target_agent, from_agent, server_seq);
|
|
621
|
+
`);
|
|
955
622
|
try {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
623
|
+
await client.execute({
|
|
624
|
+
sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
|
|
625
|
+
args: []
|
|
626
|
+
});
|
|
627
|
+
await client.execute({
|
|
628
|
+
sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
|
|
629
|
+
args: []
|
|
630
|
+
});
|
|
631
|
+
await client.execute({
|
|
632
|
+
sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
|
|
633
|
+
args: []
|
|
634
|
+
});
|
|
635
|
+
await client.execute({
|
|
636
|
+
sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
|
|
637
|
+
args: []
|
|
638
|
+
});
|
|
639
|
+
} catch {
|
|
967
640
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
641
|
+
await client.executeMultiple(`
|
|
642
|
+
CREATE TABLE IF NOT EXISTS trajectories (
|
|
643
|
+
id TEXT PRIMARY KEY,
|
|
644
|
+
task_id TEXT NOT NULL,
|
|
645
|
+
agent_id TEXT NOT NULL,
|
|
646
|
+
project_name TEXT NOT NULL,
|
|
647
|
+
task_title TEXT NOT NULL,
|
|
648
|
+
signature TEXT NOT NULL,
|
|
649
|
+
signature_hash TEXT NOT NULL,
|
|
650
|
+
tool_count INTEGER NOT NULL,
|
|
651
|
+
skill_id TEXT,
|
|
652
|
+
created_at TEXT NOT NULL
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
CREATE INDEX IF NOT EXISTS idx_trajectories_hash
|
|
656
|
+
ON trajectories(signature_hash);
|
|
657
|
+
|
|
658
|
+
CREATE INDEX IF NOT EXISTS idx_trajectories_agent
|
|
659
|
+
ON trajectories(agent_id);
|
|
660
|
+
`);
|
|
661
|
+
try {
|
|
662
|
+
await client.execute("ALTER TABLE trajectories ADD COLUMN skill_id TEXT");
|
|
663
|
+
} catch {
|
|
974
664
|
}
|
|
975
|
-
|
|
665
|
+
await client.executeMultiple(`
|
|
666
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
667
|
+
id TEXT PRIMARY KEY,
|
|
668
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
669
|
+
source_memory_id TEXT NOT NULL,
|
|
670
|
+
created_at TEXT NOT NULL
|
|
671
|
+
);
|
|
976
672
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
673
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
674
|
+
ON consolidations(source_memory_id);
|
|
675
|
+
`);
|
|
676
|
+
await client.executeMultiple(`
|
|
677
|
+
CREATE TABLE IF NOT EXISTS audit_trail (
|
|
678
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
679
|
+
timestamp TEXT NOT NULL,
|
|
680
|
+
session_id TEXT NOT NULL,
|
|
681
|
+
agent_id TEXT NOT NULL,
|
|
682
|
+
tool TEXT NOT NULL,
|
|
683
|
+
input TEXT,
|
|
684
|
+
decision TEXT NOT NULL,
|
|
685
|
+
reason TEXT,
|
|
686
|
+
is_customer_facing INTEGER NOT NULL DEFAULT 0
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
CREATE INDEX IF NOT EXISTS idx_audit_trail_agent
|
|
690
|
+
ON audit_trail(agent_id, timestamp);
|
|
691
|
+
|
|
692
|
+
CREATE INDEX IF NOT EXISTS idx_audit_trail_session
|
|
693
|
+
ON audit_trail(session_id);
|
|
694
|
+
`);
|
|
988
695
|
try {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
994
|
-
}).trim();
|
|
995
|
-
_cached = path5.basename(repoRoot);
|
|
996
|
-
_cachedCwd = dir;
|
|
997
|
-
return _cached;
|
|
696
|
+
await client.execute({
|
|
697
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
698
|
+
args: []
|
|
699
|
+
});
|
|
998
700
|
} catch {
|
|
999
|
-
_cached = path5.basename(dir);
|
|
1000
|
-
_cachedCwd = dir;
|
|
1001
|
-
return _cached;
|
|
1002
701
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
var init_project_name = __esm({
|
|
1010
|
-
"src/lib/project-name.ts"() {
|
|
1011
|
-
"use strict";
|
|
1012
|
-
_cached = null;
|
|
1013
|
-
_cachedCwd = null;
|
|
702
|
+
try {
|
|
703
|
+
await client.execute({
|
|
704
|
+
sql: `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`,
|
|
705
|
+
args: []
|
|
706
|
+
});
|
|
707
|
+
} catch {
|
|
1014
708
|
}
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
// src/lib/file-grep.ts
|
|
1018
|
-
var file_grep_exports = {};
|
|
1019
|
-
__export(file_grep_exports, {
|
|
1020
|
-
grepProjectFiles: () => grepProjectFiles
|
|
1021
|
-
});
|
|
1022
|
-
import { execSync as execSync2 } from "child_process";
|
|
1023
|
-
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
|
|
1024
|
-
import path6 from "path";
|
|
1025
|
-
import crypto2 from "crypto";
|
|
1026
|
-
async function grepProjectFiles(query, projectRoot, options) {
|
|
1027
|
-
const maxResults = options?.maxResults ?? 10;
|
|
1028
|
-
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_-]/g, "")).filter((t) => t.length >= 3);
|
|
1029
|
-
if (terms.length === 0) return [];
|
|
1030
|
-
const pattern = terms.join("|");
|
|
1031
|
-
let hits;
|
|
1032
709
|
try {
|
|
1033
|
-
|
|
710
|
+
await client.execute({
|
|
711
|
+
sql: `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`,
|
|
712
|
+
args: []
|
|
713
|
+
});
|
|
1034
714
|
} catch {
|
|
1035
|
-
hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
|
|
1036
715
|
}
|
|
1037
|
-
hits.sort((a, b) => b.density - a.density);
|
|
1038
|
-
const topHits = hits.slice(0, maxResults);
|
|
1039
|
-
return topHits.map((hit) => {
|
|
1040
|
-
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
1041
|
-
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
1042
|
-
return {
|
|
1043
|
-
id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
1044
|
-
agent_id: "project",
|
|
1045
|
-
agent_role: "file",
|
|
1046
|
-
session_id: "file-grep",
|
|
1047
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1048
|
-
tool_name: "file_grep",
|
|
1049
|
-
project_name: path6.basename(projectRoot),
|
|
1050
|
-
has_error: false,
|
|
1051
|
-
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
1052
|
-
vector: null,
|
|
1053
|
-
task_id: null
|
|
1054
|
-
};
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
function getChunkContext(filePath, lineNumber) {
|
|
1058
716
|
try {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
1064
|
-
const line = lines[i];
|
|
1065
|
-
const fnMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1066
|
-
if (fnMatch) return `function ${fnMatch[1]}`;
|
|
1067
|
-
const classMatch = line.match(/(?:export\s+)?class\s+(\w+)/);
|
|
1068
|
-
if (classMatch) return `class ${classMatch[1]}`;
|
|
1069
|
-
const arrowMatch = line.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
1070
|
-
if (arrowMatch) return `function ${arrowMatch[1]}`;
|
|
1071
|
-
}
|
|
717
|
+
await client.execute({
|
|
718
|
+
sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
|
|
719
|
+
args: []
|
|
720
|
+
});
|
|
1072
721
|
} catch {
|
|
1073
722
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const output = execSync2(cmd, {
|
|
1081
|
-
cwd: projectRoot,
|
|
1082
|
-
encoding: "utf8",
|
|
1083
|
-
timeout: 3e3,
|
|
1084
|
-
maxBuffer: 1024 * 1024
|
|
1085
|
-
});
|
|
1086
|
-
if (!output.trim()) return [];
|
|
1087
|
-
const hits = [];
|
|
1088
|
-
for (const line of output.trim().split("\n")) {
|
|
1089
|
-
const colonIdx = line.lastIndexOf(":");
|
|
1090
|
-
if (colonIdx === -1) continue;
|
|
1091
|
-
const filePath = line.slice(0, colonIdx);
|
|
1092
|
-
const matchCount = parseInt(line.slice(colonIdx + 1));
|
|
1093
|
-
if (isNaN(matchCount) || matchCount === 0) continue;
|
|
1094
|
-
try {
|
|
1095
|
-
const firstMatch = execSync2(
|
|
1096
|
-
`rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
|
|
1097
|
-
{ cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
|
|
1098
|
-
).trim();
|
|
1099
|
-
const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
|
|
1100
|
-
const totalLines = execSync2(`wc -l < '${filePath}'`, {
|
|
1101
|
-
cwd: projectRoot,
|
|
1102
|
-
encoding: "utf8",
|
|
1103
|
-
timeout: 1e3
|
|
1104
|
-
}).trim();
|
|
1105
|
-
hits.push({
|
|
1106
|
-
filePath,
|
|
1107
|
-
lineNumber: isNaN(lineNum) ? 1 : lineNum,
|
|
1108
|
-
matchLine: firstMatch.slice(firstMatch.indexOf(":") + 1).slice(0, 200),
|
|
1109
|
-
matchCount,
|
|
1110
|
-
totalLines: parseInt(totalLines) || 1,
|
|
1111
|
-
density: matchCount / (parseInt(totalLines) || 1)
|
|
1112
|
-
});
|
|
1113
|
-
} catch {
|
|
1114
|
-
}
|
|
723
|
+
try {
|
|
724
|
+
await client.execute({
|
|
725
|
+
sql: `ALTER TABLE memories ADD COLUMN last_accessed TEXT`,
|
|
726
|
+
args: []
|
|
727
|
+
});
|
|
728
|
+
} catch {
|
|
1115
729
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
730
|
+
try {
|
|
731
|
+
await client.execute({
|
|
732
|
+
sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
|
|
733
|
+
args: []
|
|
734
|
+
});
|
|
735
|
+
} catch {
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
await client.execute({
|
|
739
|
+
sql: `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`,
|
|
740
|
+
args: []
|
|
741
|
+
});
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
await client.execute({
|
|
746
|
+
sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
|
|
747
|
+
args: []
|
|
748
|
+
});
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
for (const col of [
|
|
752
|
+
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
753
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
|
|
754
|
+
]) {
|
|
1124
755
|
try {
|
|
1125
|
-
|
|
1126
|
-
if (stat.size > MAX_FILE_SIZE) continue;
|
|
1127
|
-
const content = readFileSync3(absPath, "utf8");
|
|
1128
|
-
const lines = content.split("\n");
|
|
1129
|
-
const matches = content.match(regex);
|
|
1130
|
-
if (!matches || matches.length === 0) continue;
|
|
1131
|
-
const firstMatchIdx = lines.findIndex((l) => regex.test(l));
|
|
1132
|
-
regex.lastIndex = 0;
|
|
1133
|
-
hits.push({
|
|
1134
|
-
filePath,
|
|
1135
|
-
lineNumber: firstMatchIdx >= 0 ? firstMatchIdx + 1 : 1,
|
|
1136
|
-
matchLine: firstMatchIdx >= 0 ? lines[firstMatchIdx].slice(0, 200) : "",
|
|
1137
|
-
matchCount: matches.length,
|
|
1138
|
-
totalLines: lines.length,
|
|
1139
|
-
density: matches.length / lines.length
|
|
1140
|
-
});
|
|
756
|
+
await client.execute(col);
|
|
1141
757
|
} catch {
|
|
1142
758
|
}
|
|
1143
759
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
760
|
+
await client.executeMultiple(`
|
|
761
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
762
|
+
id TEXT PRIMARY KEY,
|
|
763
|
+
name TEXT NOT NULL,
|
|
764
|
+
type TEXT NOT NULL,
|
|
765
|
+
first_seen TEXT NOT NULL,
|
|
766
|
+
last_seen TEXT NOT NULL,
|
|
767
|
+
properties TEXT DEFAULT '{}',
|
|
768
|
+
UNIQUE(name, type)
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
772
|
+
id TEXT PRIMARY KEY,
|
|
773
|
+
source_entity_id TEXT NOT NULL,
|
|
774
|
+
target_entity_id TEXT NOT NULL,
|
|
775
|
+
type TEXT NOT NULL,
|
|
776
|
+
weight REAL DEFAULT 1.0,
|
|
777
|
+
timestamp TEXT NOT NULL,
|
|
778
|
+
properties TEXT DEFAULT '{}',
|
|
779
|
+
UNIQUE(source_entity_id, target_entity_id, type)
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
783
|
+
entity_id TEXT NOT NULL,
|
|
784
|
+
memory_id TEXT NOT NULL,
|
|
785
|
+
PRIMARY KEY (entity_id, memory_id)
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
CREATE TABLE IF NOT EXISTS relationship_memories (
|
|
789
|
+
relationship_id TEXT NOT NULL,
|
|
790
|
+
memory_id TEXT NOT NULL,
|
|
791
|
+
PRIMARY KEY (relationship_id, memory_id)
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
795
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
796
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
797
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
798
|
+
|
|
799
|
+
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
800
|
+
id TEXT PRIMARY KEY,
|
|
801
|
+
label TEXT NOT NULL,
|
|
802
|
+
relation TEXT NOT NULL,
|
|
803
|
+
confidence REAL DEFAULT 1.0,
|
|
804
|
+
timestamp TEXT NOT NULL
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
CREATE TABLE IF NOT EXISTS hyperedge_nodes (
|
|
808
|
+
hyperedge_id TEXT NOT NULL,
|
|
809
|
+
entity_id TEXT NOT NULL,
|
|
810
|
+
PRIMARY KEY (hyperedge_id, entity_id)
|
|
811
|
+
);
|
|
812
|
+
`);
|
|
813
|
+
await client.executeMultiple(`
|
|
814
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
815
|
+
alias TEXT NOT NULL PRIMARY KEY,
|
|
816
|
+
canonical_entity_id TEXT NOT NULL
|
|
817
|
+
);
|
|
818
|
+
CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
|
|
819
|
+
`);
|
|
820
|
+
for (const col of [
|
|
821
|
+
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
822
|
+
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
823
|
+
]) {
|
|
1152
824
|
try {
|
|
1153
|
-
|
|
1154
|
-
for (const entry of entries) {
|
|
1155
|
-
if (files.length >= MAX_FILES) return;
|
|
1156
|
-
const rel = path6.join(relative, entry.name);
|
|
1157
|
-
if (entry.isDirectory()) {
|
|
1158
|
-
walk(path6.join(dir, entry.name), rel);
|
|
1159
|
-
} else if (entry.isFile()) {
|
|
1160
|
-
for (const pat of patterns) {
|
|
1161
|
-
if (matchGlob(rel, pat)) {
|
|
1162
|
-
files.push(rel);
|
|
1163
|
-
break;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
825
|
+
await client.execute(col);
|
|
1168
826
|
} catch {
|
|
1169
827
|
}
|
|
1170
828
|
}
|
|
1171
|
-
walk(root, "");
|
|
1172
|
-
return files;
|
|
1173
|
-
}
|
|
1174
|
-
function matchGlob(filePath, pattern) {
|
|
1175
|
-
if (!pattern.includes("*")) return filePath === pattern;
|
|
1176
|
-
const doubleStarMatch = pattern.match(/^(.+?)\/\*\*\/\*(\.\w+)$/);
|
|
1177
|
-
if (doubleStarMatch) {
|
|
1178
|
-
const dir = doubleStarMatch[1];
|
|
1179
|
-
const ext2 = doubleStarMatch[2];
|
|
1180
|
-
return filePath.startsWith(dir + "/") && filePath.endsWith(ext2);
|
|
1181
|
-
}
|
|
1182
|
-
if (pattern.startsWith("**/")) {
|
|
1183
|
-
const ext2 = pattern.slice(3).replace("*", "");
|
|
1184
|
-
return filePath.endsWith(ext2);
|
|
1185
|
-
}
|
|
1186
|
-
const slashIdx = pattern.lastIndexOf("/");
|
|
1187
|
-
if (slashIdx !== -1) {
|
|
1188
|
-
const dir = pattern.slice(0, slashIdx);
|
|
1189
|
-
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
1190
|
-
const fileDir = path6.dirname(filePath);
|
|
1191
|
-
return fileDir === dir && filePath.endsWith(ext2);
|
|
1192
|
-
}
|
|
1193
|
-
const ext = pattern.replace("*", "");
|
|
1194
|
-
return filePath.endsWith(ext) && !filePath.includes("/");
|
|
1195
|
-
}
|
|
1196
|
-
function buildSnippet(hit, projectRoot) {
|
|
1197
829
|
try {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const start = Math.max(0, hit.lineNumber - 3);
|
|
1202
|
-
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
1203
|
-
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
830
|
+
await client.execute(
|
|
831
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
|
|
832
|
+
);
|
|
1204
833
|
} catch {
|
|
1205
|
-
return hit.matchLine;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
var DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
|
|
1209
|
-
var init_file_grep = __esm({
|
|
1210
|
-
"src/lib/file-grep.ts"() {
|
|
1211
|
-
"use strict";
|
|
1212
|
-
DEFAULT_PATTERNS = [
|
|
1213
|
-
".planning/*.md",
|
|
1214
|
-
"exe/output/*.md",
|
|
1215
|
-
"README.md",
|
|
1216
|
-
"src/**/*.ts"
|
|
1217
|
-
];
|
|
1218
|
-
EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage", ".worktrees"];
|
|
1219
|
-
MAX_FILE_SIZE = 100 * 1024;
|
|
1220
|
-
MAX_FILES = 500;
|
|
1221
834
|
}
|
|
1222
|
-
|
|
835
|
+
await client.executeMultiple(`
|
|
836
|
+
CREATE TABLE IF NOT EXISTS identity (
|
|
837
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
838
|
+
agent_id TEXT NOT NULL UNIQUE,
|
|
839
|
+
content_hash TEXT NOT NULL,
|
|
840
|
+
updated_at TEXT NOT NULL,
|
|
841
|
+
updated_by TEXT NOT NULL
|
|
842
|
+
);
|
|
1223
843
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
if (_idleTimer) clearTimeout(_idleTimer);
|
|
1237
|
-
_idleTimer = setTimeout(() => {
|
|
1238
|
-
void disposeReranker();
|
|
1239
|
-
}, IDLE_TIMEOUT_MS);
|
|
1240
|
-
if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
|
|
1241
|
-
_idleTimer.unref();
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
function isRerankerAvailable() {
|
|
1245
|
-
return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
1246
|
-
}
|
|
1247
|
-
function getRerankerModelPath() {
|
|
1248
|
-
return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
1249
|
-
}
|
|
1250
|
-
async function ensureLoaded() {
|
|
1251
|
-
if (_rerankerContext) {
|
|
1252
|
-
resetIdleTimer();
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
1256
|
-
if (!existsSync6(modelPath)) {
|
|
1257
|
-
throw new Error(
|
|
1258
|
-
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
844
|
+
CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
|
|
845
|
+
`);
|
|
846
|
+
await client.executeMultiple(`
|
|
847
|
+
CREATE TABLE IF NOT EXISTS chat_history (
|
|
848
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
849
|
+
session_id TEXT NOT NULL,
|
|
850
|
+
role TEXT NOT NULL,
|
|
851
|
+
content TEXT NOT NULL,
|
|
852
|
+
tool_name TEXT,
|
|
853
|
+
tool_id TEXT,
|
|
854
|
+
is_error INTEGER NOT NULL DEFAULT 0,
|
|
855
|
+
timestamp INTEGER NOT NULL
|
|
1259
856
|
);
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
857
|
+
|
|
858
|
+
CREATE INDEX IF NOT EXISTS idx_chat_history_session
|
|
859
|
+
ON chat_history(session_id, id);
|
|
860
|
+
`);
|
|
861
|
+
await client.executeMultiple(`
|
|
862
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
863
|
+
id TEXT PRIMARY KEY,
|
|
864
|
+
slug TEXT NOT NULL UNIQUE,
|
|
865
|
+
name TEXT NOT NULL,
|
|
866
|
+
owner_agent_id TEXT,
|
|
867
|
+
created_at TEXT NOT NULL,
|
|
868
|
+
metadata TEXT
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
CREATE INDEX IF NOT EXISTS idx_workspaces_slug
|
|
872
|
+
ON workspaces(slug);
|
|
873
|
+
`);
|
|
874
|
+
await client.executeMultiple(`
|
|
875
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
876
|
+
id TEXT PRIMARY KEY,
|
|
877
|
+
workspace_id TEXT NOT NULL,
|
|
878
|
+
filename TEXT NOT NULL,
|
|
879
|
+
mime TEXT,
|
|
880
|
+
source_type TEXT,
|
|
881
|
+
user_id TEXT,
|
|
882
|
+
uploaded_at TEXT NOT NULL,
|
|
883
|
+
metadata TEXT,
|
|
884
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
CREATE INDEX IF NOT EXISTS idx_documents_workspace
|
|
888
|
+
ON documents(workspace_id);
|
|
889
|
+
|
|
890
|
+
CREATE INDEX IF NOT EXISTS idx_documents_user
|
|
891
|
+
ON documents(user_id);
|
|
892
|
+
`);
|
|
893
|
+
for (const column of [
|
|
894
|
+
"workspace_id TEXT",
|
|
895
|
+
"document_id TEXT",
|
|
896
|
+
"user_id TEXT",
|
|
897
|
+
"char_offset INTEGER",
|
|
898
|
+
"page_number INTEGER"
|
|
899
|
+
]) {
|
|
1282
900
|
try {
|
|
1283
|
-
await
|
|
901
|
+
await client.execute({
|
|
902
|
+
sql: `ALTER TABLE memories ADD COLUMN ${column}`,
|
|
903
|
+
args: []
|
|
904
|
+
});
|
|
1284
905
|
} catch {
|
|
1285
906
|
}
|
|
1286
|
-
_rerankerModel = null;
|
|
1287
907
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
await ensureLoaded();
|
|
1293
|
-
const ctx = _rerankerContext;
|
|
1294
|
-
const scored = [];
|
|
1295
|
-
for (let i = 0; i < texts.length; i++) {
|
|
1296
|
-
const text = texts[i] ?? "";
|
|
908
|
+
for (const col of [
|
|
909
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
910
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
911
|
+
]) {
|
|
1297
912
|
try {
|
|
1298
|
-
|
|
1299
|
-
const embedding = await ctx.getEmbeddingFor(input2);
|
|
1300
|
-
const score = embedding.vector[0] ?? 0;
|
|
1301
|
-
scored.push({ text, score, index: i });
|
|
913
|
+
await client.execute(col);
|
|
1302
914
|
} catch {
|
|
1303
|
-
scored.push({ text, score: -1, index: i });
|
|
1304
915
|
}
|
|
1305
916
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
async function rerank(query, candidates, topK = 5) {
|
|
1310
|
-
if (candidates.length === 0) return [];
|
|
1311
|
-
if (candidates.length <= topK) return candidates;
|
|
1312
|
-
const scored = await rerankWithScores(
|
|
1313
|
-
query,
|
|
1314
|
-
candidates.map((c) => c.raw_text),
|
|
1315
|
-
topK
|
|
1316
|
-
);
|
|
1317
|
-
return scored.map((s) => candidates[s.index]);
|
|
1318
|
-
}
|
|
1319
|
-
var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
|
|
1320
|
-
var init_reranker = __esm({
|
|
1321
|
-
"src/lib/reranker.ts"() {
|
|
1322
|
-
"use strict";
|
|
1323
|
-
init_config();
|
|
1324
|
-
RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
|
|
1325
|
-
IDLE_TIMEOUT_MS = 6e4;
|
|
1326
|
-
_rerankerContext = null;
|
|
1327
|
-
_rerankerModel = null;
|
|
1328
|
-
_idleTimer = null;
|
|
1329
|
-
}
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
// src/adapters/claude/hooks/session-start.ts
|
|
1333
|
-
init_config();
|
|
1334
|
-
init_config();
|
|
1335
|
-
import path9 from "path";
|
|
1336
|
-
import { unlinkSync as unlinkSync3 } from "fs";
|
|
917
|
+
await client.executeMultiple(`
|
|
918
|
+
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
919
|
+
ON memories(workspace_id);
|
|
1337
920
|
|
|
1338
|
-
|
|
1339
|
-
|
|
921
|
+
CREATE INDEX IF NOT EXISTS idx_memories_document
|
|
922
|
+
ON memories(document_id);
|
|
1340
923
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
var initTurso = initDatabase;
|
|
1345
|
-
async function initDatabase(config) {
|
|
1346
|
-
if (_client) {
|
|
1347
|
-
_client.close();
|
|
1348
|
-
_client = null;
|
|
1349
|
-
}
|
|
1350
|
-
const opts = {
|
|
1351
|
-
url: `file:${config.dbPath}`
|
|
1352
|
-
};
|
|
1353
|
-
if (config.encryptionKey) {
|
|
1354
|
-
opts.encryptionKey = config.encryptionKey;
|
|
1355
|
-
}
|
|
1356
|
-
_client = createClient(opts);
|
|
1357
|
-
}
|
|
1358
|
-
function getClient() {
|
|
1359
|
-
if (!_client) {
|
|
1360
|
-
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1361
|
-
}
|
|
1362
|
-
return _client;
|
|
1363
|
-
}
|
|
1364
|
-
async function ensureSchema() {
|
|
1365
|
-
const client = getClient();
|
|
1366
|
-
await client.execute("PRAGMA journal_mode = WAL");
|
|
1367
|
-
await client.execute("PRAGMA busy_timeout = 5000");
|
|
1368
|
-
try {
|
|
1369
|
-
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1370
|
-
} catch {
|
|
1371
|
-
}
|
|
924
|
+
CREATE INDEX IF NOT EXISTS idx_memories_user
|
|
925
|
+
ON memories(user_id);
|
|
926
|
+
`);
|
|
1372
927
|
await client.executeMultiple(`
|
|
1373
|
-
CREATE TABLE IF NOT EXISTS
|
|
1374
|
-
id
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1382
|
-
raw_text TEXT NOT NULL,
|
|
1383
|
-
vector F32_BLOB(1024),
|
|
1384
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
928
|
+
CREATE TABLE IF NOT EXISTS session_kills (
|
|
929
|
+
id TEXT PRIMARY KEY,
|
|
930
|
+
session_name TEXT NOT NULL,
|
|
931
|
+
agent_id TEXT NOT NULL,
|
|
932
|
+
killed_at TIMESTAMP NOT NULL,
|
|
933
|
+
reason TEXT NOT NULL,
|
|
934
|
+
ticks_idle INTEGER,
|
|
935
|
+
estimated_tokens_saved INTEGER
|
|
1385
936
|
);
|
|
1386
937
|
|
|
1387
|
-
CREATE INDEX IF NOT EXISTS
|
|
1388
|
-
ON
|
|
1389
|
-
|
|
1390
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1391
|
-
ON memories(timestamp);
|
|
938
|
+
CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
|
|
939
|
+
ON session_kills(killed_at);
|
|
1392
940
|
|
|
1393
|
-
CREATE INDEX IF NOT EXISTS
|
|
1394
|
-
ON
|
|
1395
|
-
|
|
1396
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1397
|
-
ON memories(project_name);
|
|
1398
|
-
|
|
1399
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1400
|
-
ON memories(tool_name);
|
|
1401
|
-
|
|
1402
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1403
|
-
ON memories(version);
|
|
1404
|
-
|
|
1405
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1406
|
-
ON memories(agent_id, project_name);
|
|
941
|
+
CREATE INDEX IF NOT EXISTS idx_session_kills_agent
|
|
942
|
+
ON session_kills(agent_id);
|
|
1407
943
|
`);
|
|
1408
944
|
await client.executeMultiple(`
|
|
1409
|
-
CREATE
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
945
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
946
|
+
id TEXT PRIMARY KEY,
|
|
947
|
+
platform TEXT NOT NULL,
|
|
948
|
+
external_id TEXT,
|
|
949
|
+
sender_id TEXT NOT NULL,
|
|
950
|
+
sender_name TEXT,
|
|
951
|
+
sender_phone TEXT,
|
|
952
|
+
sender_email TEXT,
|
|
953
|
+
recipient_id TEXT,
|
|
954
|
+
channel_id TEXT NOT NULL,
|
|
955
|
+
thread_id TEXT,
|
|
956
|
+
reply_to_id TEXT,
|
|
957
|
+
content_text TEXT,
|
|
958
|
+
content_media TEXT,
|
|
959
|
+
content_metadata TEXT,
|
|
960
|
+
agent_response TEXT,
|
|
961
|
+
agent_name TEXT,
|
|
962
|
+
timestamp TEXT NOT NULL,
|
|
963
|
+
ingested_at TEXT NOT NULL
|
|
1413
964
|
);
|
|
1414
965
|
|
|
1415
|
-
CREATE
|
|
1416
|
-
|
|
1417
|
-
END;
|
|
966
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_platform
|
|
967
|
+
ON conversations(platform);
|
|
1418
968
|
|
|
1419
|
-
CREATE
|
|
1420
|
-
|
|
1421
|
-
END;
|
|
969
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_sender
|
|
970
|
+
ON conversations(sender_id);
|
|
1422
971
|
|
|
1423
|
-
CREATE
|
|
1424
|
-
|
|
1425
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1426
|
-
END;
|
|
1427
|
-
`);
|
|
1428
|
-
await client.executeMultiple(`
|
|
1429
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1430
|
-
key TEXT PRIMARY KEY,
|
|
1431
|
-
value TEXT NOT NULL
|
|
1432
|
-
);
|
|
1433
|
-
`);
|
|
1434
|
-
await client.executeMultiple(`
|
|
1435
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
1436
|
-
id TEXT PRIMARY KEY,
|
|
1437
|
-
title TEXT NOT NULL,
|
|
1438
|
-
assigned_to TEXT NOT NULL,
|
|
1439
|
-
assigned_by TEXT NOT NULL,
|
|
1440
|
-
project_name TEXT NOT NULL,
|
|
1441
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1442
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
1443
|
-
task_file TEXT,
|
|
1444
|
-
created_at TEXT NOT NULL,
|
|
1445
|
-
updated_at TEXT NOT NULL
|
|
1446
|
-
);
|
|
972
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
|
|
973
|
+
ON conversations(timestamp);
|
|
1447
974
|
|
|
1448
|
-
CREATE INDEX IF NOT EXISTS
|
|
1449
|
-
ON
|
|
1450
|
-
`);
|
|
1451
|
-
await client.executeMultiple(`
|
|
1452
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1453
|
-
id TEXT PRIMARY KEY,
|
|
1454
|
-
agent_id TEXT NOT NULL,
|
|
1455
|
-
project_name TEXT,
|
|
1456
|
-
domain TEXT,
|
|
1457
|
-
content TEXT NOT NULL,
|
|
1458
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
1459
|
-
created_at TEXT NOT NULL,
|
|
1460
|
-
updated_at TEXT NOT NULL
|
|
1461
|
-
);
|
|
975
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_thread
|
|
976
|
+
ON conversations(thread_id);
|
|
1462
977
|
|
|
1463
|
-
CREATE INDEX IF NOT EXISTS
|
|
1464
|
-
ON
|
|
978
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
979
|
+
ON conversations(channel_id);
|
|
1465
980
|
`);
|
|
1466
|
-
try {
|
|
1467
|
-
const existing = await client.execute({
|
|
1468
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
|
|
1469
|
-
args: []
|
|
1470
|
-
});
|
|
1471
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1472
|
-
await client.executeMultiple(`
|
|
1473
|
-
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1474
|
-
VALUES
|
|
1475
|
-
(hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
1476
|
-
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1477
|
-
VALUES
|
|
1478
|
-
(hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
1479
|
-
INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1480
|
-
VALUES
|
|
1481
|
-
(hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
|
|
1482
|
-
`);
|
|
1483
|
-
}
|
|
1484
|
-
} catch {
|
|
1485
|
-
}
|
|
1486
981
|
try {
|
|
1487
982
|
await client.execute({
|
|
1488
|
-
sql: `ALTER TABLE
|
|
983
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1489
984
|
args: []
|
|
1490
985
|
});
|
|
1491
986
|
} catch {
|
|
1492
987
|
}
|
|
1493
988
|
try {
|
|
1494
989
|
await client.execute({
|
|
1495
|
-
sql: `ALTER TABLE tasks ADD COLUMN
|
|
990
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1496
991
|
args: []
|
|
1497
992
|
});
|
|
1498
993
|
} catch {
|
|
1499
994
|
}
|
|
1500
995
|
try {
|
|
1501
996
|
await client.execute({
|
|
1502
|
-
sql: `ALTER TABLE tasks ADD COLUMN
|
|
997
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1503
998
|
args: []
|
|
1504
999
|
});
|
|
1505
1000
|
} catch {
|
|
1506
1001
|
}
|
|
1507
1002
|
try {
|
|
1508
1003
|
await client.execute({
|
|
1509
|
-
sql: `
|
|
1510
|
-
ON tasks(parent_task_id)
|
|
1511
|
-
WHERE parent_task_id IS NOT NULL`,
|
|
1004
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1512
1005
|
args: []
|
|
1513
1006
|
});
|
|
1514
1007
|
} catch {
|
|
1515
1008
|
}
|
|
1009
|
+
await client.executeMultiple(`
|
|
1010
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
1011
|
+
content_text,
|
|
1012
|
+
sender_name,
|
|
1013
|
+
agent_response,
|
|
1014
|
+
content='conversations',
|
|
1015
|
+
content_rowid='rowid'
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
|
|
1019
|
+
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
1020
|
+
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1021
|
+
END;
|
|
1022
|
+
|
|
1023
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
|
|
1024
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1025
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1026
|
+
END;
|
|
1027
|
+
|
|
1028
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
|
|
1029
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1030
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1031
|
+
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
1032
|
+
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1033
|
+
END;
|
|
1034
|
+
`);
|
|
1516
1035
|
try {
|
|
1517
1036
|
await client.execute({
|
|
1518
|
-
sql: `
|
|
1037
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1519
1038
|
args: []
|
|
1520
1039
|
});
|
|
1521
1040
|
} catch {
|
|
1522
1041
|
}
|
|
1523
1042
|
try {
|
|
1524
|
-
await client.execute(
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
});
|
|
1043
|
+
await client.execute(
|
|
1044
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1045
|
+
);
|
|
1528
1046
|
} catch {
|
|
1529
1047
|
}
|
|
1530
1048
|
try {
|
|
1531
1049
|
await client.execute({
|
|
1532
|
-
sql: `
|
|
1050
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1533
1051
|
args: []
|
|
1534
1052
|
});
|
|
1535
|
-
} catch {
|
|
1536
|
-
}
|
|
1537
|
-
try {
|
|
1538
1053
|
await client.execute({
|
|
1539
|
-
sql: `
|
|
1054
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1540
1055
|
args: []
|
|
1541
1056
|
});
|
|
1542
1057
|
} catch {
|
|
1543
1058
|
}
|
|
1544
1059
|
try {
|
|
1545
1060
|
await client.execute({
|
|
1546
|
-
sql: `ALTER TABLE
|
|
1061
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1547
1062
|
args: []
|
|
1548
1063
|
});
|
|
1549
1064
|
} catch {
|
|
1550
1065
|
}
|
|
1551
1066
|
try {
|
|
1552
|
-
await client.execute(
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
});
|
|
1067
|
+
await client.execute(
|
|
1068
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1069
|
+
);
|
|
1556
1070
|
} catch {
|
|
1557
1071
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1072
|
+
for (const col of [
|
|
1073
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1074
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1075
|
+
]) {
|
|
1076
|
+
try {
|
|
1077
|
+
await client.execute(col);
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async function disposeDatabase() {
|
|
1083
|
+
if (_client) {
|
|
1084
|
+
_client.close();
|
|
1085
|
+
_client = null;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
var _client, initTurso, disposeTurso;
|
|
1089
|
+
var init_database = __esm({
|
|
1090
|
+
"src/lib/database.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
_client = null;
|
|
1093
|
+
initTurso = initDatabase;
|
|
1094
|
+
disposeTurso = disposeDatabase;
|
|
1564
1095
|
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/lib/keychain.ts
|
|
1099
|
+
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
|
|
1100
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1101
|
+
import path2 from "path";
|
|
1102
|
+
import crypto from "crypto";
|
|
1103
|
+
function getKeyDir() {
|
|
1104
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
|
|
1105
|
+
}
|
|
1106
|
+
function getKeyPath() {
|
|
1107
|
+
return path2.join(getKeyDir(), "master.key");
|
|
1108
|
+
}
|
|
1109
|
+
async function tryKeytar() {
|
|
1565
1110
|
try {
|
|
1566
|
-
await
|
|
1567
|
-
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1568
|
-
args: []
|
|
1569
|
-
});
|
|
1111
|
+
return await import("keytar");
|
|
1570
1112
|
} catch {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async function getMasterKey() {
|
|
1117
|
+
const keytar = await tryKeytar();
|
|
1118
|
+
if (keytar) {
|
|
1119
|
+
try {
|
|
1120
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
1121
|
+
if (stored) {
|
|
1122
|
+
return Buffer.from(stored, "base64");
|
|
1123
|
+
}
|
|
1124
|
+
} catch {
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const keyPath = getKeyPath();
|
|
1128
|
+
if (!existsSync2(keyPath)) {
|
|
1129
|
+
return null;
|
|
1571
1130
|
}
|
|
1572
1131
|
try {
|
|
1573
|
-
await
|
|
1574
|
-
|
|
1575
|
-
args: []
|
|
1576
|
-
});
|
|
1132
|
+
const content = await readFile2(keyPath, "utf-8");
|
|
1133
|
+
return Buffer.from(content.trim(), "base64");
|
|
1577
1134
|
} catch {
|
|
1135
|
+
return null;
|
|
1578
1136
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
ON consolidations(source_memory_id);
|
|
1137
|
+
}
|
|
1138
|
+
var SERVICE, ACCOUNT;
|
|
1139
|
+
var init_keychain = __esm({
|
|
1140
|
+
"src/lib/keychain.ts"() {
|
|
1141
|
+
"use strict";
|
|
1142
|
+
SERVICE = "exe-mem";
|
|
1143
|
+
ACCOUNT = "master-key";
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1589
1146
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1147
|
+
// src/lib/shard-manager.ts
|
|
1148
|
+
var shard_manager_exports = {};
|
|
1149
|
+
__export(shard_manager_exports, {
|
|
1150
|
+
disposeShards: () => disposeShards,
|
|
1151
|
+
ensureShardSchema: () => ensureShardSchema,
|
|
1152
|
+
getReadyShardClient: () => getReadyShardClient,
|
|
1153
|
+
getShardClient: () => getShardClient,
|
|
1154
|
+
getShardsDir: () => getShardsDir,
|
|
1155
|
+
initShardManager: () => initShardManager,
|
|
1156
|
+
isShardingEnabled: () => isShardingEnabled,
|
|
1157
|
+
listShards: () => listShards,
|
|
1158
|
+
shardExists: () => shardExists
|
|
1159
|
+
});
|
|
1160
|
+
import path3 from "path";
|
|
1161
|
+
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
1162
|
+
import { createClient as createClient2 } from "@libsql/client";
|
|
1163
|
+
function initShardManager(encryptionKey) {
|
|
1164
|
+
_encryptionKey = encryptionKey;
|
|
1165
|
+
if (!existsSync3(SHARDS_DIR)) {
|
|
1166
|
+
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1167
|
+
}
|
|
1168
|
+
_shardingEnabled = true;
|
|
1169
|
+
}
|
|
1170
|
+
function isShardingEnabled() {
|
|
1171
|
+
return _shardingEnabled;
|
|
1172
|
+
}
|
|
1173
|
+
function getShardsDir() {
|
|
1174
|
+
return SHARDS_DIR;
|
|
1175
|
+
}
|
|
1176
|
+
function getShardClient(projectName) {
|
|
1177
|
+
if (!_encryptionKey) {
|
|
1178
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
1179
|
+
}
|
|
1180
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1181
|
+
if (!safeName) {
|
|
1182
|
+
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1183
|
+
}
|
|
1184
|
+
const cached = _shards.get(safeName);
|
|
1185
|
+
if (cached) return cached;
|
|
1186
|
+
const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
|
|
1187
|
+
const client = createClient2({
|
|
1188
|
+
url: `file:${dbPath}`,
|
|
1189
|
+
encryptionKey: _encryptionKey
|
|
1190
|
+
});
|
|
1191
|
+
_shards.set(safeName, client);
|
|
1192
|
+
return client;
|
|
1193
|
+
}
|
|
1194
|
+
function shardExists(projectName) {
|
|
1195
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1196
|
+
return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
|
|
1197
|
+
}
|
|
1198
|
+
function listShards() {
|
|
1199
|
+
if (!existsSync3(SHARDS_DIR)) return [];
|
|
1200
|
+
const { readdirSync: readdirSync3 } = __require("fs");
|
|
1201
|
+
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1202
|
+
}
|
|
1203
|
+
async function ensureShardSchema(client) {
|
|
1204
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1205
|
+
await client.execute("PRAGMA busy_timeout = 5000");
|
|
1206
|
+
try {
|
|
1207
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1624
1210
|
await client.executeMultiple(`
|
|
1625
|
-
CREATE TABLE IF NOT EXISTS
|
|
1211
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1626
1212
|
id TEXT PRIMARY KEY,
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
project_name TEXT,
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
`);
|
|
1638
|
-
await client.executeMultiple(`
|
|
1639
|
-
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1640
|
-
device_id TEXT PRIMARY KEY,
|
|
1641
|
-
friendly_name TEXT NOT NULL,
|
|
1642
|
-
hostname TEXT NOT NULL,
|
|
1643
|
-
projects TEXT NOT NULL DEFAULT '[]',
|
|
1644
|
-
agents TEXT NOT NULL DEFAULT '[]',
|
|
1645
|
-
connected INTEGER DEFAULT 0,
|
|
1646
|
-
last_seen TEXT NOT NULL
|
|
1213
|
+
agent_id TEXT NOT NULL,
|
|
1214
|
+
agent_role TEXT NOT NULL,
|
|
1215
|
+
session_id TEXT NOT NULL,
|
|
1216
|
+
timestamp TEXT NOT NULL,
|
|
1217
|
+
tool_name TEXT NOT NULL,
|
|
1218
|
+
project_name TEXT NOT NULL,
|
|
1219
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1220
|
+
raw_text TEXT NOT NULL,
|
|
1221
|
+
vector F32_BLOB(1024),
|
|
1222
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
1647
1223
|
);
|
|
1224
|
+
|
|
1225
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
|
|
1226
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
1227
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
|
|
1648
1228
|
`);
|
|
1649
1229
|
await client.executeMultiple(`
|
|
1650
|
-
CREATE TABLE IF NOT EXISTS
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
target_agent TEXT NOT NULL,
|
|
1655
|
-
target_project TEXT,
|
|
1656
|
-
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1657
|
-
content TEXT NOT NULL,
|
|
1658
|
-
priority TEXT DEFAULT 'normal',
|
|
1659
|
-
status TEXT DEFAULT 'pending',
|
|
1660
|
-
server_seq INTEGER,
|
|
1661
|
-
retry_count INTEGER DEFAULT 0,
|
|
1662
|
-
created_at TEXT NOT NULL,
|
|
1663
|
-
delivered_at TEXT,
|
|
1664
|
-
processed_at TEXT,
|
|
1665
|
-
failed_at TEXT,
|
|
1666
|
-
failure_reason TEXT
|
|
1230
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1231
|
+
raw_text,
|
|
1232
|
+
content='memories',
|
|
1233
|
+
content_rowid='rowid'
|
|
1667
1234
|
);
|
|
1668
1235
|
|
|
1669
|
-
CREATE
|
|
1670
|
-
|
|
1236
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1237
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1238
|
+
END;
|
|
1671
1239
|
|
|
1672
|
-
CREATE
|
|
1673
|
-
|
|
1240
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1241
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1242
|
+
END;
|
|
1243
|
+
|
|
1244
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1245
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1246
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1247
|
+
END;
|
|
1674
1248
|
`);
|
|
1249
|
+
for (const col of [
|
|
1250
|
+
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1251
|
+
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
1252
|
+
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1253
|
+
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1254
|
+
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
1255
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
1256
|
+
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
1257
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
1258
|
+
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
1259
|
+
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
1260
|
+
// Wiki linkage columns (must match database.ts)
|
|
1261
|
+
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
1262
|
+
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1263
|
+
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1264
|
+
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1265
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1266
|
+
// Source provenance columns (must match database.ts)
|
|
1267
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1268
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1269
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1270
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1271
|
+
]) {
|
|
1272
|
+
try {
|
|
1273
|
+
await client.execute(col);
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
for (const idx of [
|
|
1278
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1279
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1280
|
+
]) {
|
|
1281
|
+
try {
|
|
1282
|
+
await client.execute(idx);
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1675
1286
|
try {
|
|
1676
|
-
await client.execute(
|
|
1677
|
-
sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
|
|
1678
|
-
args: []
|
|
1679
|
-
});
|
|
1680
|
-
await client.execute({
|
|
1681
|
-
sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
|
|
1682
|
-
args: []
|
|
1683
|
-
});
|
|
1684
|
-
await client.execute({
|
|
1685
|
-
sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
|
|
1686
|
-
args: []
|
|
1687
|
-
});
|
|
1688
|
-
await client.execute({
|
|
1689
|
-
sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
|
|
1690
|
-
args: []
|
|
1691
|
-
});
|
|
1287
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1692
1288
|
} catch {
|
|
1693
1289
|
}
|
|
1290
|
+
for (const idx of [
|
|
1291
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
|
|
1292
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
|
|
1293
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
|
|
1294
|
+
]) {
|
|
1295
|
+
try {
|
|
1296
|
+
await client.execute(idx);
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1694
1300
|
await client.executeMultiple(`
|
|
1695
|
-
CREATE TABLE IF NOT EXISTS
|
|
1696
|
-
id
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
tool_count INTEGER NOT NULL,
|
|
1704
|
-
skill_id TEXT,
|
|
1705
|
-
created_at TEXT NOT NULL
|
|
1301
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
1302
|
+
id TEXT PRIMARY KEY,
|
|
1303
|
+
name TEXT NOT NULL,
|
|
1304
|
+
type TEXT NOT NULL,
|
|
1305
|
+
first_seen TEXT NOT NULL,
|
|
1306
|
+
last_seen TEXT NOT NULL,
|
|
1307
|
+
properties TEXT DEFAULT '{}',
|
|
1308
|
+
UNIQUE(name, type)
|
|
1706
1309
|
);
|
|
1707
1310
|
|
|
1708
|
-
CREATE
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
}
|
|
1718
|
-
await client.executeMultiple(`
|
|
1719
|
-
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1720
|
-
id TEXT PRIMARY KEY,
|
|
1721
|
-
consolidated_memory_id TEXT NOT NULL,
|
|
1722
|
-
source_memory_id TEXT NOT NULL,
|
|
1723
|
-
created_at TEXT NOT NULL
|
|
1724
|
-
);
|
|
1725
|
-
|
|
1726
|
-
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1727
|
-
ON consolidations(source_memory_id);
|
|
1728
|
-
`);
|
|
1729
|
-
await client.executeMultiple(`
|
|
1730
|
-
CREATE TABLE IF NOT EXISTS audit_trail (
|
|
1731
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1732
|
-
timestamp TEXT NOT NULL,
|
|
1733
|
-
session_id TEXT NOT NULL,
|
|
1734
|
-
agent_id TEXT NOT NULL,
|
|
1735
|
-
tool TEXT NOT NULL,
|
|
1736
|
-
input TEXT,
|
|
1737
|
-
decision TEXT NOT NULL,
|
|
1738
|
-
reason TEXT,
|
|
1739
|
-
is_customer_facing INTEGER NOT NULL DEFAULT 0
|
|
1740
|
-
);
|
|
1741
|
-
|
|
1742
|
-
CREATE INDEX IF NOT EXISTS idx_audit_trail_agent
|
|
1743
|
-
ON audit_trail(agent_id, timestamp);
|
|
1744
|
-
|
|
1745
|
-
CREATE INDEX IF NOT EXISTS idx_audit_trail_session
|
|
1746
|
-
ON audit_trail(session_id);
|
|
1747
|
-
`);
|
|
1748
|
-
try {
|
|
1749
|
-
await client.execute({
|
|
1750
|
-
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1751
|
-
args: []
|
|
1752
|
-
});
|
|
1753
|
-
} catch {
|
|
1754
|
-
}
|
|
1755
|
-
try {
|
|
1756
|
-
await client.execute({
|
|
1757
|
-
sql: `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`,
|
|
1758
|
-
args: []
|
|
1759
|
-
});
|
|
1760
|
-
} catch {
|
|
1761
|
-
}
|
|
1762
|
-
try {
|
|
1763
|
-
await client.execute({
|
|
1764
|
-
sql: `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`,
|
|
1765
|
-
args: []
|
|
1766
|
-
});
|
|
1767
|
-
} catch {
|
|
1768
|
-
}
|
|
1769
|
-
try {
|
|
1770
|
-
await client.execute({
|
|
1771
|
-
sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
|
|
1772
|
-
args: []
|
|
1773
|
-
});
|
|
1774
|
-
} catch {
|
|
1775
|
-
}
|
|
1776
|
-
try {
|
|
1777
|
-
await client.execute({
|
|
1778
|
-
sql: `ALTER TABLE memories ADD COLUMN last_accessed TEXT`,
|
|
1779
|
-
args: []
|
|
1780
|
-
});
|
|
1781
|
-
} catch {
|
|
1782
|
-
}
|
|
1783
|
-
try {
|
|
1784
|
-
await client.execute({
|
|
1785
|
-
sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
|
|
1786
|
-
args: []
|
|
1787
|
-
});
|
|
1788
|
-
} catch {
|
|
1789
|
-
}
|
|
1790
|
-
try {
|
|
1791
|
-
await client.execute({
|
|
1792
|
-
sql: `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`,
|
|
1793
|
-
args: []
|
|
1794
|
-
});
|
|
1795
|
-
} catch {
|
|
1796
|
-
}
|
|
1797
|
-
try {
|
|
1798
|
-
await client.execute({
|
|
1799
|
-
sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
|
|
1800
|
-
args: []
|
|
1801
|
-
});
|
|
1802
|
-
} catch {
|
|
1803
|
-
}
|
|
1804
|
-
for (const col of [
|
|
1805
|
-
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
1806
|
-
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
|
|
1807
|
-
]) {
|
|
1808
|
-
try {
|
|
1809
|
-
await client.execute(col);
|
|
1810
|
-
} catch {
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
await client.executeMultiple(`
|
|
1814
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
1815
|
-
id TEXT PRIMARY KEY,
|
|
1816
|
-
name TEXT NOT NULL,
|
|
1817
|
-
type TEXT NOT NULL,
|
|
1818
|
-
first_seen TEXT NOT NULL,
|
|
1819
|
-
last_seen TEXT NOT NULL,
|
|
1820
|
-
properties TEXT DEFAULT '{}',
|
|
1821
|
-
UNIQUE(name, type)
|
|
1822
|
-
);
|
|
1823
|
-
|
|
1824
|
-
CREATE TABLE IF NOT EXISTS relationships (
|
|
1825
|
-
id TEXT PRIMARY KEY,
|
|
1826
|
-
source_entity_id TEXT NOT NULL,
|
|
1827
|
-
target_entity_id TEXT NOT NULL,
|
|
1828
|
-
type TEXT NOT NULL,
|
|
1829
|
-
weight REAL DEFAULT 1.0,
|
|
1830
|
-
timestamp TEXT NOT NULL,
|
|
1831
|
-
properties TEXT DEFAULT '{}',
|
|
1832
|
-
UNIQUE(source_entity_id, target_entity_id, type)
|
|
1311
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
1312
|
+
id TEXT PRIMARY KEY,
|
|
1313
|
+
source_entity_id TEXT NOT NULL,
|
|
1314
|
+
target_entity_id TEXT NOT NULL,
|
|
1315
|
+
type TEXT NOT NULL,
|
|
1316
|
+
weight REAL DEFAULT 1.0,
|
|
1317
|
+
timestamp TEXT NOT NULL,
|
|
1318
|
+
properties TEXT DEFAULT '{}',
|
|
1319
|
+
UNIQUE(source_entity_id, target_entity_id, type)
|
|
1833
1320
|
);
|
|
1834
1321
|
|
|
1835
1322
|
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
@@ -1848,6 +1335,7 @@ async function ensureSchema() {
|
|
|
1848
1335
|
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
1849
1336
|
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
1850
1337
|
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
1338
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
1851
1339
|
|
|
1852
1340
|
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
1853
1341
|
id TEXT PRIMARY KEY,
|
|
@@ -1863,13 +1351,6 @@ async function ensureSchema() {
|
|
|
1863
1351
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1864
1352
|
);
|
|
1865
1353
|
`);
|
|
1866
|
-
await client.executeMultiple(`
|
|
1867
|
-
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1868
|
-
alias TEXT NOT NULL PRIMARY KEY,
|
|
1869
|
-
canonical_entity_id TEXT NOT NULL
|
|
1870
|
-
);
|
|
1871
|
-
CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
|
|
1872
|
-
`);
|
|
1873
1354
|
for (const col of [
|
|
1874
1355
|
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
1875
1356
|
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
@@ -1879,396 +1360,1353 @@ async function ensureSchema() {
|
|
|
1879
1360
|
} catch {
|
|
1880
1361
|
}
|
|
1881
1362
|
}
|
|
1363
|
+
}
|
|
1364
|
+
async function getReadyShardClient(projectName) {
|
|
1365
|
+
const client = getShardClient(projectName);
|
|
1366
|
+
await ensureShardSchema(client);
|
|
1367
|
+
return client;
|
|
1368
|
+
}
|
|
1369
|
+
function disposeShards() {
|
|
1370
|
+
for (const [, client] of _shards) {
|
|
1371
|
+
client.close();
|
|
1372
|
+
}
|
|
1373
|
+
_shards.clear();
|
|
1374
|
+
_shardingEnabled = false;
|
|
1375
|
+
_encryptionKey = null;
|
|
1376
|
+
}
|
|
1377
|
+
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
1378
|
+
var init_shard_manager = __esm({
|
|
1379
|
+
"src/lib/shard-manager.ts"() {
|
|
1380
|
+
"use strict";
|
|
1381
|
+
init_config();
|
|
1382
|
+
SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
|
|
1383
|
+
_shards = /* @__PURE__ */ new Map();
|
|
1384
|
+
_encryptionKey = null;
|
|
1385
|
+
_shardingEnabled = false;
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
// src/lib/store.ts
|
|
1390
|
+
var store_exports = {};
|
|
1391
|
+
__export(store_exports, {
|
|
1392
|
+
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
1393
|
+
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
1394
|
+
classifyTier: () => classifyTier,
|
|
1395
|
+
disposeStore: () => disposeStore,
|
|
1396
|
+
flushBatch: () => flushBatch,
|
|
1397
|
+
flushTier3: () => flushTier3,
|
|
1398
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
1399
|
+
initStore: () => initStore,
|
|
1400
|
+
reserveVersions: () => reserveVersions,
|
|
1401
|
+
searchMemories: () => searchMemories,
|
|
1402
|
+
updateMemoryStatus: () => updateMemoryStatus,
|
|
1403
|
+
vectorToBlob: () => vectorToBlob,
|
|
1404
|
+
writeMemory: () => writeMemory
|
|
1405
|
+
});
|
|
1406
|
+
async function initStore(options) {
|
|
1407
|
+
if (_flushTimer !== null) {
|
|
1408
|
+
clearInterval(_flushTimer);
|
|
1409
|
+
_flushTimer = null;
|
|
1410
|
+
}
|
|
1411
|
+
_pendingRecords = [];
|
|
1412
|
+
_flushing = false;
|
|
1413
|
+
_batchSize = options?.batchSize ?? 20;
|
|
1414
|
+
_flushIntervalMs = options?.flushIntervalMs ?? 1e4;
|
|
1415
|
+
let dbPath = options?.dbPath;
|
|
1416
|
+
if (!dbPath) {
|
|
1417
|
+
const config = await loadConfig();
|
|
1418
|
+
dbPath = config.dbPath;
|
|
1419
|
+
}
|
|
1420
|
+
let masterKey = options?.masterKey ?? null;
|
|
1421
|
+
if (!masterKey) {
|
|
1422
|
+
masterKey = await getMasterKey();
|
|
1423
|
+
if (!masterKey) {
|
|
1424
|
+
throw new Error(
|
|
1425
|
+
"No encryption key found. Run /exe-setup to generate one."
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
const hexKey = masterKey.toString("hex");
|
|
1430
|
+
await initTurso({
|
|
1431
|
+
dbPath,
|
|
1432
|
+
encryptionKey: hexKey
|
|
1433
|
+
});
|
|
1434
|
+
await ensureSchema();
|
|
1882
1435
|
try {
|
|
1883
|
-
await
|
|
1884
|
-
|
|
1885
|
-
);
|
|
1436
|
+
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1437
|
+
initShardManager2(hexKey);
|
|
1886
1438
|
} catch {
|
|
1887
1439
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1902
|
-
session_id TEXT NOT NULL,
|
|
1903
|
-
role TEXT NOT NULL,
|
|
1904
|
-
content TEXT NOT NULL,
|
|
1905
|
-
tool_name TEXT,
|
|
1906
|
-
tool_id TEXT,
|
|
1907
|
-
is_error INTEGER NOT NULL DEFAULT 0,
|
|
1908
|
-
timestamp INTEGER NOT NULL
|
|
1909
|
-
);
|
|
1910
|
-
|
|
1911
|
-
CREATE INDEX IF NOT EXISTS idx_chat_history_session
|
|
1912
|
-
ON chat_history(session_id, id);
|
|
1913
|
-
`);
|
|
1914
|
-
await client.executeMultiple(`
|
|
1915
|
-
CREATE TABLE IF NOT EXISTS workspaces (
|
|
1916
|
-
id TEXT PRIMARY KEY,
|
|
1917
|
-
slug TEXT NOT NULL UNIQUE,
|
|
1918
|
-
name TEXT NOT NULL,
|
|
1919
|
-
owner_agent_id TEXT,
|
|
1920
|
-
created_at TEXT NOT NULL,
|
|
1921
|
-
metadata TEXT
|
|
1922
|
-
);
|
|
1923
|
-
|
|
1924
|
-
CREATE INDEX IF NOT EXISTS idx_workspaces_slug
|
|
1925
|
-
ON workspaces(slug);
|
|
1926
|
-
`);
|
|
1927
|
-
await client.executeMultiple(`
|
|
1928
|
-
CREATE TABLE IF NOT EXISTS documents (
|
|
1929
|
-
id TEXT PRIMARY KEY,
|
|
1930
|
-
workspace_id TEXT NOT NULL,
|
|
1931
|
-
filename TEXT NOT NULL,
|
|
1932
|
-
mime TEXT,
|
|
1933
|
-
source_type TEXT,
|
|
1934
|
-
user_id TEXT,
|
|
1935
|
-
uploaded_at TEXT NOT NULL,
|
|
1936
|
-
metadata TEXT,
|
|
1937
|
-
FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
|
|
1440
|
+
const client = getClient();
|
|
1441
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1442
|
+
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1443
|
+
}
|
|
1444
|
+
function classifyTier(record) {
|
|
1445
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
1446
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
1447
|
+
return 3;
|
|
1448
|
+
}
|
|
1449
|
+
async function writeMemory(record) {
|
|
1450
|
+
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
1451
|
+
throw new Error(
|
|
1452
|
+
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
1938
1453
|
);
|
|
1939
|
-
|
|
1940
|
-
CREATE INDEX IF NOT EXISTS idx_documents_workspace
|
|
1941
|
-
ON documents(workspace_id);
|
|
1942
|
-
|
|
1943
|
-
CREATE INDEX IF NOT EXISTS idx_documents_user
|
|
1944
|
-
ON documents(user_id);
|
|
1945
|
-
`);
|
|
1946
|
-
for (const column of [
|
|
1947
|
-
"workspace_id TEXT",
|
|
1948
|
-
"document_id TEXT",
|
|
1949
|
-
"user_id TEXT",
|
|
1950
|
-
"char_offset INTEGER",
|
|
1951
|
-
"page_number INTEGER"
|
|
1952
|
-
]) {
|
|
1953
|
-
try {
|
|
1954
|
-
await client.execute({
|
|
1955
|
-
sql: `ALTER TABLE memories ADD COLUMN ${column}`,
|
|
1956
|
-
args: []
|
|
1957
|
-
});
|
|
1958
|
-
} catch {
|
|
1959
|
-
}
|
|
1960
1454
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1455
|
+
const dbRow = {
|
|
1456
|
+
id: record.id,
|
|
1457
|
+
agent_id: record.agent_id,
|
|
1458
|
+
agent_role: record.agent_role,
|
|
1459
|
+
session_id: record.session_id,
|
|
1460
|
+
timestamp: record.timestamp,
|
|
1461
|
+
tool_name: record.tool_name,
|
|
1462
|
+
project_name: record.project_name,
|
|
1463
|
+
has_error: record.has_error ? 1 : 0,
|
|
1464
|
+
raw_text: record.raw_text,
|
|
1465
|
+
vector: record.vector,
|
|
1466
|
+
version: _nextVersion++,
|
|
1467
|
+
task_id: record.task_id ?? null,
|
|
1468
|
+
importance: record.importance ?? 5,
|
|
1469
|
+
status: record.status ?? "active",
|
|
1470
|
+
confidence: record.confidence ?? 0.7,
|
|
1471
|
+
last_accessed: record.last_accessed ?? record.timestamp,
|
|
1472
|
+
workspace_id: record.workspace_id ?? null,
|
|
1473
|
+
document_id: record.document_id ?? null,
|
|
1474
|
+
user_id: record.user_id ?? null,
|
|
1475
|
+
char_offset: record.char_offset ?? null,
|
|
1476
|
+
page_number: record.page_number ?? null,
|
|
1477
|
+
source_path: record.source_path ?? null,
|
|
1478
|
+
source_type: record.source_type ?? null,
|
|
1479
|
+
tier: record.tier ?? classifyTier(record),
|
|
1480
|
+
supersedes_id: record.supersedes_id ?? null
|
|
1481
|
+
};
|
|
1482
|
+
_pendingRecords.push(dbRow);
|
|
1483
|
+
if (_flushTimer === null) {
|
|
1484
|
+
_flushTimer = setInterval(() => {
|
|
1485
|
+
void flushBatch();
|
|
1486
|
+
}, _flushIntervalMs);
|
|
1487
|
+
if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
|
|
1488
|
+
_flushTimer.unref();
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (_pendingRecords.length >= _batchSize) {
|
|
1492
|
+
await flushBatch();
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function flushBatch() {
|
|
1496
|
+
if (_flushing || _pendingRecords.length === 0) return 0;
|
|
1497
|
+
_flushing = true;
|
|
1498
|
+
try {
|
|
1499
|
+
const batch = _pendingRecords.slice(0);
|
|
1500
|
+
const buildStmt = (row) => {
|
|
1501
|
+
const hasVector = row.vector !== null;
|
|
1502
|
+
const taskId = row.task_id ?? null;
|
|
1503
|
+
const importance = row.importance ?? 5;
|
|
1504
|
+
const status = row.status ?? "active";
|
|
1505
|
+
const confidence = row.confidence ?? 0.7;
|
|
1506
|
+
const lastAccessed = row.last_accessed ?? row.timestamp;
|
|
1507
|
+
const workspaceId = row.workspace_id ?? null;
|
|
1508
|
+
const documentId = row.document_id ?? null;
|
|
1509
|
+
const userId = row.user_id ?? null;
|
|
1510
|
+
const charOffset = row.char_offset ?? null;
|
|
1511
|
+
const pageNumber = row.page_number ?? null;
|
|
1512
|
+
const sourcePath = row.source_path ?? null;
|
|
1513
|
+
const sourceType = row.source_type ?? null;
|
|
1514
|
+
const tier = row.tier ?? 3;
|
|
1515
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
1516
|
+
return {
|
|
1517
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
1518
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
1519
|
+
tool_name, project_name,
|
|
1520
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1521
|
+
confidence, last_accessed,
|
|
1522
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1523
|
+
source_path, source_type, tier, supersedes_id)
|
|
1524
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
1525
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
1526
|
+
tool_name, project_name,
|
|
1527
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1528
|
+
confidence, last_accessed,
|
|
1529
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1530
|
+
source_path, source_type, tier, supersedes_id)
|
|
1531
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1532
|
+
args: hasVector ? [
|
|
1533
|
+
row.id,
|
|
1534
|
+
row.agent_id,
|
|
1535
|
+
row.agent_role,
|
|
1536
|
+
row.session_id,
|
|
1537
|
+
row.timestamp,
|
|
1538
|
+
row.tool_name,
|
|
1539
|
+
row.project_name,
|
|
1540
|
+
row.has_error,
|
|
1541
|
+
row.raw_text,
|
|
1542
|
+
vectorToBlob(row.vector),
|
|
1543
|
+
row.version,
|
|
1544
|
+
taskId,
|
|
1545
|
+
importance,
|
|
1546
|
+
status,
|
|
1547
|
+
confidence,
|
|
1548
|
+
lastAccessed,
|
|
1549
|
+
workspaceId,
|
|
1550
|
+
documentId,
|
|
1551
|
+
userId,
|
|
1552
|
+
charOffset,
|
|
1553
|
+
pageNumber,
|
|
1554
|
+
sourcePath,
|
|
1555
|
+
sourceType,
|
|
1556
|
+
tier,
|
|
1557
|
+
supersedesId
|
|
1558
|
+
] : [
|
|
1559
|
+
row.id,
|
|
1560
|
+
row.agent_id,
|
|
1561
|
+
row.agent_role,
|
|
1562
|
+
row.session_id,
|
|
1563
|
+
row.timestamp,
|
|
1564
|
+
row.tool_name,
|
|
1565
|
+
row.project_name,
|
|
1566
|
+
row.has_error,
|
|
1567
|
+
row.raw_text,
|
|
1568
|
+
row.version,
|
|
1569
|
+
taskId,
|
|
1570
|
+
importance,
|
|
1571
|
+
status,
|
|
1572
|
+
confidence,
|
|
1573
|
+
lastAccessed,
|
|
1574
|
+
workspaceId,
|
|
1575
|
+
documentId,
|
|
1576
|
+
userId,
|
|
1577
|
+
charOffset,
|
|
1578
|
+
pageNumber,
|
|
1579
|
+
sourcePath,
|
|
1580
|
+
sourceType,
|
|
1581
|
+
tier,
|
|
1582
|
+
supersedesId
|
|
1583
|
+
]
|
|
1584
|
+
};
|
|
1585
|
+
};
|
|
1586
|
+
const globalClient = getClient();
|
|
1587
|
+
const globalStmts = batch.map(buildStmt);
|
|
1588
|
+
await globalClient.batch(globalStmts, "write");
|
|
1589
|
+
_pendingRecords.splice(0, batch.length);
|
|
1590
|
+
try {
|
|
1591
|
+
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1592
|
+
if (isShardingEnabled2()) {
|
|
1593
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
1594
|
+
for (const row of batch) {
|
|
1595
|
+
const proj = row.project_name || "unknown";
|
|
1596
|
+
if (!byProject.has(proj)) byProject.set(proj, []);
|
|
1597
|
+
byProject.get(proj).push(row);
|
|
1598
|
+
}
|
|
1599
|
+
for (const [project, rows] of byProject) {
|
|
1600
|
+
try {
|
|
1601
|
+
const shardClient = await getReadyShardClient2(project);
|
|
1602
|
+
const shardStmts = rows.map(buildStmt);
|
|
1603
|
+
await shardClient.batch(shardStmts, "write");
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
process.stderr.write(
|
|
1606
|
+
`[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
|
|
1607
|
+
`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
return batch.length;
|
|
1615
|
+
} finally {
|
|
1616
|
+
_flushing = false;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function buildWikiScopeFilter(options, columnPrefix) {
|
|
1620
|
+
const args = [];
|
|
1621
|
+
let clause = "";
|
|
1622
|
+
if (options?.workspaceId !== void 0) {
|
|
1623
|
+
clause += ` AND ${columnPrefix}workspace_id = ?`;
|
|
1624
|
+
args.push(options.workspaceId);
|
|
1625
|
+
}
|
|
1626
|
+
if (options?.userId === void 0) {
|
|
1627
|
+
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
1628
|
+
} else if (options.userId === null) {
|
|
1629
|
+
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
1630
|
+
} else {
|
|
1631
|
+
clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
|
|
1632
|
+
args.push(options.userId);
|
|
1633
|
+
}
|
|
1634
|
+
return { clause, args };
|
|
1635
|
+
}
|
|
1636
|
+
async function searchMemories(queryVector, agentId, options) {
|
|
1637
|
+
let client;
|
|
1638
|
+
try {
|
|
1639
|
+
const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1640
|
+
if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
|
|
1641
|
+
client = await getReadyShardClient2(options.projectName);
|
|
1642
|
+
} else {
|
|
1643
|
+
client = getClient();
|
|
1644
|
+
}
|
|
1645
|
+
} catch {
|
|
1646
|
+
client = getClient();
|
|
1647
|
+
}
|
|
1648
|
+
const limit = options?.limit ?? 10;
|
|
1649
|
+
const statusFilter = options?.includeArchived ? "" : `
|
|
1650
|
+
AND COALESCE(status, 'active') = 'active'`;
|
|
1651
|
+
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
1652
|
+
tool_name, project_name,
|
|
1653
|
+
has_error, raw_text, vector, importance, status,
|
|
1654
|
+
confidence, last_accessed,
|
|
1655
|
+
workspace_id, document_id, user_id,
|
|
1656
|
+
char_offset, page_number,
|
|
1657
|
+
source_path, source_type
|
|
1658
|
+
FROM memories
|
|
1659
|
+
WHERE agent_id = ?
|
|
1660
|
+
AND vector IS NOT NULL${statusFilter}
|
|
1661
|
+
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
1662
|
+
const args = [agentId];
|
|
1663
|
+
const scope = buildWikiScopeFilter(options, "");
|
|
1664
|
+
sql += scope.clause;
|
|
1665
|
+
args.push(...scope.args);
|
|
1666
|
+
if (options?.projectName) {
|
|
1667
|
+
sql += ` AND project_name = ?`;
|
|
1668
|
+
args.push(options.projectName);
|
|
1669
|
+
}
|
|
1670
|
+
if (options?.toolName) {
|
|
1671
|
+
sql += ` AND tool_name = ?`;
|
|
1672
|
+
args.push(options.toolName);
|
|
1673
|
+
}
|
|
1674
|
+
if (options?.hasError !== void 0) {
|
|
1675
|
+
sql += ` AND has_error = ?`;
|
|
1676
|
+
args.push(options.hasError ? 1 : 0);
|
|
1677
|
+
}
|
|
1678
|
+
if (options?.since) {
|
|
1679
|
+
sql += ` AND timestamp >= ?`;
|
|
1680
|
+
args.push(options.since);
|
|
1681
|
+
}
|
|
1682
|
+
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
1683
|
+
args.push(vectorToBlob(queryVector));
|
|
1684
|
+
sql += ` LIMIT ?`;
|
|
1685
|
+
args.push(limit);
|
|
1686
|
+
const result = await client.execute({ sql, args });
|
|
1687
|
+
return result.rows.map((row) => ({
|
|
1688
|
+
id: row.id,
|
|
1689
|
+
agent_id: row.agent_id,
|
|
1690
|
+
agent_role: row.agent_role,
|
|
1691
|
+
session_id: row.session_id,
|
|
1692
|
+
timestamp: row.timestamp,
|
|
1693
|
+
tool_name: row.tool_name,
|
|
1694
|
+
project_name: row.project_name,
|
|
1695
|
+
has_error: row.has_error === 1,
|
|
1696
|
+
raw_text: row.raw_text,
|
|
1697
|
+
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
1698
|
+
importance: row.importance ?? 5,
|
|
1699
|
+
status: row.status ?? "active",
|
|
1700
|
+
confidence: row.confidence ?? 0.7,
|
|
1701
|
+
last_accessed: row.last_accessed ?? row.timestamp,
|
|
1702
|
+
workspace_id: row.workspace_id ?? null,
|
|
1703
|
+
document_id: row.document_id ?? null,
|
|
1704
|
+
user_id: row.user_id ?? null,
|
|
1705
|
+
char_offset: row.char_offset ?? null,
|
|
1706
|
+
page_number: row.page_number ?? null,
|
|
1707
|
+
source_path: row.source_path ?? null,
|
|
1708
|
+
source_type: row.source_type ?? null
|
|
1709
|
+
}));
|
|
1710
|
+
}
|
|
1711
|
+
async function attachDocumentMetadata(records) {
|
|
1712
|
+
const docIds = [
|
|
1713
|
+
...new Set(
|
|
1714
|
+
records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
|
|
1715
|
+
)
|
|
1716
|
+
];
|
|
1717
|
+
if (docIds.length === 0) return records;
|
|
1718
|
+
try {
|
|
1719
|
+
const client = getClient();
|
|
1720
|
+
const placeholders = docIds.map(() => "?").join(",");
|
|
1721
|
+
const result = await client.execute({
|
|
1722
|
+
sql: `SELECT id, filename, mime, source_type, uploaded_at
|
|
1723
|
+
FROM documents
|
|
1724
|
+
WHERE id IN (${placeholders})`,
|
|
1725
|
+
args: docIds
|
|
1726
|
+
});
|
|
1727
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1728
|
+
for (const row of result.rows) {
|
|
1729
|
+
const id = row.id;
|
|
1730
|
+
byId.set(id, {
|
|
1731
|
+
document_id: id,
|
|
1732
|
+
filename: row.filename,
|
|
1733
|
+
mime: row.mime ?? null,
|
|
1734
|
+
source_type: row.source_type ?? null,
|
|
1735
|
+
uploaded_at: row.uploaded_at
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
for (const record of records) {
|
|
1739
|
+
if (!record.document_id) continue;
|
|
1740
|
+
record.document_metadata = byId.get(record.document_id) ?? null;
|
|
1741
|
+
}
|
|
1742
|
+
} catch {
|
|
1743
|
+
}
|
|
1744
|
+
return records;
|
|
1745
|
+
}
|
|
1746
|
+
async function flushTier3(agentId, options) {
|
|
1747
|
+
const client = getClient();
|
|
1748
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
1749
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
1750
|
+
if (options?.dryRun) {
|
|
1751
|
+
const result2 = await client.execute({
|
|
1752
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
1753
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1754
|
+
args: [agentId, cutoff]
|
|
1755
|
+
});
|
|
1756
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
1757
|
+
}
|
|
1758
|
+
const result = await client.execute({
|
|
1759
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
1760
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1761
|
+
args: [agentId, cutoff]
|
|
1762
|
+
});
|
|
1763
|
+
return { archived: result.rowsAffected };
|
|
1764
|
+
}
|
|
1765
|
+
async function disposeStore() {
|
|
1766
|
+
if (_flushTimer !== null) {
|
|
1767
|
+
clearInterval(_flushTimer);
|
|
1768
|
+
_flushTimer = null;
|
|
1769
|
+
}
|
|
1770
|
+
if (_pendingRecords.length > 0) {
|
|
1771
|
+
await flushBatch();
|
|
1772
|
+
}
|
|
1773
|
+
await disposeTurso();
|
|
1774
|
+
_pendingRecords = [];
|
|
1775
|
+
_nextVersion = 1;
|
|
1776
|
+
}
|
|
1777
|
+
function vectorToBlob(vector) {
|
|
1778
|
+
const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
|
|
1779
|
+
return JSON.stringify(Array.from(f32));
|
|
1780
|
+
}
|
|
1781
|
+
async function updateMemoryStatus(id, status) {
|
|
1782
|
+
const client = getClient();
|
|
1783
|
+
await client.execute({
|
|
1784
|
+
sql: `UPDATE memories SET status = ? WHERE id = ?`,
|
|
1785
|
+
args: [status, id]
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
function reserveVersions(count) {
|
|
1789
|
+
const reserved = [];
|
|
1790
|
+
for (let i = 0; i < count; i++) {
|
|
1791
|
+
reserved.push(_nextVersion++);
|
|
1792
|
+
}
|
|
1793
|
+
return reserved;
|
|
1794
|
+
}
|
|
1795
|
+
async function getMemoryCardinality(agentId) {
|
|
1796
|
+
try {
|
|
1797
|
+
const client = getClient();
|
|
1798
|
+
const result = await client.execute({
|
|
1799
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
1800
|
+
args: [agentId]
|
|
1801
|
+
});
|
|
1802
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
1803
|
+
} catch {
|
|
1804
|
+
return 0;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
1808
|
+
var init_store = __esm({
|
|
1809
|
+
"src/lib/store.ts"() {
|
|
1810
|
+
"use strict";
|
|
1811
|
+
init_memory();
|
|
1812
|
+
init_database();
|
|
1813
|
+
init_keychain();
|
|
1814
|
+
init_config();
|
|
1815
|
+
_pendingRecords = [];
|
|
1816
|
+
_batchSize = 20;
|
|
1817
|
+
_flushIntervalMs = 1e4;
|
|
1818
|
+
_flushTimer = null;
|
|
1819
|
+
_flushing = false;
|
|
1820
|
+
_nextVersion = 1;
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1964
1823
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1824
|
+
// src/lib/self-query-router.ts
|
|
1825
|
+
var self_query_router_exports = {};
|
|
1826
|
+
__export(self_query_router_exports, {
|
|
1827
|
+
routeQuery: () => routeQuery
|
|
1828
|
+
});
|
|
1829
|
+
async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
1830
|
+
if (query.length < 10) {
|
|
1831
|
+
return {
|
|
1832
|
+
semanticQuery: query,
|
|
1833
|
+
projectFilter: null,
|
|
1834
|
+
roleFilter: null,
|
|
1835
|
+
timeFilter: null,
|
|
1836
|
+
isBroadQuery: false
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
try {
|
|
1840
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
1841
|
+
const client = new Anthropic();
|
|
1842
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1843
|
+
const response = await client.messages.create({
|
|
1844
|
+
model,
|
|
1845
|
+
max_tokens: 256,
|
|
1846
|
+
system: `You are a search query router. Extract metadata filters from the user's memory search query. Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
|
|
1847
|
+
messages: [{ role: "user", content: query }],
|
|
1848
|
+
tools: [EXTRACT_TOOL],
|
|
1849
|
+
tool_choice: { type: "tool", name: "extract_search_filters" }
|
|
1850
|
+
});
|
|
1851
|
+
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
1852
|
+
if (toolBlock && toolBlock.type === "tool_use") {
|
|
1853
|
+
const input2 = toolBlock.input;
|
|
1854
|
+
return {
|
|
1855
|
+
semanticQuery: input2.semantic_query || query,
|
|
1856
|
+
projectFilter: input2.project_filter || null,
|
|
1857
|
+
roleFilter: input2.role_filter || null,
|
|
1858
|
+
timeFilter: input2.time_filter || null,
|
|
1859
|
+
isBroadQuery: Boolean(input2.is_broad_query)
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
process.stderr.write(
|
|
1864
|
+
`[self-query-router] LLM extraction failed, using passthrough: ${err instanceof Error ? err.message : String(err)}
|
|
1865
|
+
`
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
return {
|
|
1869
|
+
semanticQuery: query,
|
|
1870
|
+
projectFilter: null,
|
|
1871
|
+
roleFilter: null,
|
|
1872
|
+
timeFilter: null,
|
|
1873
|
+
isBroadQuery: false
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
var EXTRACT_TOOL;
|
|
1877
|
+
var init_self_query_router = __esm({
|
|
1878
|
+
"src/lib/self-query-router.ts"() {
|
|
1879
|
+
"use strict";
|
|
1880
|
+
EXTRACT_TOOL = {
|
|
1881
|
+
name: "extract_search_filters",
|
|
1882
|
+
description: "Extract metadata filters from a memory search query to improve retrieval precision.",
|
|
1883
|
+
input_schema: {
|
|
1884
|
+
type: "object",
|
|
1885
|
+
properties: {
|
|
1886
|
+
semantic_query: {
|
|
1887
|
+
type: "string",
|
|
1888
|
+
description: "The core semantic meaning of the query, stripped of metadata references. This is used for embedding search."
|
|
1889
|
+
},
|
|
1890
|
+
project_filter: {
|
|
1891
|
+
type: ["string", "null"],
|
|
1892
|
+
description: "Project name if the query references a specific project (e.g., 'exe-os', 'exe-create'). Null if no project specified."
|
|
1893
|
+
},
|
|
1894
|
+
role_filter: {
|
|
1895
|
+
type: ["string", "null"],
|
|
1896
|
+
description: "Agent role if the query targets a specific role (e.g., 'CTO', 'CMO'). Null if no role specified."
|
|
1897
|
+
},
|
|
1898
|
+
time_filter: {
|
|
1899
|
+
type: ["string", "null"],
|
|
1900
|
+
description: "ISO 8601 timestamp lower bound if the query references a time period (e.g., 'last week', 'yesterday'). Null if no time reference."
|
|
1901
|
+
},
|
|
1902
|
+
is_broad_query: {
|
|
1903
|
+
type: "boolean",
|
|
1904
|
+
description: "True if the query is exploratory/broad (e.g., 'what has yoshi been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
|
|
1905
|
+
}
|
|
1906
|
+
},
|
|
1907
|
+
required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
// src/lib/exe-daemon-client.ts
|
|
1914
|
+
import net from "net";
|
|
1915
|
+
import { spawn } from "child_process";
|
|
1916
|
+
import { randomUUID } from "crypto";
|
|
1917
|
+
import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
|
|
1918
|
+
import path4 from "path";
|
|
1919
|
+
import { fileURLToPath } from "url";
|
|
1920
|
+
function handleData(chunk) {
|
|
1921
|
+
_buffer += chunk.toString();
|
|
1922
|
+
let newlineIdx;
|
|
1923
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1924
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
1925
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
1926
|
+
if (!line) continue;
|
|
1927
|
+
try {
|
|
1928
|
+
const response = JSON.parse(line);
|
|
1929
|
+
const entry = _pending.get(response.id);
|
|
1930
|
+
if (entry) {
|
|
1931
|
+
clearTimeout(entry.timer);
|
|
1932
|
+
_pending.delete(response.id);
|
|
1933
|
+
entry.resolve(response);
|
|
1934
|
+
}
|
|
1935
|
+
} catch {
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function cleanupStaleFiles() {
|
|
1940
|
+
if (existsSync4(PID_PATH)) {
|
|
1941
|
+
try {
|
|
1942
|
+
const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
|
|
1943
|
+
if (pid > 0) {
|
|
1944
|
+
try {
|
|
1945
|
+
process.kill(pid, 0);
|
|
1946
|
+
return;
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
} catch {
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
unlinkSync(PID_PATH);
|
|
1954
|
+
} catch {
|
|
1955
|
+
}
|
|
1956
|
+
try {
|
|
1957
|
+
unlinkSync(SOCKET_PATH);
|
|
1958
|
+
} catch {
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function findPackageRoot() {
|
|
1963
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
1964
|
+
const { root } = path4.parse(dir);
|
|
1965
|
+
while (dir !== root) {
|
|
1966
|
+
if (existsSync4(path4.join(dir, "package.json"))) return dir;
|
|
1967
|
+
dir = path4.dirname(dir);
|
|
1968
|
+
}
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
function spawnDaemon() {
|
|
1972
|
+
const pkgRoot = findPackageRoot();
|
|
1973
|
+
if (!pkgRoot) {
|
|
1974
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1978
|
+
if (!existsSync4(daemonPath)) {
|
|
1979
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1980
|
+
`);
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
const resolvedPath = daemonPath;
|
|
1984
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1985
|
+
`);
|
|
1986
|
+
const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
|
|
1987
|
+
let stderrFd = "ignore";
|
|
1988
|
+
try {
|
|
1989
|
+
stderrFd = openSync(logPath, "a");
|
|
1990
|
+
} catch {
|
|
1991
|
+
}
|
|
1992
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
1993
|
+
detached: true,
|
|
1994
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
1995
|
+
env: {
|
|
1996
|
+
...process.env,
|
|
1997
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1998
|
+
EXE_DAEMON_PID: PID_PATH
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
child.unref();
|
|
2002
|
+
if (typeof stderrFd === "number") {
|
|
2003
|
+
try {
|
|
2004
|
+
closeSync(stderrFd);
|
|
2005
|
+
} catch {
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
function acquireSpawnLock() {
|
|
2010
|
+
try {
|
|
2011
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
2012
|
+
closeSync(fd);
|
|
2013
|
+
return true;
|
|
2014
|
+
} catch {
|
|
2015
|
+
try {
|
|
2016
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
2017
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
2018
|
+
try {
|
|
2019
|
+
unlinkSync(SPAWN_LOCK_PATH);
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
2022
|
+
try {
|
|
2023
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
2024
|
+
closeSync(fd);
|
|
2025
|
+
return true;
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
} catch {
|
|
2030
|
+
}
|
|
2031
|
+
return false;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
function releaseSpawnLock() {
|
|
2035
|
+
try {
|
|
2036
|
+
unlinkSync(SPAWN_LOCK_PATH);
|
|
2037
|
+
} catch {
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
function connectToSocket() {
|
|
2041
|
+
return new Promise((resolve) => {
|
|
2042
|
+
if (_socket && _connected) {
|
|
2043
|
+
resolve(true);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
2047
|
+
const connectTimeout = setTimeout(() => {
|
|
2048
|
+
socket.destroy();
|
|
2049
|
+
resolve(false);
|
|
2050
|
+
}, 2e3);
|
|
2051
|
+
socket.on("connect", () => {
|
|
2052
|
+
clearTimeout(connectTimeout);
|
|
2053
|
+
_socket = socket;
|
|
2054
|
+
_connected = true;
|
|
2055
|
+
_buffer = "";
|
|
2056
|
+
socket.on("data", handleData);
|
|
2057
|
+
socket.on("close", () => {
|
|
2058
|
+
_connected = false;
|
|
2059
|
+
_socket = null;
|
|
2060
|
+
for (const [id, entry] of _pending) {
|
|
2061
|
+
clearTimeout(entry.timer);
|
|
2062
|
+
_pending.delete(id);
|
|
2063
|
+
entry.resolve({ error: "Connection closed" });
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
socket.on("error", () => {
|
|
2067
|
+
_connected = false;
|
|
2068
|
+
_socket = null;
|
|
2069
|
+
});
|
|
2070
|
+
resolve(true);
|
|
2071
|
+
});
|
|
2072
|
+
socket.on("error", () => {
|
|
2073
|
+
clearTimeout(connectTimeout);
|
|
2074
|
+
resolve(false);
|
|
2075
|
+
});
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
async function connectEmbedDaemon() {
|
|
2079
|
+
if (_socket && _connected) return true;
|
|
2080
|
+
if (await connectToSocket()) return true;
|
|
2081
|
+
if (acquireSpawnLock()) {
|
|
2082
|
+
try {
|
|
2083
|
+
cleanupStaleFiles();
|
|
2084
|
+
spawnDaemon();
|
|
2085
|
+
} finally {
|
|
2086
|
+
releaseSpawnLock();
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
const start = Date.now();
|
|
2090
|
+
let delay = 100;
|
|
2091
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2092
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2093
|
+
if (await connectToSocket()) return true;
|
|
2094
|
+
delay = Math.min(delay * 2, 3e3);
|
|
2095
|
+
}
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
function sendRequest(texts, priority) {
|
|
2099
|
+
return new Promise((resolve) => {
|
|
2100
|
+
if (!_socket || !_connected) {
|
|
2101
|
+
resolve({ error: "Not connected" });
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
const id = randomUUID();
|
|
2105
|
+
const timer = setTimeout(() => {
|
|
2106
|
+
_pending.delete(id);
|
|
2107
|
+
resolve({ error: "Request timeout" });
|
|
2108
|
+
}, REQUEST_TIMEOUT_MS);
|
|
2109
|
+
_pending.set(id, { resolve, timer });
|
|
2110
|
+
try {
|
|
2111
|
+
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
2112
|
+
} catch {
|
|
2113
|
+
clearTimeout(timer);
|
|
2114
|
+
_pending.delete(id);
|
|
2115
|
+
resolve({ error: "Write failed" });
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
async function pingDaemon() {
|
|
2120
|
+
if (!_socket || !_connected) return null;
|
|
2121
|
+
return new Promise((resolve) => {
|
|
2122
|
+
const id = randomUUID();
|
|
2123
|
+
const timer = setTimeout(() => {
|
|
2124
|
+
_pending.delete(id);
|
|
2125
|
+
resolve(null);
|
|
2126
|
+
}, 5e3);
|
|
2127
|
+
_pending.set(id, {
|
|
2128
|
+
resolve: (data) => {
|
|
2129
|
+
if (data.health) {
|
|
2130
|
+
resolve(data.health);
|
|
2131
|
+
} else {
|
|
2132
|
+
resolve(null);
|
|
2133
|
+
}
|
|
2134
|
+
},
|
|
2135
|
+
timer
|
|
2136
|
+
});
|
|
2137
|
+
try {
|
|
2138
|
+
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
2139
|
+
} catch {
|
|
2140
|
+
clearTimeout(timer);
|
|
2141
|
+
_pending.delete(id);
|
|
2142
|
+
resolve(null);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
function killAndRespawnDaemon() {
|
|
2147
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2148
|
+
if (existsSync4(PID_PATH)) {
|
|
2149
|
+
try {
|
|
2150
|
+
const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
|
|
2151
|
+
if (pid > 0) {
|
|
2152
|
+
try {
|
|
2153
|
+
process.kill(pid, "SIGKILL");
|
|
2154
|
+
} catch {
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
} catch {
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
if (_socket) {
|
|
2161
|
+
_socket.destroy();
|
|
2162
|
+
_socket = null;
|
|
2163
|
+
}
|
|
2164
|
+
_connected = false;
|
|
2165
|
+
_buffer = "";
|
|
2166
|
+
try {
|
|
2167
|
+
unlinkSync(PID_PATH);
|
|
2168
|
+
} catch {
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
unlinkSync(SOCKET_PATH);
|
|
2172
|
+
} catch {
|
|
2173
|
+
}
|
|
2174
|
+
spawnDaemon();
|
|
2175
|
+
}
|
|
2176
|
+
async function embedViaClient(text, priority = "high") {
|
|
2177
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
2178
|
+
_requestCount++;
|
|
2179
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
2180
|
+
const health = await pingDaemon();
|
|
2181
|
+
if (!health) {
|
|
2182
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
2183
|
+
`);
|
|
2184
|
+
killAndRespawnDaemon();
|
|
2185
|
+
const start = Date.now();
|
|
2186
|
+
let delay = 200;
|
|
2187
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2188
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2189
|
+
if (await connectToSocket()) break;
|
|
2190
|
+
delay = Math.min(delay * 2, 3e3);
|
|
2191
|
+
}
|
|
2192
|
+
if (!_connected) return null;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const result = await sendRequest([text], priority);
|
|
2196
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
2197
|
+
if (result.error) {
|
|
2198
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
2199
|
+
`);
|
|
2200
|
+
killAndRespawnDaemon();
|
|
2201
|
+
const start = Date.now();
|
|
2202
|
+
let delay = 200;
|
|
2203
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2204
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2205
|
+
if (await connectToSocket()) break;
|
|
2206
|
+
delay = Math.min(delay * 2, 3e3);
|
|
2207
|
+
}
|
|
2208
|
+
if (!_connected) return null;
|
|
2209
|
+
const retry = await sendRequest([text], priority);
|
|
2210
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2211
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2212
|
+
`);
|
|
2213
|
+
}
|
|
2214
|
+
return null;
|
|
2215
|
+
}
|
|
2216
|
+
function disconnectClient() {
|
|
2217
|
+
if (_socket) {
|
|
2218
|
+
_socket.destroy();
|
|
2219
|
+
_socket = null;
|
|
2220
|
+
}
|
|
2221
|
+
_connected = false;
|
|
2222
|
+
_buffer = "";
|
|
2223
|
+
for (const [id, entry] of _pending) {
|
|
2224
|
+
clearTimeout(entry.timer);
|
|
2225
|
+
_pending.delete(id);
|
|
2226
|
+
entry.resolve({ error: "Client disconnected" });
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
|
|
2230
|
+
var init_exe_daemon_client = __esm({
|
|
2231
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
2232
|
+
"use strict";
|
|
2233
|
+
init_config();
|
|
2234
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
|
|
2235
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
|
|
2236
|
+
SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2237
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2238
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
2239
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
2240
|
+
_socket = null;
|
|
2241
|
+
_connected = false;
|
|
2242
|
+
_buffer = "";
|
|
2243
|
+
_requestCount = 0;
|
|
2244
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
2245
|
+
_pending = /* @__PURE__ */ new Map();
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
1967
2248
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2249
|
+
// src/lib/embedder.ts
|
|
2250
|
+
var embedder_exports = {};
|
|
2251
|
+
__export(embedder_exports, {
|
|
2252
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
2253
|
+
embed: () => embed,
|
|
2254
|
+
embedDirect: () => embedDirect,
|
|
2255
|
+
getEmbedder: () => getEmbedder
|
|
2256
|
+
});
|
|
2257
|
+
async function getEmbedder() {
|
|
2258
|
+
const ok = await connectEmbedDaemon();
|
|
2259
|
+
if (!ok) {
|
|
2260
|
+
throw new Error(
|
|
2261
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
1980
2262
|
);
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
1990
|
-
id TEXT PRIMARY KEY,
|
|
1991
|
-
platform TEXT NOT NULL,
|
|
1992
|
-
external_id TEXT,
|
|
1993
|
-
sender_id TEXT NOT NULL,
|
|
1994
|
-
sender_name TEXT,
|
|
1995
|
-
sender_phone TEXT,
|
|
1996
|
-
sender_email TEXT,
|
|
1997
|
-
recipient_id TEXT,
|
|
1998
|
-
channel_id TEXT NOT NULL,
|
|
1999
|
-
thread_id TEXT,
|
|
2000
|
-
reply_to_id TEXT,
|
|
2001
|
-
content_text TEXT,
|
|
2002
|
-
content_media TEXT,
|
|
2003
|
-
content_metadata TEXT,
|
|
2004
|
-
agent_response TEXT,
|
|
2005
|
-
agent_name TEXT,
|
|
2006
|
-
timestamp TEXT NOT NULL,
|
|
2007
|
-
ingested_at TEXT NOT NULL
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
async function embed(text) {
|
|
2266
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
2267
|
+
const vector = await embedViaClient(text, priority);
|
|
2268
|
+
if (!vector) {
|
|
2269
|
+
throw new Error(
|
|
2270
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
2008
2271
|
);
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
CREATE INDEX IF NOT EXISTS idx_conversations_sender
|
|
2014
|
-
ON conversations(sender_id);
|
|
2015
|
-
|
|
2016
|
-
CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
|
|
2017
|
-
ON conversations(timestamp);
|
|
2018
|
-
|
|
2019
|
-
CREATE INDEX IF NOT EXISTS idx_conversations_thread
|
|
2020
|
-
ON conversations(thread_id);
|
|
2021
|
-
|
|
2022
|
-
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
2023
|
-
ON conversations(channel_id);
|
|
2024
|
-
`);
|
|
2025
|
-
await client.executeMultiple(`
|
|
2026
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
2027
|
-
content_text,
|
|
2028
|
-
sender_name,
|
|
2029
|
-
agent_response,
|
|
2030
|
-
content='conversations',
|
|
2031
|
-
content_rowid='rowid'
|
|
2272
|
+
}
|
|
2273
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
2032
2276
|
);
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
2036
|
-
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
2037
|
-
END;
|
|
2038
|
-
|
|
2039
|
-
CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
|
|
2040
|
-
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
2041
|
-
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
2042
|
-
END;
|
|
2043
|
-
|
|
2044
|
-
CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
|
|
2045
|
-
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
2046
|
-
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
2047
|
-
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
2048
|
-
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
2049
|
-
END;
|
|
2050
|
-
`);
|
|
2277
|
+
}
|
|
2278
|
+
return vector;
|
|
2051
2279
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
|
|
2055
|
-
import { existsSync as existsSync2 } from "fs";
|
|
2056
|
-
import path2 from "path";
|
|
2057
|
-
import crypto from "crypto";
|
|
2058
|
-
var SERVICE = "exe-mem";
|
|
2059
|
-
var ACCOUNT = "master-key";
|
|
2060
|
-
function getKeyDir() {
|
|
2061
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
|
|
2280
|
+
async function disposeEmbedder() {
|
|
2281
|
+
disconnectClient();
|
|
2062
2282
|
}
|
|
2063
|
-
function
|
|
2064
|
-
|
|
2283
|
+
async function embedDirect(text) {
|
|
2284
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
2285
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2286
|
+
const { existsSync: existsSync7 } = await import("fs");
|
|
2287
|
+
const path10 = await import("path");
|
|
2288
|
+
const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
2289
|
+
if (!existsSync7(modelPath)) {
|
|
2290
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2291
|
+
}
|
|
2292
|
+
const llama = await llamaCpp.getLlama();
|
|
2293
|
+
const model = await llama.loadModel({ modelPath });
|
|
2294
|
+
const context = await model.createEmbeddingContext();
|
|
2295
|
+
try {
|
|
2296
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
2297
|
+
const vector = Array.from(embedding.vector);
|
|
2298
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
2299
|
+
throw new Error(
|
|
2300
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
return vector;
|
|
2304
|
+
} finally {
|
|
2305
|
+
await context.dispose();
|
|
2306
|
+
await model.dispose();
|
|
2307
|
+
}
|
|
2065
2308
|
}
|
|
2066
|
-
|
|
2309
|
+
var init_embedder = __esm({
|
|
2310
|
+
"src/lib/embedder.ts"() {
|
|
2311
|
+
"use strict";
|
|
2312
|
+
init_memory();
|
|
2313
|
+
init_exe_daemon_client();
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
|
|
2317
|
+
// src/lib/project-name.ts
|
|
2318
|
+
var project_name_exports = {};
|
|
2319
|
+
__export(project_name_exports, {
|
|
2320
|
+
_resetCache: () => _resetCache,
|
|
2321
|
+
getProjectName: () => getProjectName
|
|
2322
|
+
});
|
|
2323
|
+
import { execSync } from "child_process";
|
|
2324
|
+
import path5 from "path";
|
|
2325
|
+
function getProjectName(cwd) {
|
|
2326
|
+
const dir = cwd ?? process.cwd();
|
|
2327
|
+
if (_cached && _cachedCwd === dir) return _cached;
|
|
2067
2328
|
try {
|
|
2068
|
-
|
|
2329
|
+
let repoRoot;
|
|
2330
|
+
try {
|
|
2331
|
+
const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2332
|
+
cwd: dir,
|
|
2333
|
+
encoding: "utf8",
|
|
2334
|
+
timeout: 2e3,
|
|
2335
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2336
|
+
}).trim();
|
|
2337
|
+
repoRoot = path5.dirname(gitCommonDir);
|
|
2338
|
+
} catch {
|
|
2339
|
+
repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
2340
|
+
cwd: dir,
|
|
2341
|
+
encoding: "utf8",
|
|
2342
|
+
timeout: 2e3,
|
|
2343
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2344
|
+
}).trim();
|
|
2345
|
+
}
|
|
2346
|
+
_cached = path5.basename(repoRoot);
|
|
2347
|
+
_cachedCwd = dir;
|
|
2348
|
+
return _cached;
|
|
2069
2349
|
} catch {
|
|
2070
|
-
|
|
2350
|
+
_cached = path5.basename(dir);
|
|
2351
|
+
_cachedCwd = dir;
|
|
2352
|
+
return _cached;
|
|
2071
2353
|
}
|
|
2072
2354
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2355
|
+
function _resetCache() {
|
|
2356
|
+
_cached = null;
|
|
2357
|
+
_cachedCwd = null;
|
|
2358
|
+
}
|
|
2359
|
+
var _cached, _cachedCwd;
|
|
2360
|
+
var init_project_name = __esm({
|
|
2361
|
+
"src/lib/project-name.ts"() {
|
|
2362
|
+
"use strict";
|
|
2363
|
+
_cached = null;
|
|
2364
|
+
_cachedCwd = null;
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
// src/lib/file-grep.ts
|
|
2369
|
+
var file_grep_exports = {};
|
|
2370
|
+
__export(file_grep_exports, {
|
|
2371
|
+
grepProjectFiles: () => grepProjectFiles
|
|
2372
|
+
});
|
|
2373
|
+
import { execSync as execSync2 } from "child_process";
|
|
2374
|
+
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
|
|
2375
|
+
import path6 from "path";
|
|
2376
|
+
import crypto2 from "crypto";
|
|
2377
|
+
function hasRipgrep() {
|
|
2378
|
+
if (_hasRg === null) {
|
|
2076
2379
|
try {
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
return Buffer.from(stored, "base64");
|
|
2080
|
-
}
|
|
2380
|
+
execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
|
|
2381
|
+
_hasRg = true;
|
|
2081
2382
|
} catch {
|
|
2383
|
+
_hasRg = false;
|
|
2082
2384
|
}
|
|
2083
2385
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2386
|
+
return _hasRg;
|
|
2387
|
+
}
|
|
2388
|
+
async function grepProjectFiles(query, projectRoot, options) {
|
|
2389
|
+
const maxResults = options?.maxResults ?? 10;
|
|
2390
|
+
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_-]/g, "")).filter((t) => t.length >= 3);
|
|
2391
|
+
if (terms.length === 0) return [];
|
|
2392
|
+
const pattern = terms.join("|");
|
|
2393
|
+
let hits;
|
|
2394
|
+
if (hasRipgrep()) {
|
|
2395
|
+
try {
|
|
2396
|
+
hits = grepWithRipgrep(pattern, projectRoot, options?.patterns);
|
|
2397
|
+
} catch {
|
|
2398
|
+
hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
|
|
2399
|
+
}
|
|
2400
|
+
} else {
|
|
2401
|
+
hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
|
|
2087
2402
|
}
|
|
2403
|
+
hits.sort((a, b) => b.density - a.density);
|
|
2404
|
+
const topHits = hits.slice(0, maxResults);
|
|
2405
|
+
return topHits.map((hit) => {
|
|
2406
|
+
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
2407
|
+
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
2408
|
+
return {
|
|
2409
|
+
id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
2410
|
+
agent_id: "project",
|
|
2411
|
+
agent_role: "file",
|
|
2412
|
+
session_id: "file-grep",
|
|
2413
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2414
|
+
tool_name: "file_grep",
|
|
2415
|
+
project_name: path6.basename(projectRoot),
|
|
2416
|
+
has_error: false,
|
|
2417
|
+
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
2418
|
+
vector: null,
|
|
2419
|
+
task_id: null
|
|
2420
|
+
};
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
function getChunkContext(filePath, lineNumber) {
|
|
2088
2424
|
try {
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2425
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
2426
|
+
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
2427
|
+
const source = readFileSync3(filePath, "utf8");
|
|
2428
|
+
const lines = source.split("\n");
|
|
2429
|
+
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
2430
|
+
const line = lines[i];
|
|
2431
|
+
const fnMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
2432
|
+
if (fnMatch) return `function ${fnMatch[1]}`;
|
|
2433
|
+
const classMatch = line.match(/(?:export\s+)?class\s+(\w+)/);
|
|
2434
|
+
if (classMatch) return `class ${classMatch[1]}`;
|
|
2435
|
+
const arrowMatch = line.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
2436
|
+
if (arrowMatch) return `function ${arrowMatch[1]}`;
|
|
2437
|
+
}
|
|
2091
2438
|
} catch {
|
|
2092
|
-
return null;
|
|
2093
2439
|
}
|
|
2440
|
+
return "";
|
|
2094
2441
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
if (
|
|
2106
|
-
|
|
2107
|
-
|
|
2442
|
+
function grepWithRipgrep(pattern, projectRoot, patterns) {
|
|
2443
|
+
const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
|
|
2444
|
+
const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
|
|
2445
|
+
const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
|
|
2446
|
+
const output = execSync2(cmd, {
|
|
2447
|
+
cwd: projectRoot,
|
|
2448
|
+
encoding: "utf8",
|
|
2449
|
+
timeout: 3e3,
|
|
2450
|
+
maxBuffer: 1024 * 1024
|
|
2451
|
+
});
|
|
2452
|
+
if (!output.trim()) return [];
|
|
2453
|
+
const hits = [];
|
|
2454
|
+
for (const line of output.trim().split("\n")) {
|
|
2455
|
+
const colonIdx = line.lastIndexOf(":");
|
|
2456
|
+
if (colonIdx === -1) continue;
|
|
2457
|
+
const filePath = line.slice(0, colonIdx);
|
|
2458
|
+
const matchCount = parseInt(line.slice(colonIdx + 1));
|
|
2459
|
+
if (isNaN(matchCount) || matchCount === 0) continue;
|
|
2460
|
+
try {
|
|
2461
|
+
const firstMatch = execSync2(
|
|
2462
|
+
`rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
|
|
2463
|
+
{ cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
|
|
2464
|
+
).trim();
|
|
2465
|
+
const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
|
|
2466
|
+
const totalLines = execSync2(`wc -l < '${filePath}'`, {
|
|
2467
|
+
cwd: projectRoot,
|
|
2468
|
+
encoding: "utf8",
|
|
2469
|
+
timeout: 1e3
|
|
2470
|
+
}).trim();
|
|
2471
|
+
hits.push({
|
|
2472
|
+
filePath,
|
|
2473
|
+
lineNumber: isNaN(lineNum) ? 1 : lineNum,
|
|
2474
|
+
matchLine: firstMatch.slice(firstMatch.indexOf(":") + 1).slice(0, 200),
|
|
2475
|
+
matchCount,
|
|
2476
|
+
totalLines: parseInt(totalLines) || 1,
|
|
2477
|
+
density: matchCount / (parseInt(totalLines) || 1)
|
|
2478
|
+
});
|
|
2479
|
+
} catch {
|
|
2480
|
+
}
|
|
2108
2481
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2482
|
+
return hits;
|
|
2483
|
+
}
|
|
2484
|
+
function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
2485
|
+
const regex = new RegExp(pattern, "gi");
|
|
2486
|
+
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
2487
|
+
const hits = [];
|
|
2488
|
+
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
2489
|
+
const absPath = path6.join(projectRoot, filePath);
|
|
2490
|
+
try {
|
|
2491
|
+
const stat = statSync2(absPath);
|
|
2492
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
2493
|
+
const content = readFileSync3(absPath, "utf8");
|
|
2494
|
+
const lines = content.split("\n");
|
|
2495
|
+
const matches = content.match(regex);
|
|
2496
|
+
if (!matches || matches.length === 0) continue;
|
|
2497
|
+
const firstMatchIdx = lines.findIndex((l) => regex.test(l));
|
|
2498
|
+
regex.lastIndex = 0;
|
|
2499
|
+
hits.push({
|
|
2500
|
+
filePath,
|
|
2501
|
+
lineNumber: firstMatchIdx >= 0 ? firstMatchIdx + 1 : 1,
|
|
2502
|
+
matchLine: firstMatchIdx >= 0 ? lines[firstMatchIdx].slice(0, 200) : "",
|
|
2503
|
+
matchCount: matches.length,
|
|
2504
|
+
totalLines: lines.length,
|
|
2505
|
+
density: matches.length / lines.length
|
|
2506
|
+
});
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2117
2509
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2510
|
+
return hits;
|
|
2511
|
+
}
|
|
2512
|
+
function collectFiles(root, patterns) {
|
|
2513
|
+
const files = [];
|
|
2514
|
+
function walk(dir, relative) {
|
|
2515
|
+
if (files.length >= MAX_FILES) return;
|
|
2516
|
+
const basename = path6.basename(dir);
|
|
2517
|
+
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
2518
|
+
try {
|
|
2519
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2520
|
+
for (const entry of entries) {
|
|
2521
|
+
if (files.length >= MAX_FILES) return;
|
|
2522
|
+
const rel = path6.join(relative, entry.name);
|
|
2523
|
+
if (entry.isDirectory()) {
|
|
2524
|
+
walk(path6.join(dir, entry.name), rel);
|
|
2525
|
+
} else if (entry.isFile()) {
|
|
2526
|
+
for (const pat of patterns) {
|
|
2527
|
+
if (matchGlob(rel, pat)) {
|
|
2528
|
+
files.push(rel);
|
|
2529
|
+
break;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
} catch {
|
|
2125
2535
|
}
|
|
2126
2536
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2537
|
+
walk(root, "");
|
|
2538
|
+
return files;
|
|
2539
|
+
}
|
|
2540
|
+
function matchGlob(filePath, pattern) {
|
|
2541
|
+
if (!pattern.includes("*")) return filePath === pattern;
|
|
2542
|
+
const doubleStarMatch = pattern.match(/^(.+?)\/\*\*\/\*(\.\w+)$/);
|
|
2543
|
+
if (doubleStarMatch) {
|
|
2544
|
+
const dir = doubleStarMatch[1];
|
|
2545
|
+
const ext2 = doubleStarMatch[2];
|
|
2546
|
+
return filePath.startsWith(dir + "/") && filePath.endsWith(ext2);
|
|
2547
|
+
}
|
|
2548
|
+
if (pattern.startsWith("**/")) {
|
|
2549
|
+
const ext2 = pattern.slice(3).replace("*", "");
|
|
2550
|
+
return filePath.endsWith(ext2);
|
|
2551
|
+
}
|
|
2552
|
+
const slashIdx = pattern.lastIndexOf("/");
|
|
2553
|
+
if (slashIdx !== -1) {
|
|
2554
|
+
const dir = pattern.slice(0, slashIdx);
|
|
2555
|
+
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
2556
|
+
const fileDir = path6.dirname(filePath);
|
|
2557
|
+
return fileDir === dir && filePath.endsWith(ext2);
|
|
2558
|
+
}
|
|
2559
|
+
const ext = pattern.replace("*", "");
|
|
2560
|
+
return filePath.endsWith(ext) && !filePath.includes("/");
|
|
2561
|
+
}
|
|
2562
|
+
function buildSnippet(hit, projectRoot) {
|
|
2133
2563
|
try {
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2564
|
+
const absPath = path6.join(projectRoot, hit.filePath);
|
|
2565
|
+
if (!existsSync5(absPath)) return hit.matchLine;
|
|
2566
|
+
const lines = readFileSync3(absPath, "utf8").split("\n");
|
|
2567
|
+
const start = Math.max(0, hit.lineNumber - 3);
|
|
2568
|
+
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
2569
|
+
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
2136
2570
|
} catch {
|
|
2571
|
+
return hit.matchLine;
|
|
2137
2572
|
}
|
|
2138
|
-
const client = getClient();
|
|
2139
|
-
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
2140
|
-
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2141
2573
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2574
|
+
var _hasRg, DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
|
|
2575
|
+
var init_file_grep = __esm({
|
|
2576
|
+
"src/lib/file-grep.ts"() {
|
|
2577
|
+
"use strict";
|
|
2578
|
+
_hasRg = null;
|
|
2579
|
+
DEFAULT_PATTERNS = [
|
|
2580
|
+
".planning/*.md",
|
|
2581
|
+
"exe/output/*.md",
|
|
2582
|
+
"README.md",
|
|
2583
|
+
"src/**/*.ts"
|
|
2584
|
+
];
|
|
2585
|
+
EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage", ".worktrees"];
|
|
2586
|
+
MAX_FILE_SIZE = 100 * 1024;
|
|
2587
|
+
MAX_FILES = 500;
|
|
2148
2588
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
// src/lib/reranker.ts
|
|
2592
|
+
var reranker_exports = {};
|
|
2593
|
+
__export(reranker_exports, {
|
|
2594
|
+
disposeReranker: () => disposeReranker,
|
|
2595
|
+
getRerankerModelPath: () => getRerankerModelPath,
|
|
2596
|
+
isRerankerAvailable: () => isRerankerAvailable,
|
|
2597
|
+
rerank: () => rerank,
|
|
2598
|
+
rerankWithScores: () => rerankWithScores
|
|
2599
|
+
});
|
|
2600
|
+
import path7 from "path";
|
|
2601
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2602
|
+
function resetIdleTimer() {
|
|
2603
|
+
if (_idleTimer) clearTimeout(_idleTimer);
|
|
2604
|
+
_idleTimer = setTimeout(() => {
|
|
2605
|
+
void disposeReranker();
|
|
2606
|
+
}, IDLE_TIMEOUT_MS);
|
|
2607
|
+
if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
|
|
2608
|
+
_idleTimer.unref();
|
|
2156
2609
|
}
|
|
2157
|
-
return { clause, args };
|
|
2158
2610
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
client = getClient();
|
|
2611
|
+
function isRerankerAvailable() {
|
|
2612
|
+
return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
2613
|
+
}
|
|
2614
|
+
function getRerankerModelPath() {
|
|
2615
|
+
return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2616
|
+
}
|
|
2617
|
+
async function ensureLoaded() {
|
|
2618
|
+
if (_rerankerContext) {
|
|
2619
|
+
resetIdleTimer();
|
|
2620
|
+
return;
|
|
2170
2621
|
}
|
|
2171
|
-
const
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
has_error, raw_text, vector, importance, status,
|
|
2177
|
-
confidence, last_accessed,
|
|
2178
|
-
workspace_id, document_id, user_id,
|
|
2179
|
-
char_offset, page_number
|
|
2180
|
-
FROM memories
|
|
2181
|
-
WHERE agent_id = ?
|
|
2182
|
-
AND vector IS NOT NULL${statusFilter}
|
|
2183
|
-
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
2184
|
-
const args = [agentId];
|
|
2185
|
-
const scope = buildWikiScopeFilter(options, "");
|
|
2186
|
-
sql += scope.clause;
|
|
2187
|
-
args.push(...scope.args);
|
|
2188
|
-
if (options?.projectName) {
|
|
2189
|
-
sql += ` AND project_name = ?`;
|
|
2190
|
-
args.push(options.projectName);
|
|
2622
|
+
const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2623
|
+
if (!existsSync6(modelPath)) {
|
|
2624
|
+
throw new Error(
|
|
2625
|
+
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
2626
|
+
);
|
|
2191
2627
|
}
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2628
|
+
process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
|
|
2629
|
+
const { getLlama } = await import("node-llama-cpp");
|
|
2630
|
+
const llama = await getLlama();
|
|
2631
|
+
_rerankerModel = await llama.loadModel({ modelPath });
|
|
2632
|
+
_rerankerContext = await _rerankerModel.createEmbeddingContext();
|
|
2633
|
+
process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
|
|
2634
|
+
resetIdleTimer();
|
|
2635
|
+
}
|
|
2636
|
+
async function disposeReranker() {
|
|
2637
|
+
if (_idleTimer) {
|
|
2638
|
+
clearTimeout(_idleTimer);
|
|
2639
|
+
_idleTimer = null;
|
|
2195
2640
|
}
|
|
2196
|
-
if (
|
|
2197
|
-
|
|
2198
|
-
|
|
2641
|
+
if (_rerankerContext) {
|
|
2642
|
+
try {
|
|
2643
|
+
await _rerankerContext.dispose();
|
|
2644
|
+
} catch {
|
|
2645
|
+
}
|
|
2646
|
+
_rerankerContext = null;
|
|
2199
2647
|
}
|
|
2200
|
-
if (
|
|
2201
|
-
|
|
2202
|
-
|
|
2648
|
+
if (_rerankerModel) {
|
|
2649
|
+
try {
|
|
2650
|
+
await _rerankerModel.dispose();
|
|
2651
|
+
} catch {
|
|
2652
|
+
}
|
|
2653
|
+
_rerankerModel = null;
|
|
2203
2654
|
}
|
|
2204
|
-
|
|
2205
|
-
args.push(vectorToBlob(queryVector));
|
|
2206
|
-
sql += ` LIMIT ?`;
|
|
2207
|
-
args.push(limit);
|
|
2208
|
-
const result = await client.execute({ sql, args });
|
|
2209
|
-
return result.rows.map((row) => ({
|
|
2210
|
-
id: row.id,
|
|
2211
|
-
agent_id: row.agent_id,
|
|
2212
|
-
agent_role: row.agent_role,
|
|
2213
|
-
session_id: row.session_id,
|
|
2214
|
-
timestamp: row.timestamp,
|
|
2215
|
-
tool_name: row.tool_name,
|
|
2216
|
-
project_name: row.project_name,
|
|
2217
|
-
has_error: row.has_error === 1,
|
|
2218
|
-
raw_text: row.raw_text,
|
|
2219
|
-
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
2220
|
-
importance: row.importance ?? 5,
|
|
2221
|
-
status: row.status ?? "active",
|
|
2222
|
-
confidence: row.confidence ?? 0.7,
|
|
2223
|
-
last_accessed: row.last_accessed ?? row.timestamp,
|
|
2224
|
-
workspace_id: row.workspace_id ?? null,
|
|
2225
|
-
document_id: row.document_id ?? null,
|
|
2226
|
-
user_id: row.user_id ?? null,
|
|
2227
|
-
char_offset: row.char_offset ?? null,
|
|
2228
|
-
page_number: row.page_number ?? null
|
|
2229
|
-
}));
|
|
2655
|
+
process.stderr.write("[reranker] Unloaded (idle timeout).\n");
|
|
2230
2656
|
}
|
|
2231
|
-
async function
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
args: docIds
|
|
2246
|
-
});
|
|
2247
|
-
const byId = /* @__PURE__ */ new Map();
|
|
2248
|
-
for (const row of result.rows) {
|
|
2249
|
-
const id = row.id;
|
|
2250
|
-
byId.set(id, {
|
|
2251
|
-
document_id: id,
|
|
2252
|
-
filename: row.filename,
|
|
2253
|
-
mime: row.mime ?? null,
|
|
2254
|
-
source_type: row.source_type ?? null,
|
|
2255
|
-
uploaded_at: row.uploaded_at
|
|
2256
|
-
});
|
|
2257
|
-
}
|
|
2258
|
-
for (const record of records) {
|
|
2259
|
-
if (!record.document_id) continue;
|
|
2260
|
-
record.document_metadata = byId.get(record.document_id) ?? null;
|
|
2657
|
+
async function rerankWithScores(query, texts, topK) {
|
|
2658
|
+
if (texts.length === 0) return [];
|
|
2659
|
+
await ensureLoaded();
|
|
2660
|
+
const ctx = _rerankerContext;
|
|
2661
|
+
const scored = [];
|
|
2662
|
+
for (let i = 0; i < texts.length; i++) {
|
|
2663
|
+
const text = texts[i] ?? "";
|
|
2664
|
+
try {
|
|
2665
|
+
const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
|
|
2666
|
+
const embedding = await ctx.getEmbeddingFor(input2);
|
|
2667
|
+
const score = embedding.vector[0] ?? 0;
|
|
2668
|
+
scored.push({ text, score, index: i });
|
|
2669
|
+
} catch {
|
|
2670
|
+
scored.push({ text, score: -1, index: i });
|
|
2261
2671
|
}
|
|
2262
|
-
} catch {
|
|
2263
2672
|
}
|
|
2264
|
-
|
|
2673
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2674
|
+
return typeof topK === "number" ? scored.slice(0, topK) : scored;
|
|
2265
2675
|
}
|
|
2266
|
-
function
|
|
2267
|
-
|
|
2268
|
-
|
|
2676
|
+
async function rerank(query, candidates, topK = 5) {
|
|
2677
|
+
if (candidates.length === 0) return [];
|
|
2678
|
+
if (candidates.length <= topK) return candidates;
|
|
2679
|
+
const scored = await rerankWithScores(
|
|
2680
|
+
query,
|
|
2681
|
+
candidates.map((c) => c.raw_text),
|
|
2682
|
+
topK
|
|
2683
|
+
);
|
|
2684
|
+
return scored.map((s) => candidates[s.index]);
|
|
2269
2685
|
}
|
|
2686
|
+
var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
|
|
2687
|
+
var init_reranker = __esm({
|
|
2688
|
+
"src/lib/reranker.ts"() {
|
|
2689
|
+
"use strict";
|
|
2690
|
+
init_config();
|
|
2691
|
+
RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
|
|
2692
|
+
IDLE_TIMEOUT_MS = 6e4;
|
|
2693
|
+
_rerankerContext = null;
|
|
2694
|
+
_rerankerModel = null;
|
|
2695
|
+
_idleTimer = null;
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
|
|
2699
|
+
// src/adapters/claude/hooks/session-start.ts
|
|
2700
|
+
init_config();
|
|
2701
|
+
init_config();
|
|
2702
|
+
init_store();
|
|
2703
|
+
init_database();
|
|
2704
|
+
import path9 from "path";
|
|
2705
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
2270
2706
|
|
|
2271
2707
|
// src/lib/hybrid-search.ts
|
|
2708
|
+
init_store();
|
|
2709
|
+
init_database();
|
|
2272
2710
|
var RRF_K = 60;
|
|
2273
2711
|
async function hybridSearch(queryText, agentId, options) {
|
|
2274
2712
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -2295,8 +2733,21 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
2295
2733
|
} catch {
|
|
2296
2734
|
}
|
|
2297
2735
|
}
|
|
2736
|
+
const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
2737
|
+
const cardinality = await getMemoryCardinality2(agentId);
|
|
2738
|
+
const { rerankerAutoTrigger } = config.scalingRoadmap ?? {};
|
|
2739
|
+
const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
|
|
2740
|
+
const useNarrowPath = cardinality < 1e4;
|
|
2741
|
+
const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
|
|
2742
|
+
const effectiveIsBroad = useBroadPath && !useNarrowPath;
|
|
2743
|
+
if (effectiveIsBroad !== _isBroadQuery) {
|
|
2744
|
+
process.stderr.write(
|
|
2745
|
+
`[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
|
|
2746
|
+
`
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2298
2749
|
const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
|
|
2299
|
-
const fetchLimit =
|
|
2750
|
+
const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
|
|
2300
2751
|
const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
|
|
2301
2752
|
let queryVector = null;
|
|
2302
2753
|
try {
|
|
@@ -2340,8 +2791,8 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
2340
2791
|
weights.push(0.5);
|
|
2341
2792
|
}
|
|
2342
2793
|
if (lists.length === 0) return [];
|
|
2343
|
-
if (lists.length === 1 && !
|
|
2344
|
-
const rrfLimit =
|
|
2794
|
+
if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
|
|
2795
|
+
const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
|
|
2345
2796
|
const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
|
|
2346
2797
|
const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
|
|
2347
2798
|
enabled: config.rerankerEnabled ?? true,
|
|
@@ -2350,9 +2801,9 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
2350
2801
|
returnTopK: 5
|
|
2351
2802
|
};
|
|
2352
2803
|
let rerankedAndBlended = null;
|
|
2353
|
-
if (
|
|
2354
|
-
const
|
|
2355
|
-
if (
|
|
2804
|
+
if (effectiveIsBroad && auto.enabled) {
|
|
2805
|
+
const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
|
|
2806
|
+
if (cardinality2 > auto.broadQueryMinCardinality) {
|
|
2356
2807
|
try {
|
|
2357
2808
|
const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
|
|
2358
2809
|
if (isRerankerAvailable2()) {
|
|
@@ -2429,6 +2880,11 @@ function recencyScore(timestamp) {
|
|
|
2429
2880
|
function normalizedImportance(importance) {
|
|
2430
2881
|
return ((importance ?? 5) - 1) / 9;
|
|
2431
2882
|
}
|
|
2883
|
+
function frecencyBoost(lastAccessed, timestamp) {
|
|
2884
|
+
const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
|
|
2885
|
+
const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
|
|
2886
|
+
return Math.exp(-0.01 * hoursSince);
|
|
2887
|
+
}
|
|
2432
2888
|
function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
2433
2889
|
const scores = /* @__PURE__ */ new Map();
|
|
2434
2890
|
for (let listIdx = 0; listIdx < lists.length; listIdx++) {
|
|
@@ -2445,7 +2901,9 @@ function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
|
2445
2901
|
const recency = recencyScore(e.record.timestamp);
|
|
2446
2902
|
const importance = normalizedImportance(e.record.importance);
|
|
2447
2903
|
const confidence = e.record.confidence ?? 0.7;
|
|
2448
|
-
const
|
|
2904
|
+
const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
|
|
2905
|
+
const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
|
|
2906
|
+
const finalScore = baseScore * (1 + 0.3 * frecency);
|
|
2449
2907
|
return { score: finalScore, record: e.record };
|
|
2450
2908
|
});
|
|
2451
2909
|
return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
|
|
@@ -2485,7 +2943,8 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
|
2485
2943
|
m.has_error, m.raw_text, m.vector, m.task_id,
|
|
2486
2944
|
m.importance, m.status, m.confidence, m.last_accessed,
|
|
2487
2945
|
m.workspace_id, m.document_id, m.user_id,
|
|
2488
|
-
m.char_offset, m.page_number
|
|
2946
|
+
m.char_offset, m.page_number,
|
|
2947
|
+
m.source_path, m.source_type
|
|
2489
2948
|
FROM memories m
|
|
2490
2949
|
JOIN memories_fts fts ON m.rowid = fts.rowid
|
|
2491
2950
|
WHERE memories_fts MATCH ?
|
|
@@ -2534,7 +2993,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
|
2534
2993
|
document_id: row.document_id ?? null,
|
|
2535
2994
|
user_id: row.user_id ?? null,
|
|
2536
2995
|
char_offset: row.char_offset ?? null,
|
|
2537
|
-
page_number: row.page_number ?? null
|
|
2996
|
+
page_number: row.page_number ?? null,
|
|
2997
|
+
source_path: row.source_path ?? null,
|
|
2998
|
+
source_type: row.source_type ?? null
|
|
2538
2999
|
}));
|
|
2539
3000
|
}
|
|
2540
3001
|
async function recentRecords(agentId, options, limit) {
|
|
@@ -2546,7 +3007,8 @@ async function recentRecords(agentId, options, limit) {
|
|
|
2546
3007
|
has_error, raw_text, vector, task_id,
|
|
2547
3008
|
importance, status, confidence, last_accessed,
|
|
2548
3009
|
workspace_id, document_id, user_id,
|
|
2549
|
-
char_offset, page_number
|
|
3010
|
+
char_offset, page_number,
|
|
3011
|
+
source_path, source_type
|
|
2550
3012
|
FROM memories
|
|
2551
3013
|
WHERE agent_id = ?${statusFilter}
|
|
2552
3014
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
@@ -2593,7 +3055,9 @@ async function recentRecords(agentId, options, limit) {
|
|
|
2593
3055
|
document_id: row.document_id ?? null,
|
|
2594
3056
|
user_id: row.user_id ?? null,
|
|
2595
3057
|
char_offset: row.char_offset ?? null,
|
|
2596
|
-
page_number: row.page_number ?? null
|
|
3058
|
+
page_number: row.page_number ?? null,
|
|
3059
|
+
source_path: row.source_path ?? null,
|
|
3060
|
+
source_type: row.source_type ?? null
|
|
2597
3061
|
}));
|
|
2598
3062
|
}
|
|
2599
3063
|
|