@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
|
@@ -106,6 +106,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
106
106
|
const userSL = raw.sessionLifecycle ?? {};
|
|
107
107
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
108
108
|
}
|
|
109
|
+
function normalizeAutoUpdate(raw) {
|
|
110
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
111
|
+
const userAU = raw.autoUpdate ?? {};
|
|
112
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
113
|
+
}
|
|
109
114
|
async function loadConfig() {
|
|
110
115
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
111
116
|
await mkdir(dir, { recursive: true });
|
|
@@ -128,6 +133,7 @@ async function loadConfig() {
|
|
|
128
133
|
}
|
|
129
134
|
normalizeScalingRoadmap(migratedCfg);
|
|
130
135
|
normalizeSessionLifecycle(migratedCfg);
|
|
136
|
+
normalizeAutoUpdate(migratedCfg);
|
|
131
137
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
132
138
|
if (config.dbPath.startsWith("~")) {
|
|
133
139
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -150,6 +156,7 @@ function loadConfigSync() {
|
|
|
150
156
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
151
157
|
normalizeScalingRoadmap(migratedCfg);
|
|
152
158
|
normalizeSessionLifecycle(migratedCfg);
|
|
159
|
+
normalizeAutoUpdate(migratedCfg);
|
|
153
160
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
154
161
|
} catch {
|
|
155
162
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -169,6 +176,7 @@ async function loadConfigFrom(configPath) {
|
|
|
169
176
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
170
177
|
normalizeScalingRoadmap(migratedCfg);
|
|
171
178
|
normalizeSessionLifecycle(migratedCfg);
|
|
179
|
+
normalizeAutoUpdate(migratedCfg);
|
|
172
180
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
173
181
|
} catch {
|
|
174
182
|
return { ...DEFAULT_CONFIG };
|
|
@@ -240,6 +248,11 @@ var init_config = __esm({
|
|
|
240
248
|
idleKillTicksRequired: 3,
|
|
241
249
|
idleKillIntercomAckWindowMs: 1e4,
|
|
242
250
|
maxAutoInstances: 10
|
|
251
|
+
},
|
|
252
|
+
autoUpdate: {
|
|
253
|
+
checkOnBoot: true,
|
|
254
|
+
autoInstall: false,
|
|
255
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
243
256
|
}
|
|
244
257
|
};
|
|
245
258
|
CONFIG_MIGRATIONS = [
|
|
@@ -472,6 +485,27 @@ async function ensureSchema() {
|
|
|
472
485
|
});
|
|
473
486
|
} catch {
|
|
474
487
|
}
|
|
488
|
+
try {
|
|
489
|
+
await client.execute({
|
|
490
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
491
|
+
args: []
|
|
492
|
+
});
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
await client.execute({
|
|
497
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
498
|
+
args: []
|
|
499
|
+
});
|
|
500
|
+
} catch {
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
await client.execute({
|
|
504
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
505
|
+
args: []
|
|
506
|
+
});
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
475
509
|
try {
|
|
476
510
|
await client.execute({
|
|
477
511
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -882,6 +916,15 @@ async function ensureSchema() {
|
|
|
882
916
|
} catch {
|
|
883
917
|
}
|
|
884
918
|
}
|
|
919
|
+
for (const col of [
|
|
920
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
921
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
922
|
+
]) {
|
|
923
|
+
try {
|
|
924
|
+
await client.execute(col);
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
}
|
|
885
928
|
await client.executeMultiple(`
|
|
886
929
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
887
930
|
ON memories(workspace_id);
|
|
@@ -946,6 +989,34 @@ async function ensureSchema() {
|
|
|
946
989
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
947
990
|
ON conversations(channel_id);
|
|
948
991
|
`);
|
|
992
|
+
try {
|
|
993
|
+
await client.execute({
|
|
994
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
995
|
+
args: []
|
|
996
|
+
});
|
|
997
|
+
} catch {
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
await client.execute({
|
|
1001
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1002
|
+
args: []
|
|
1003
|
+
});
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
await client.execute({
|
|
1008
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1009
|
+
args: []
|
|
1010
|
+
});
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
await client.execute({
|
|
1015
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1016
|
+
args: []
|
|
1017
|
+
});
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
949
1020
|
await client.executeMultiple(`
|
|
950
1021
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
951
1022
|
content_text,
|
|
@@ -972,13 +1043,115 @@ async function ensureSchema() {
|
|
|
972
1043
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
973
1044
|
END;
|
|
974
1045
|
`);
|
|
1046
|
+
try {
|
|
1047
|
+
await client.execute({
|
|
1048
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1049
|
+
args: []
|
|
1050
|
+
});
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
await client.execute(
|
|
1055
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1056
|
+
);
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
await client.execute({
|
|
1061
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1062
|
+
args: []
|
|
1063
|
+
});
|
|
1064
|
+
await client.execute({
|
|
1065
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1066
|
+
args: []
|
|
1067
|
+
});
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
1070
|
+
try {
|
|
1071
|
+
await client.execute({
|
|
1072
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1073
|
+
args: []
|
|
1074
|
+
});
|
|
1075
|
+
} catch {
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
await client.execute(
|
|
1079
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1080
|
+
);
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
for (const col of [
|
|
1084
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1085
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1086
|
+
]) {
|
|
1087
|
+
try {
|
|
1088
|
+
await client.execute(col);
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
async function disposeDatabase() {
|
|
1094
|
+
if (_client) {
|
|
1095
|
+
_client.close();
|
|
1096
|
+
_client = null;
|
|
1097
|
+
}
|
|
975
1098
|
}
|
|
976
|
-
var _client, initTurso;
|
|
1099
|
+
var _client, initTurso, disposeTurso;
|
|
977
1100
|
var init_database = __esm({
|
|
978
1101
|
"src/lib/database.ts"() {
|
|
979
1102
|
"use strict";
|
|
980
1103
|
_client = null;
|
|
981
1104
|
initTurso = initDatabase;
|
|
1105
|
+
disposeTurso = disposeDatabase;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// src/lib/keychain.ts
|
|
1110
|
+
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
|
|
1111
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1112
|
+
import path2 from "path";
|
|
1113
|
+
import crypto from "crypto";
|
|
1114
|
+
function getKeyDir() {
|
|
1115
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
|
|
1116
|
+
}
|
|
1117
|
+
function getKeyPath() {
|
|
1118
|
+
return path2.join(getKeyDir(), "master.key");
|
|
1119
|
+
}
|
|
1120
|
+
async function tryKeytar() {
|
|
1121
|
+
try {
|
|
1122
|
+
return await import("keytar");
|
|
1123
|
+
} catch {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async function getMasterKey() {
|
|
1128
|
+
const keytar = await tryKeytar();
|
|
1129
|
+
if (keytar) {
|
|
1130
|
+
try {
|
|
1131
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
1132
|
+
if (stored) {
|
|
1133
|
+
return Buffer.from(stored, "base64");
|
|
1134
|
+
}
|
|
1135
|
+
} catch {
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const keyPath = getKeyPath();
|
|
1139
|
+
if (!existsSync2(keyPath)) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
const content = await readFile2(keyPath, "utf-8");
|
|
1144
|
+
return Buffer.from(content.trim(), "base64");
|
|
1145
|
+
} catch {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
var SERVICE, ACCOUNT;
|
|
1150
|
+
var init_keychain = __esm({
|
|
1151
|
+
"src/lib/keychain.ts"() {
|
|
1152
|
+
"use strict";
|
|
1153
|
+
SERVICE = "exe-mem";
|
|
1154
|
+
ACCOUNT = "master-key";
|
|
982
1155
|
}
|
|
983
1156
|
});
|
|
984
1157
|
|
|
@@ -1100,13 +1273,27 @@ async function ensureShardSchema(client) {
|
|
|
1100
1273
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1101
1274
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1102
1275
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1103
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
1276
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1277
|
+
// Source provenance columns (must match database.ts)
|
|
1278
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1279
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1280
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1281
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1104
1282
|
]) {
|
|
1105
1283
|
try {
|
|
1106
1284
|
await client.execute(col);
|
|
1107
1285
|
} catch {
|
|
1108
1286
|
}
|
|
1109
1287
|
}
|
|
1288
|
+
for (const idx of [
|
|
1289
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1290
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1291
|
+
]) {
|
|
1292
|
+
try {
|
|
1293
|
+
await client.execute(idx);
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1110
1297
|
try {
|
|
1111
1298
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1112
1299
|
} catch {
|
|
@@ -1210,6 +1397,441 @@ var init_shard_manager = __esm({
|
|
|
1210
1397
|
}
|
|
1211
1398
|
});
|
|
1212
1399
|
|
|
1400
|
+
// src/lib/store.ts
|
|
1401
|
+
var store_exports = {};
|
|
1402
|
+
__export(store_exports, {
|
|
1403
|
+
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
1404
|
+
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
1405
|
+
classifyTier: () => classifyTier,
|
|
1406
|
+
disposeStore: () => disposeStore,
|
|
1407
|
+
flushBatch: () => flushBatch,
|
|
1408
|
+
flushTier3: () => flushTier3,
|
|
1409
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
1410
|
+
initStore: () => initStore,
|
|
1411
|
+
reserveVersions: () => reserveVersions,
|
|
1412
|
+
searchMemories: () => searchMemories,
|
|
1413
|
+
updateMemoryStatus: () => updateMemoryStatus,
|
|
1414
|
+
vectorToBlob: () => vectorToBlob,
|
|
1415
|
+
writeMemory: () => writeMemory
|
|
1416
|
+
});
|
|
1417
|
+
async function initStore(options) {
|
|
1418
|
+
if (_flushTimer !== null) {
|
|
1419
|
+
clearInterval(_flushTimer);
|
|
1420
|
+
_flushTimer = null;
|
|
1421
|
+
}
|
|
1422
|
+
_pendingRecords = [];
|
|
1423
|
+
_flushing = false;
|
|
1424
|
+
_batchSize = options?.batchSize ?? 20;
|
|
1425
|
+
_flushIntervalMs = options?.flushIntervalMs ?? 1e4;
|
|
1426
|
+
let dbPath = options?.dbPath;
|
|
1427
|
+
if (!dbPath) {
|
|
1428
|
+
const config = await loadConfig();
|
|
1429
|
+
dbPath = config.dbPath;
|
|
1430
|
+
}
|
|
1431
|
+
let masterKey = options?.masterKey ?? null;
|
|
1432
|
+
if (!masterKey) {
|
|
1433
|
+
masterKey = await getMasterKey();
|
|
1434
|
+
if (!masterKey) {
|
|
1435
|
+
throw new Error(
|
|
1436
|
+
"No encryption key found. Run /exe-setup to generate one."
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
const hexKey = masterKey.toString("hex");
|
|
1441
|
+
await initTurso({
|
|
1442
|
+
dbPath,
|
|
1443
|
+
encryptionKey: hexKey
|
|
1444
|
+
});
|
|
1445
|
+
await ensureSchema();
|
|
1446
|
+
try {
|
|
1447
|
+
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1448
|
+
initShardManager2(hexKey);
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
const client = getClient();
|
|
1452
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1453
|
+
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1454
|
+
}
|
|
1455
|
+
function classifyTier(record) {
|
|
1456
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
1457
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
1458
|
+
return 3;
|
|
1459
|
+
}
|
|
1460
|
+
async function writeMemory(record) {
|
|
1461
|
+
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
1462
|
+
throw new Error(
|
|
1463
|
+
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
const dbRow = {
|
|
1467
|
+
id: record.id,
|
|
1468
|
+
agent_id: record.agent_id,
|
|
1469
|
+
agent_role: record.agent_role,
|
|
1470
|
+
session_id: record.session_id,
|
|
1471
|
+
timestamp: record.timestamp,
|
|
1472
|
+
tool_name: record.tool_name,
|
|
1473
|
+
project_name: record.project_name,
|
|
1474
|
+
has_error: record.has_error ? 1 : 0,
|
|
1475
|
+
raw_text: record.raw_text,
|
|
1476
|
+
vector: record.vector,
|
|
1477
|
+
version: _nextVersion++,
|
|
1478
|
+
task_id: record.task_id ?? null,
|
|
1479
|
+
importance: record.importance ?? 5,
|
|
1480
|
+
status: record.status ?? "active",
|
|
1481
|
+
confidence: record.confidence ?? 0.7,
|
|
1482
|
+
last_accessed: record.last_accessed ?? record.timestamp,
|
|
1483
|
+
workspace_id: record.workspace_id ?? null,
|
|
1484
|
+
document_id: record.document_id ?? null,
|
|
1485
|
+
user_id: record.user_id ?? null,
|
|
1486
|
+
char_offset: record.char_offset ?? null,
|
|
1487
|
+
page_number: record.page_number ?? null,
|
|
1488
|
+
source_path: record.source_path ?? null,
|
|
1489
|
+
source_type: record.source_type ?? null,
|
|
1490
|
+
tier: record.tier ?? classifyTier(record),
|
|
1491
|
+
supersedes_id: record.supersedes_id ?? null
|
|
1492
|
+
};
|
|
1493
|
+
_pendingRecords.push(dbRow);
|
|
1494
|
+
if (_flushTimer === null) {
|
|
1495
|
+
_flushTimer = setInterval(() => {
|
|
1496
|
+
void flushBatch();
|
|
1497
|
+
}, _flushIntervalMs);
|
|
1498
|
+
if (_flushTimer && typeof _flushTimer === "object" && "unref" in _flushTimer) {
|
|
1499
|
+
_flushTimer.unref();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (_pendingRecords.length >= _batchSize) {
|
|
1503
|
+
await flushBatch();
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
async function flushBatch() {
|
|
1507
|
+
if (_flushing || _pendingRecords.length === 0) return 0;
|
|
1508
|
+
_flushing = true;
|
|
1509
|
+
try {
|
|
1510
|
+
const batch = _pendingRecords.slice(0);
|
|
1511
|
+
const buildStmt = (row) => {
|
|
1512
|
+
const hasVector = row.vector !== null;
|
|
1513
|
+
const taskId = row.task_id ?? null;
|
|
1514
|
+
const importance = row.importance ?? 5;
|
|
1515
|
+
const status = row.status ?? "active";
|
|
1516
|
+
const confidence = row.confidence ?? 0.7;
|
|
1517
|
+
const lastAccessed = row.last_accessed ?? row.timestamp;
|
|
1518
|
+
const workspaceId = row.workspace_id ?? null;
|
|
1519
|
+
const documentId = row.document_id ?? null;
|
|
1520
|
+
const userId = row.user_id ?? null;
|
|
1521
|
+
const charOffset = row.char_offset ?? null;
|
|
1522
|
+
const pageNumber = row.page_number ?? null;
|
|
1523
|
+
const sourcePath = row.source_path ?? null;
|
|
1524
|
+
const sourceType = row.source_type ?? null;
|
|
1525
|
+
const tier = row.tier ?? 3;
|
|
1526
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
1527
|
+
return {
|
|
1528
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
1529
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
1530
|
+
tool_name, project_name,
|
|
1531
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1532
|
+
confidence, last_accessed,
|
|
1533
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1534
|
+
source_path, source_type, tier, supersedes_id)
|
|
1535
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
1536
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
1537
|
+
tool_name, project_name,
|
|
1538
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1539
|
+
confidence, last_accessed,
|
|
1540
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1541
|
+
source_path, source_type, tier, supersedes_id)
|
|
1542
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1543
|
+
args: hasVector ? [
|
|
1544
|
+
row.id,
|
|
1545
|
+
row.agent_id,
|
|
1546
|
+
row.agent_role,
|
|
1547
|
+
row.session_id,
|
|
1548
|
+
row.timestamp,
|
|
1549
|
+
row.tool_name,
|
|
1550
|
+
row.project_name,
|
|
1551
|
+
row.has_error,
|
|
1552
|
+
row.raw_text,
|
|
1553
|
+
vectorToBlob(row.vector),
|
|
1554
|
+
row.version,
|
|
1555
|
+
taskId,
|
|
1556
|
+
importance,
|
|
1557
|
+
status,
|
|
1558
|
+
confidence,
|
|
1559
|
+
lastAccessed,
|
|
1560
|
+
workspaceId,
|
|
1561
|
+
documentId,
|
|
1562
|
+
userId,
|
|
1563
|
+
charOffset,
|
|
1564
|
+
pageNumber,
|
|
1565
|
+
sourcePath,
|
|
1566
|
+
sourceType,
|
|
1567
|
+
tier,
|
|
1568
|
+
supersedesId
|
|
1569
|
+
] : [
|
|
1570
|
+
row.id,
|
|
1571
|
+
row.agent_id,
|
|
1572
|
+
row.agent_role,
|
|
1573
|
+
row.session_id,
|
|
1574
|
+
row.timestamp,
|
|
1575
|
+
row.tool_name,
|
|
1576
|
+
row.project_name,
|
|
1577
|
+
row.has_error,
|
|
1578
|
+
row.raw_text,
|
|
1579
|
+
row.version,
|
|
1580
|
+
taskId,
|
|
1581
|
+
importance,
|
|
1582
|
+
status,
|
|
1583
|
+
confidence,
|
|
1584
|
+
lastAccessed,
|
|
1585
|
+
workspaceId,
|
|
1586
|
+
documentId,
|
|
1587
|
+
userId,
|
|
1588
|
+
charOffset,
|
|
1589
|
+
pageNumber,
|
|
1590
|
+
sourcePath,
|
|
1591
|
+
sourceType,
|
|
1592
|
+
tier,
|
|
1593
|
+
supersedesId
|
|
1594
|
+
]
|
|
1595
|
+
};
|
|
1596
|
+
};
|
|
1597
|
+
const globalClient = getClient();
|
|
1598
|
+
const globalStmts = batch.map(buildStmt);
|
|
1599
|
+
await globalClient.batch(globalStmts, "write");
|
|
1600
|
+
_pendingRecords.splice(0, batch.length);
|
|
1601
|
+
try {
|
|
1602
|
+
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1603
|
+
if (isShardingEnabled2()) {
|
|
1604
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
1605
|
+
for (const row of batch) {
|
|
1606
|
+
const proj = row.project_name || "unknown";
|
|
1607
|
+
if (!byProject.has(proj)) byProject.set(proj, []);
|
|
1608
|
+
byProject.get(proj).push(row);
|
|
1609
|
+
}
|
|
1610
|
+
for (const [project, rows] of byProject) {
|
|
1611
|
+
try {
|
|
1612
|
+
const shardClient = await getReadyShardClient2(project);
|
|
1613
|
+
const shardStmts = rows.map(buildStmt);
|
|
1614
|
+
await shardClient.batch(shardStmts, "write");
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
process.stderr.write(
|
|
1617
|
+
`[store] Shard write failed for ${project}: ${err instanceof Error ? err.message : String(err)}
|
|
1618
|
+
`
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
return batch.length;
|
|
1626
|
+
} finally {
|
|
1627
|
+
_flushing = false;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
function buildWikiScopeFilter(options, columnPrefix) {
|
|
1631
|
+
const args = [];
|
|
1632
|
+
let clause = "";
|
|
1633
|
+
if (options?.workspaceId !== void 0) {
|
|
1634
|
+
clause += ` AND ${columnPrefix}workspace_id = ?`;
|
|
1635
|
+
args.push(options.workspaceId);
|
|
1636
|
+
}
|
|
1637
|
+
if (options?.userId === void 0) {
|
|
1638
|
+
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
1639
|
+
} else if (options.userId === null) {
|
|
1640
|
+
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
1641
|
+
} else {
|
|
1642
|
+
clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
|
|
1643
|
+
args.push(options.userId);
|
|
1644
|
+
}
|
|
1645
|
+
return { clause, args };
|
|
1646
|
+
}
|
|
1647
|
+
async function searchMemories(queryVector, agentId, options) {
|
|
1648
|
+
let client;
|
|
1649
|
+
try {
|
|
1650
|
+
const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1651
|
+
if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
|
|
1652
|
+
client = await getReadyShardClient2(options.projectName);
|
|
1653
|
+
} else {
|
|
1654
|
+
client = getClient();
|
|
1655
|
+
}
|
|
1656
|
+
} catch {
|
|
1657
|
+
client = getClient();
|
|
1658
|
+
}
|
|
1659
|
+
const limit = options?.limit ?? 10;
|
|
1660
|
+
const statusFilter = options?.includeArchived ? "" : `
|
|
1661
|
+
AND COALESCE(status, 'active') = 'active'`;
|
|
1662
|
+
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
1663
|
+
tool_name, project_name,
|
|
1664
|
+
has_error, raw_text, vector, importance, status,
|
|
1665
|
+
confidence, last_accessed,
|
|
1666
|
+
workspace_id, document_id, user_id,
|
|
1667
|
+
char_offset, page_number,
|
|
1668
|
+
source_path, source_type
|
|
1669
|
+
FROM memories
|
|
1670
|
+
WHERE agent_id = ?
|
|
1671
|
+
AND vector IS NOT NULL${statusFilter}
|
|
1672
|
+
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
1673
|
+
const args = [agentId];
|
|
1674
|
+
const scope = buildWikiScopeFilter(options, "");
|
|
1675
|
+
sql += scope.clause;
|
|
1676
|
+
args.push(...scope.args);
|
|
1677
|
+
if (options?.projectName) {
|
|
1678
|
+
sql += ` AND project_name = ?`;
|
|
1679
|
+
args.push(options.projectName);
|
|
1680
|
+
}
|
|
1681
|
+
if (options?.toolName) {
|
|
1682
|
+
sql += ` AND tool_name = ?`;
|
|
1683
|
+
args.push(options.toolName);
|
|
1684
|
+
}
|
|
1685
|
+
if (options?.hasError !== void 0) {
|
|
1686
|
+
sql += ` AND has_error = ?`;
|
|
1687
|
+
args.push(options.hasError ? 1 : 0);
|
|
1688
|
+
}
|
|
1689
|
+
if (options?.since) {
|
|
1690
|
+
sql += ` AND timestamp >= ?`;
|
|
1691
|
+
args.push(options.since);
|
|
1692
|
+
}
|
|
1693
|
+
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
1694
|
+
args.push(vectorToBlob(queryVector));
|
|
1695
|
+
sql += ` LIMIT ?`;
|
|
1696
|
+
args.push(limit);
|
|
1697
|
+
const result = await client.execute({ sql, args });
|
|
1698
|
+
return result.rows.map((row) => ({
|
|
1699
|
+
id: row.id,
|
|
1700
|
+
agent_id: row.agent_id,
|
|
1701
|
+
agent_role: row.agent_role,
|
|
1702
|
+
session_id: row.session_id,
|
|
1703
|
+
timestamp: row.timestamp,
|
|
1704
|
+
tool_name: row.tool_name,
|
|
1705
|
+
project_name: row.project_name,
|
|
1706
|
+
has_error: row.has_error === 1,
|
|
1707
|
+
raw_text: row.raw_text,
|
|
1708
|
+
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
1709
|
+
importance: row.importance ?? 5,
|
|
1710
|
+
status: row.status ?? "active",
|
|
1711
|
+
confidence: row.confidence ?? 0.7,
|
|
1712
|
+
last_accessed: row.last_accessed ?? row.timestamp,
|
|
1713
|
+
workspace_id: row.workspace_id ?? null,
|
|
1714
|
+
document_id: row.document_id ?? null,
|
|
1715
|
+
user_id: row.user_id ?? null,
|
|
1716
|
+
char_offset: row.char_offset ?? null,
|
|
1717
|
+
page_number: row.page_number ?? null,
|
|
1718
|
+
source_path: row.source_path ?? null,
|
|
1719
|
+
source_type: row.source_type ?? null
|
|
1720
|
+
}));
|
|
1721
|
+
}
|
|
1722
|
+
async function attachDocumentMetadata(records) {
|
|
1723
|
+
const docIds = [
|
|
1724
|
+
...new Set(
|
|
1725
|
+
records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
|
|
1726
|
+
)
|
|
1727
|
+
];
|
|
1728
|
+
if (docIds.length === 0) return records;
|
|
1729
|
+
try {
|
|
1730
|
+
const client = getClient();
|
|
1731
|
+
const placeholders = docIds.map(() => "?").join(",");
|
|
1732
|
+
const result = await client.execute({
|
|
1733
|
+
sql: `SELECT id, filename, mime, source_type, uploaded_at
|
|
1734
|
+
FROM documents
|
|
1735
|
+
WHERE id IN (${placeholders})`,
|
|
1736
|
+
args: docIds
|
|
1737
|
+
});
|
|
1738
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1739
|
+
for (const row of result.rows) {
|
|
1740
|
+
const id = row.id;
|
|
1741
|
+
byId.set(id, {
|
|
1742
|
+
document_id: id,
|
|
1743
|
+
filename: row.filename,
|
|
1744
|
+
mime: row.mime ?? null,
|
|
1745
|
+
source_type: row.source_type ?? null,
|
|
1746
|
+
uploaded_at: row.uploaded_at
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
for (const record of records) {
|
|
1750
|
+
if (!record.document_id) continue;
|
|
1751
|
+
record.document_metadata = byId.get(record.document_id) ?? null;
|
|
1752
|
+
}
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
return records;
|
|
1756
|
+
}
|
|
1757
|
+
async function flushTier3(agentId, options) {
|
|
1758
|
+
const client = getClient();
|
|
1759
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
1760
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
1761
|
+
if (options?.dryRun) {
|
|
1762
|
+
const result2 = await client.execute({
|
|
1763
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
1764
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1765
|
+
args: [agentId, cutoff]
|
|
1766
|
+
});
|
|
1767
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
1768
|
+
}
|
|
1769
|
+
const result = await client.execute({
|
|
1770
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
1771
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1772
|
+
args: [agentId, cutoff]
|
|
1773
|
+
});
|
|
1774
|
+
return { archived: result.rowsAffected };
|
|
1775
|
+
}
|
|
1776
|
+
async function disposeStore() {
|
|
1777
|
+
if (_flushTimer !== null) {
|
|
1778
|
+
clearInterval(_flushTimer);
|
|
1779
|
+
_flushTimer = null;
|
|
1780
|
+
}
|
|
1781
|
+
if (_pendingRecords.length > 0) {
|
|
1782
|
+
await flushBatch();
|
|
1783
|
+
}
|
|
1784
|
+
await disposeTurso();
|
|
1785
|
+
_pendingRecords = [];
|
|
1786
|
+
_nextVersion = 1;
|
|
1787
|
+
}
|
|
1788
|
+
function vectorToBlob(vector) {
|
|
1789
|
+
const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
|
|
1790
|
+
return JSON.stringify(Array.from(f32));
|
|
1791
|
+
}
|
|
1792
|
+
async function updateMemoryStatus(id, status) {
|
|
1793
|
+
const client = getClient();
|
|
1794
|
+
await client.execute({
|
|
1795
|
+
sql: `UPDATE memories SET status = ? WHERE id = ?`,
|
|
1796
|
+
args: [status, id]
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
function reserveVersions(count) {
|
|
1800
|
+
const reserved = [];
|
|
1801
|
+
for (let i = 0; i < count; i++) {
|
|
1802
|
+
reserved.push(_nextVersion++);
|
|
1803
|
+
}
|
|
1804
|
+
return reserved;
|
|
1805
|
+
}
|
|
1806
|
+
async function getMemoryCardinality(agentId) {
|
|
1807
|
+
try {
|
|
1808
|
+
const client = getClient();
|
|
1809
|
+
const result = await client.execute({
|
|
1810
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
1811
|
+
args: [agentId]
|
|
1812
|
+
});
|
|
1813
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
1814
|
+
} catch {
|
|
1815
|
+
return 0;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
1819
|
+
var init_store = __esm({
|
|
1820
|
+
"src/lib/store.ts"() {
|
|
1821
|
+
"use strict";
|
|
1822
|
+
init_memory();
|
|
1823
|
+
init_database();
|
|
1824
|
+
init_keychain();
|
|
1825
|
+
init_config();
|
|
1826
|
+
_pendingRecords = [];
|
|
1827
|
+
_batchSize = 20;
|
|
1828
|
+
_flushIntervalMs = 1e4;
|
|
1829
|
+
_flushTimer = null;
|
|
1830
|
+
_flushing = false;
|
|
1831
|
+
_nextVersion = 1;
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1213
1835
|
// src/lib/self-query-router.ts
|
|
1214
1836
|
var self_query_router_exports = {};
|
|
1215
1837
|
__export(self_query_router_exports, {
|
|
@@ -1715,12 +2337,23 @@ function getProjectName(cwd) {
|
|
|
1715
2337
|
const dir = cwd ?? process.cwd();
|
|
1716
2338
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
1717
2339
|
try {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2340
|
+
let repoRoot;
|
|
2341
|
+
try {
|
|
2342
|
+
const gitCommonDir = execSync("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2343
|
+
cwd: dir,
|
|
2344
|
+
encoding: "utf8",
|
|
2345
|
+
timeout: 2e3,
|
|
2346
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2347
|
+
}).trim();
|
|
2348
|
+
repoRoot = path5.dirname(gitCommonDir);
|
|
2349
|
+
} catch {
|
|
2350
|
+
repoRoot = execSync("git rev-parse --show-toplevel", {
|
|
2351
|
+
cwd: dir,
|
|
2352
|
+
encoding: "utf8",
|
|
2353
|
+
timeout: 2e3,
|
|
2354
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2355
|
+
}).trim();
|
|
2356
|
+
}
|
|
1724
2357
|
_cached = path5.basename(repoRoot);
|
|
1725
2358
|
_cachedCwd = dir;
|
|
1726
2359
|
return _cached;
|
|
@@ -1752,15 +2385,30 @@ import { execSync as execSync2 } from "child_process";
|
|
|
1752
2385
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
|
|
1753
2386
|
import path6 from "path";
|
|
1754
2387
|
import crypto2 from "crypto";
|
|
2388
|
+
function hasRipgrep() {
|
|
2389
|
+
if (_hasRg === null) {
|
|
2390
|
+
try {
|
|
2391
|
+
execSync2("rg --version", { stdio: "ignore", timeout: 2e3 });
|
|
2392
|
+
_hasRg = true;
|
|
2393
|
+
} catch {
|
|
2394
|
+
_hasRg = false;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
return _hasRg;
|
|
2398
|
+
}
|
|
1755
2399
|
async function grepProjectFiles(query, projectRoot, options) {
|
|
1756
2400
|
const maxResults = options?.maxResults ?? 10;
|
|
1757
2401
|
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_-]/g, "")).filter((t) => t.length >= 3);
|
|
1758
2402
|
if (terms.length === 0) return [];
|
|
1759
2403
|
const pattern = terms.join("|");
|
|
1760
2404
|
let hits;
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2405
|
+
if (hasRipgrep()) {
|
|
2406
|
+
try {
|
|
2407
|
+
hits = grepWithRipgrep(pattern, projectRoot, options?.patterns);
|
|
2408
|
+
} catch {
|
|
2409
|
+
hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
|
|
2410
|
+
}
|
|
2411
|
+
} else {
|
|
1764
2412
|
hits = grepWithNodeFs(pattern, projectRoot, options?.patterns);
|
|
1765
2413
|
}
|
|
1766
2414
|
hits.sort((a, b) => b.density - a.density);
|
|
@@ -1805,7 +2453,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
1805
2453
|
function grepWithRipgrep(pattern, projectRoot, patterns) {
|
|
1806
2454
|
const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
|
|
1807
2455
|
const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
|
|
1808
|
-
const cmd = `rg -i -c --hidden '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
|
|
2456
|
+
const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
|
|
1809
2457
|
const output = execSync2(cmd, {
|
|
1810
2458
|
cwd: projectRoot,
|
|
1811
2459
|
encoding: "utf8",
|
|
@@ -1934,10 +2582,11 @@ function buildSnippet(hit, projectRoot) {
|
|
|
1934
2582
|
return hit.matchLine;
|
|
1935
2583
|
}
|
|
1936
2584
|
}
|
|
1937
|
-
var DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
|
|
2585
|
+
var _hasRg, DEFAULT_PATTERNS, EXCLUDE_DIRS, MAX_FILE_SIZE, MAX_FILES;
|
|
1938
2586
|
var init_file_grep = __esm({
|
|
1939
2587
|
"src/lib/file-grep.ts"() {
|
|
1940
2588
|
"use strict";
|
|
2589
|
+
_hasRg = null;
|
|
1941
2590
|
DEFAULT_PATTERNS = [
|
|
1942
2591
|
".planning/*.md",
|
|
1943
2592
|
"exe/output/*.md",
|
|
@@ -2361,15 +3010,21 @@ function queueIntercom(targetSession, reason) {
|
|
|
2361
3010
|
}
|
|
2362
3011
|
writeQueue(queue);
|
|
2363
3012
|
}
|
|
2364
|
-
function drainQueue(
|
|
3013
|
+
function drainQueue(isSessionBusy, sendKeys) {
|
|
2365
3014
|
const queue = readQueue();
|
|
2366
3015
|
if (queue.length === 0) return { drained: 0, failed: 0 };
|
|
2367
3016
|
const remaining = [];
|
|
2368
3017
|
let drained = 0;
|
|
2369
3018
|
let failed = 0;
|
|
2370
3019
|
for (const item of queue) {
|
|
3020
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
3021
|
+
if (age > TTL_MS) {
|
|
3022
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
3023
|
+
failed++;
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
2371
3026
|
try {
|
|
2372
|
-
if (!
|
|
3027
|
+
if (!isSessionBusy(item.targetSession)) {
|
|
2373
3028
|
const success = sendKeys(item.targetSession);
|
|
2374
3029
|
if (success) {
|
|
2375
3030
|
logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
|
|
@@ -2414,12 +3069,13 @@ function logQueue(msg) {
|
|
|
2414
3069
|
} catch {
|
|
2415
3070
|
}
|
|
2416
3071
|
}
|
|
2417
|
-
var QUEUE_PATH, MAX_RETRIES, INTERCOM_LOG;
|
|
3072
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
2418
3073
|
var init_intercom_queue = __esm({
|
|
2419
3074
|
"src/lib/intercom-queue.ts"() {
|
|
2420
3075
|
"use strict";
|
|
2421
3076
|
QUEUE_PATH = path10.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
2422
3077
|
MAX_RETRIES = 5;
|
|
3078
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
2423
3079
|
INTERCOM_LOG = path10.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
2424
3080
|
}
|
|
2425
3081
|
});
|
|
@@ -2570,6 +3226,17 @@ function getGitRoot(dir) {
|
|
|
2570
3226
|
return null;
|
|
2571
3227
|
}
|
|
2572
3228
|
}
|
|
3229
|
+
function getMainRepoRoot(dir) {
|
|
3230
|
+
try {
|
|
3231
|
+
const commonDir = execSync7(
|
|
3232
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
3233
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
3234
|
+
).trim();
|
|
3235
|
+
return realpath(path14.dirname(commonDir));
|
|
3236
|
+
} catch {
|
|
3237
|
+
return null;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
2573
3240
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
2574
3241
|
const label = instanceLabel(employeeName, instance);
|
|
2575
3242
|
return path14.join(repoRoot, ".worktrees", label);
|
|
@@ -2795,6 +3462,11 @@ function getSessionState(sessionName) {
|
|
|
2795
3462
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
2796
3463
|
try {
|
|
2797
3464
|
const pane = transport.capturePane(sessionName, 5);
|
|
3465
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
3466
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
3467
|
+
return "no_claude";
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
2798
3470
|
if (/Running…/.test(pane)) return "tool";
|
|
2799
3471
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
2800
3472
|
return "idle";
|
|
@@ -2802,10 +3474,6 @@ function getSessionState(sessionName) {
|
|
|
2802
3474
|
return "offline";
|
|
2803
3475
|
}
|
|
2804
3476
|
}
|
|
2805
|
-
function isSessionBusy(sessionName) {
|
|
2806
|
-
const state = getSessionState(sessionName);
|
|
2807
|
-
return state === "thinking" || state === "tool";
|
|
2808
|
-
}
|
|
2809
3477
|
function isExeSession(sessionName) {
|
|
2810
3478
|
return /^exe\d*$/.test(sessionName);
|
|
2811
3479
|
}
|
|
@@ -2825,7 +3493,14 @@ function sendIntercom(targetSession) {
|
|
|
2825
3493
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
2826
3494
|
return "failed";
|
|
2827
3495
|
}
|
|
2828
|
-
|
|
3496
|
+
const sessionState = getSessionState(targetSession);
|
|
3497
|
+
if (sessionState === "no_claude") {
|
|
3498
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
3499
|
+
recordDebounce(targetSession);
|
|
3500
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
3501
|
+
return "queued";
|
|
3502
|
+
}
|
|
3503
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
2829
3504
|
queueIntercom(targetSession, "session busy at send time");
|
|
2830
3505
|
recordDebounce(targetSession);
|
|
2831
3506
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -2837,18 +3512,7 @@ function sendIntercom(targetSession) {
|
|
|
2837
3512
|
}
|
|
2838
3513
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
2839
3514
|
recordDebounce(targetSession);
|
|
2840
|
-
|
|
2841
|
-
try {
|
|
2842
|
-
execSync8(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
2843
|
-
} catch {
|
|
2844
|
-
}
|
|
2845
|
-
const state = getSessionState(targetSession);
|
|
2846
|
-
if (state === "thinking" || state === "tool") {
|
|
2847
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
2848
|
-
return "acknowledged";
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
3515
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
2852
3516
|
return "delivered";
|
|
2853
3517
|
} catch {
|
|
2854
3518
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -2902,7 +3566,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2902
3566
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
2903
3567
|
}
|
|
2904
3568
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
2905
|
-
const
|
|
3569
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
3570
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
2906
3571
|
if (wtPath) {
|
|
2907
3572
|
spawnOpts.cwd = wtPath;
|
|
2908
3573
|
}
|
|
@@ -3083,7 +3748,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3083
3748
|
let booted = false;
|
|
3084
3749
|
for (let i = 0; i < 30; i++) {
|
|
3085
3750
|
try {
|
|
3086
|
-
execSync8("sleep
|
|
3751
|
+
execSync8("sleep 0.5");
|
|
3087
3752
|
} catch {
|
|
3088
3753
|
}
|
|
3089
3754
|
try {
|
|
@@ -3103,7 +3768,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3103
3768
|
}
|
|
3104
3769
|
}
|
|
3105
3770
|
if (!booted) {
|
|
3106
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
3771
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3107
3772
|
}
|
|
3108
3773
|
if (!useExeAgent) {
|
|
3109
3774
|
try {
|
|
@@ -3121,7 +3786,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3121
3786
|
});
|
|
3122
3787
|
return { sessionName };
|
|
3123
3788
|
}
|
|
3124
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
3789
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3125
3790
|
var init_tmux_routing = __esm({
|
|
3126
3791
|
"src/lib/tmux-routing.ts"() {
|
|
3127
3792
|
"use strict";
|
|
@@ -3141,8 +3806,6 @@ var init_tmux_routing = __esm({
|
|
|
3141
3806
|
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3142
3807
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3143
3808
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3144
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
3145
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
3146
3809
|
}
|
|
3147
3810
|
});
|
|
3148
3811
|
|
|
@@ -3550,9 +4213,10 @@ async function createTaskCore(input2) {
|
|
|
3550
4213
|
} catch {
|
|
3551
4214
|
}
|
|
3552
4215
|
}
|
|
4216
|
+
const complexity = input2.complexity ?? "standard";
|
|
3553
4217
|
await client.execute({
|
|
3554
|
-
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
|
|
3555
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4218
|
+
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
|
|
4219
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3556
4220
|
args: [
|
|
3557
4221
|
id,
|
|
3558
4222
|
input2.title,
|
|
@@ -3566,6 +4230,11 @@ async function createTaskCore(input2) {
|
|
|
3566
4230
|
parentTaskId,
|
|
3567
4231
|
input2.reviewer ?? null,
|
|
3568
4232
|
input2.context,
|
|
4233
|
+
input2.budgetTokens ?? null,
|
|
4234
|
+
input2.budgetFallbackModel ?? null,
|
|
4235
|
+
0,
|
|
4236
|
+
null,
|
|
4237
|
+
complexity,
|
|
3569
4238
|
now,
|
|
3570
4239
|
now
|
|
3571
4240
|
]
|
|
@@ -3581,7 +4250,11 @@ async function createTaskCore(input2) {
|
|
|
3581
4250
|
taskFile,
|
|
3582
4251
|
createdAt: now,
|
|
3583
4252
|
updatedAt: now,
|
|
3584
|
-
warning
|
|
4253
|
+
warning,
|
|
4254
|
+
budgetTokens: input2.budgetTokens ?? null,
|
|
4255
|
+
budgetFallbackModel: input2.budgetFallbackModel ?? null,
|
|
4256
|
+
tokensUsed: 0,
|
|
4257
|
+
tokensWarnedAt: null
|
|
3585
4258
|
};
|
|
3586
4259
|
}
|
|
3587
4260
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
@@ -3772,6 +4445,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
3772
4445
|
"- **Approved:** mark this review task as done",
|
|
3773
4446
|
"- **Needs work:** re-open the original task with notes, mark this review as done"
|
|
3774
4447
|
].join("\n");
|
|
4448
|
+
const originalTaskId = String(row.id);
|
|
3775
4449
|
const reviewTask = await createTaskCore({
|
|
3776
4450
|
title: `Review: ${agent} completed "${taskTitle}"`,
|
|
3777
4451
|
assignedTo: reviewer,
|
|
@@ -3780,6 +4454,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
3780
4454
|
priority: "p1",
|
|
3781
4455
|
context: reviewContext,
|
|
3782
4456
|
taskFile: reviewFile,
|
|
4457
|
+
parentTaskId: originalTaskId,
|
|
3783
4458
|
skipDispatch: true
|
|
3784
4459
|
});
|
|
3785
4460
|
const reviewId = reviewTask.id;
|
|
@@ -3818,23 +4493,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3818
4493
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
3819
4494
|
try {
|
|
3820
4495
|
const client = getClient();
|
|
3821
|
-
const
|
|
3822
|
-
const
|
|
3823
|
-
|
|
3824
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
3825
|
-
const agent = parts[1];
|
|
3826
|
-
const slug = parts.slice(2).join("-");
|
|
3827
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
4496
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4497
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
4498
|
+
if (parentId) {
|
|
3828
4499
|
const result = await client.execute({
|
|
3829
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
3830
|
-
args: [
|
|
4500
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
4501
|
+
args: [now, parentId]
|
|
3831
4502
|
});
|
|
3832
4503
|
if (result.rowsAffected > 0) {
|
|
3833
4504
|
process.stderr.write(
|
|
3834
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
4505
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
3835
4506
|
`
|
|
3836
4507
|
);
|
|
3837
4508
|
}
|
|
4509
|
+
} else {
|
|
4510
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
4511
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
4512
|
+
const parts = reviewPrefix.split("-");
|
|
4513
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
4514
|
+
const agent = parts[1];
|
|
4515
|
+
const slug = parts.slice(2).join("-");
|
|
4516
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
4517
|
+
const result = await client.execute({
|
|
4518
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
4519
|
+
args: [now, originalTaskFile]
|
|
4520
|
+
});
|
|
4521
|
+
if (result.rowsAffected > 0) {
|
|
4522
|
+
process.stderr.write(
|
|
4523
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
4524
|
+
`
|
|
4525
|
+
);
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
3838
4528
|
}
|
|
3839
4529
|
} catch (err) {
|
|
3840
4530
|
process.stderr.write(
|
|
@@ -3870,234 +4560,14 @@ var init_tasks_review = __esm({
|
|
|
3870
4560
|
// src/adapters/claude/hooks/prompt-submit.ts
|
|
3871
4561
|
init_config();
|
|
3872
4562
|
init_config();
|
|
4563
|
+
init_store();
|
|
3873
4564
|
import { spawn as spawn2 } from "child_process";
|
|
3874
4565
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, existsSync as existsSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3875
4566
|
import path19 from "path";
|
|
3876
4567
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3877
4568
|
|
|
3878
|
-
// src/lib/store.ts
|
|
3879
|
-
init_memory();
|
|
3880
|
-
init_database();
|
|
3881
|
-
|
|
3882
|
-
// src/lib/keychain.ts
|
|
3883
|
-
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
|
|
3884
|
-
import { existsSync as existsSync2 } from "fs";
|
|
3885
|
-
import path2 from "path";
|
|
3886
|
-
import crypto from "crypto";
|
|
3887
|
-
var SERVICE = "exe-mem";
|
|
3888
|
-
var ACCOUNT = "master-key";
|
|
3889
|
-
function getKeyDir() {
|
|
3890
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
|
|
3891
|
-
}
|
|
3892
|
-
function getKeyPath() {
|
|
3893
|
-
return path2.join(getKeyDir(), "master.key");
|
|
3894
|
-
}
|
|
3895
|
-
async function tryKeytar() {
|
|
3896
|
-
try {
|
|
3897
|
-
return await import("keytar");
|
|
3898
|
-
} catch {
|
|
3899
|
-
return null;
|
|
3900
|
-
}
|
|
3901
|
-
}
|
|
3902
|
-
async function getMasterKey() {
|
|
3903
|
-
const keytar = await tryKeytar();
|
|
3904
|
-
if (keytar) {
|
|
3905
|
-
try {
|
|
3906
|
-
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
3907
|
-
if (stored) {
|
|
3908
|
-
return Buffer.from(stored, "base64");
|
|
3909
|
-
}
|
|
3910
|
-
} catch {
|
|
3911
|
-
}
|
|
3912
|
-
}
|
|
3913
|
-
const keyPath = getKeyPath();
|
|
3914
|
-
if (!existsSync2(keyPath)) {
|
|
3915
|
-
return null;
|
|
3916
|
-
}
|
|
3917
|
-
try {
|
|
3918
|
-
const content = await readFile2(keyPath, "utf-8");
|
|
3919
|
-
return Buffer.from(content.trim(), "base64");
|
|
3920
|
-
} catch {
|
|
3921
|
-
return null;
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
|
-
// src/lib/store.ts
|
|
3926
|
-
init_config();
|
|
3927
|
-
var _pendingRecords = [];
|
|
3928
|
-
var _batchSize = 20;
|
|
3929
|
-
var _flushIntervalMs = 1e4;
|
|
3930
|
-
var _flushTimer = null;
|
|
3931
|
-
var _flushing = false;
|
|
3932
|
-
var _nextVersion = 1;
|
|
3933
|
-
async function initStore(options) {
|
|
3934
|
-
if (_flushTimer !== null) {
|
|
3935
|
-
clearInterval(_flushTimer);
|
|
3936
|
-
_flushTimer = null;
|
|
3937
|
-
}
|
|
3938
|
-
_pendingRecords = [];
|
|
3939
|
-
_flushing = false;
|
|
3940
|
-
_batchSize = options?.batchSize ?? 20;
|
|
3941
|
-
_flushIntervalMs = options?.flushIntervalMs ?? 1e4;
|
|
3942
|
-
let dbPath = options?.dbPath;
|
|
3943
|
-
if (!dbPath) {
|
|
3944
|
-
const config = await loadConfig();
|
|
3945
|
-
dbPath = config.dbPath;
|
|
3946
|
-
}
|
|
3947
|
-
let masterKey = options?.masterKey ?? null;
|
|
3948
|
-
if (!masterKey) {
|
|
3949
|
-
masterKey = await getMasterKey();
|
|
3950
|
-
if (!masterKey) {
|
|
3951
|
-
throw new Error(
|
|
3952
|
-
"No encryption key found. Run /exe-setup to generate one."
|
|
3953
|
-
);
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
const hexKey = masterKey.toString("hex");
|
|
3957
|
-
await initTurso({
|
|
3958
|
-
dbPath,
|
|
3959
|
-
encryptionKey: hexKey
|
|
3960
|
-
});
|
|
3961
|
-
await ensureSchema();
|
|
3962
|
-
try {
|
|
3963
|
-
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
3964
|
-
initShardManager2(hexKey);
|
|
3965
|
-
} catch {
|
|
3966
|
-
}
|
|
3967
|
-
const client = getClient();
|
|
3968
|
-
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
3969
|
-
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
3970
|
-
}
|
|
3971
|
-
function buildWikiScopeFilter(options, columnPrefix) {
|
|
3972
|
-
const args = [];
|
|
3973
|
-
let clause = "";
|
|
3974
|
-
if (options?.workspaceId !== void 0) {
|
|
3975
|
-
clause += ` AND ${columnPrefix}workspace_id = ?`;
|
|
3976
|
-
args.push(options.workspaceId);
|
|
3977
|
-
}
|
|
3978
|
-
if (options?.userId === void 0) {
|
|
3979
|
-
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
3980
|
-
} else if (options.userId === null) {
|
|
3981
|
-
clause += ` AND ${columnPrefix}user_id IS NULL`;
|
|
3982
|
-
} else {
|
|
3983
|
-
clause += ` AND (${columnPrefix}user_id = ? OR ${columnPrefix}user_id IS NULL)`;
|
|
3984
|
-
args.push(options.userId);
|
|
3985
|
-
}
|
|
3986
|
-
return { clause, args };
|
|
3987
|
-
}
|
|
3988
|
-
async function searchMemories(queryVector, agentId, options) {
|
|
3989
|
-
let client;
|
|
3990
|
-
try {
|
|
3991
|
-
const { isShardingEnabled: isShardingEnabled2, shardExists: shardExists2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
3992
|
-
if (isShardingEnabled2() && options?.projectName && shardExists2(options.projectName)) {
|
|
3993
|
-
client = await getReadyShardClient2(options.projectName);
|
|
3994
|
-
} else {
|
|
3995
|
-
client = getClient();
|
|
3996
|
-
}
|
|
3997
|
-
} catch {
|
|
3998
|
-
client = getClient();
|
|
3999
|
-
}
|
|
4000
|
-
const limit = options?.limit ?? 10;
|
|
4001
|
-
const statusFilter = options?.includeArchived ? "" : `
|
|
4002
|
-
AND COALESCE(status, 'active') = 'active'`;
|
|
4003
|
-
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
4004
|
-
tool_name, project_name,
|
|
4005
|
-
has_error, raw_text, vector, importance, status,
|
|
4006
|
-
confidence, last_accessed,
|
|
4007
|
-
workspace_id, document_id, user_id,
|
|
4008
|
-
char_offset, page_number
|
|
4009
|
-
FROM memories
|
|
4010
|
-
WHERE agent_id = ?
|
|
4011
|
-
AND vector IS NOT NULL${statusFilter}
|
|
4012
|
-
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
4013
|
-
const args = [agentId];
|
|
4014
|
-
const scope = buildWikiScopeFilter(options, "");
|
|
4015
|
-
sql += scope.clause;
|
|
4016
|
-
args.push(...scope.args);
|
|
4017
|
-
if (options?.projectName) {
|
|
4018
|
-
sql += ` AND project_name = ?`;
|
|
4019
|
-
args.push(options.projectName);
|
|
4020
|
-
}
|
|
4021
|
-
if (options?.toolName) {
|
|
4022
|
-
sql += ` AND tool_name = ?`;
|
|
4023
|
-
args.push(options.toolName);
|
|
4024
|
-
}
|
|
4025
|
-
if (options?.hasError !== void 0) {
|
|
4026
|
-
sql += ` AND has_error = ?`;
|
|
4027
|
-
args.push(options.hasError ? 1 : 0);
|
|
4028
|
-
}
|
|
4029
|
-
if (options?.since) {
|
|
4030
|
-
sql += ` AND timestamp >= ?`;
|
|
4031
|
-
args.push(options.since);
|
|
4032
|
-
}
|
|
4033
|
-
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
4034
|
-
args.push(vectorToBlob(queryVector));
|
|
4035
|
-
sql += ` LIMIT ?`;
|
|
4036
|
-
args.push(limit);
|
|
4037
|
-
const result = await client.execute({ sql, args });
|
|
4038
|
-
return result.rows.map((row) => ({
|
|
4039
|
-
id: row.id,
|
|
4040
|
-
agent_id: row.agent_id,
|
|
4041
|
-
agent_role: row.agent_role,
|
|
4042
|
-
session_id: row.session_id,
|
|
4043
|
-
timestamp: row.timestamp,
|
|
4044
|
-
tool_name: row.tool_name,
|
|
4045
|
-
project_name: row.project_name,
|
|
4046
|
-
has_error: row.has_error === 1,
|
|
4047
|
-
raw_text: row.raw_text,
|
|
4048
|
-
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
4049
|
-
importance: row.importance ?? 5,
|
|
4050
|
-
status: row.status ?? "active",
|
|
4051
|
-
confidence: row.confidence ?? 0.7,
|
|
4052
|
-
last_accessed: row.last_accessed ?? row.timestamp,
|
|
4053
|
-
workspace_id: row.workspace_id ?? null,
|
|
4054
|
-
document_id: row.document_id ?? null,
|
|
4055
|
-
user_id: row.user_id ?? null,
|
|
4056
|
-
char_offset: row.char_offset ?? null,
|
|
4057
|
-
page_number: row.page_number ?? null
|
|
4058
|
-
}));
|
|
4059
|
-
}
|
|
4060
|
-
async function attachDocumentMetadata(records) {
|
|
4061
|
-
const docIds = [
|
|
4062
|
-
...new Set(
|
|
4063
|
-
records.map((r) => r.document_id).filter((id) => typeof id === "string" && id.length > 0)
|
|
4064
|
-
)
|
|
4065
|
-
];
|
|
4066
|
-
if (docIds.length === 0) return records;
|
|
4067
|
-
try {
|
|
4068
|
-
const client = getClient();
|
|
4069
|
-
const placeholders = docIds.map(() => "?").join(",");
|
|
4070
|
-
const result = await client.execute({
|
|
4071
|
-
sql: `SELECT id, filename, mime, source_type, uploaded_at
|
|
4072
|
-
FROM documents
|
|
4073
|
-
WHERE id IN (${placeholders})`,
|
|
4074
|
-
args: docIds
|
|
4075
|
-
});
|
|
4076
|
-
const byId = /* @__PURE__ */ new Map();
|
|
4077
|
-
for (const row of result.rows) {
|
|
4078
|
-
const id = row.id;
|
|
4079
|
-
byId.set(id, {
|
|
4080
|
-
document_id: id,
|
|
4081
|
-
filename: row.filename,
|
|
4082
|
-
mime: row.mime ?? null,
|
|
4083
|
-
source_type: row.source_type ?? null,
|
|
4084
|
-
uploaded_at: row.uploaded_at
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4087
|
-
for (const record of records) {
|
|
4088
|
-
if (!record.document_id) continue;
|
|
4089
|
-
record.document_metadata = byId.get(record.document_id) ?? null;
|
|
4090
|
-
}
|
|
4091
|
-
} catch {
|
|
4092
|
-
}
|
|
4093
|
-
return records;
|
|
4094
|
-
}
|
|
4095
|
-
function vectorToBlob(vector) {
|
|
4096
|
-
const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
|
|
4097
|
-
return JSON.stringify(Array.from(f32));
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
4569
|
// src/lib/hybrid-search.ts
|
|
4570
|
+
init_store();
|
|
4101
4571
|
init_database();
|
|
4102
4572
|
var RRF_K = 60;
|
|
4103
4573
|
async function hybridSearch(queryText, agentId, options) {
|
|
@@ -4125,8 +4595,21 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
4125
4595
|
} catch {
|
|
4126
4596
|
}
|
|
4127
4597
|
}
|
|
4598
|
+
const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
4599
|
+
const cardinality = await getMemoryCardinality2(agentId);
|
|
4600
|
+
const { rerankerAutoTrigger } = config.scalingRoadmap ?? {};
|
|
4601
|
+
const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
|
|
4602
|
+
const useNarrowPath = cardinality < 1e4;
|
|
4603
|
+
const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
|
|
4604
|
+
const effectiveIsBroad = useBroadPath && !useNarrowPath;
|
|
4605
|
+
if (effectiveIsBroad !== _isBroadQuery) {
|
|
4606
|
+
process.stderr.write(
|
|
4607
|
+
`[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
|
|
4608
|
+
`
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4128
4611
|
const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
|
|
4129
|
-
const fetchLimit =
|
|
4612
|
+
const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
|
|
4130
4613
|
const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
|
|
4131
4614
|
let queryVector = null;
|
|
4132
4615
|
try {
|
|
@@ -4170,8 +4653,8 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
4170
4653
|
weights.push(0.5);
|
|
4171
4654
|
}
|
|
4172
4655
|
if (lists.length === 0) return [];
|
|
4173
|
-
if (lists.length === 1 && !
|
|
4174
|
-
const rrfLimit =
|
|
4656
|
+
if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
|
|
4657
|
+
const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
|
|
4175
4658
|
const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
|
|
4176
4659
|
const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
|
|
4177
4660
|
enabled: config.rerankerEnabled ?? true,
|
|
@@ -4180,9 +4663,9 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
4180
4663
|
returnTopK: 5
|
|
4181
4664
|
};
|
|
4182
4665
|
let rerankedAndBlended = null;
|
|
4183
|
-
if (
|
|
4184
|
-
const
|
|
4185
|
-
if (
|
|
4666
|
+
if (effectiveIsBroad && auto.enabled) {
|
|
4667
|
+
const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
|
|
4668
|
+
if (cardinality2 > auto.broadQueryMinCardinality) {
|
|
4186
4669
|
try {
|
|
4187
4670
|
const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
|
|
4188
4671
|
if (isRerankerAvailable2()) {
|
|
@@ -4259,6 +4742,11 @@ function recencyScore(timestamp) {
|
|
|
4259
4742
|
function normalizedImportance(importance) {
|
|
4260
4743
|
return ((importance ?? 5) - 1) / 9;
|
|
4261
4744
|
}
|
|
4745
|
+
function frecencyBoost(lastAccessed, timestamp) {
|
|
4746
|
+
const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
|
|
4747
|
+
const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
|
|
4748
|
+
return Math.exp(-0.01 * hoursSince);
|
|
4749
|
+
}
|
|
4262
4750
|
function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
4263
4751
|
const scores = /* @__PURE__ */ new Map();
|
|
4264
4752
|
for (let listIdx = 0; listIdx < lists.length; listIdx++) {
|
|
@@ -4275,7 +4763,9 @@ function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
|
4275
4763
|
const recency = recencyScore(e.record.timestamp);
|
|
4276
4764
|
const importance = normalizedImportance(e.record.importance);
|
|
4277
4765
|
const confidence = e.record.confidence ?? 0.7;
|
|
4278
|
-
const
|
|
4766
|
+
const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
|
|
4767
|
+
const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
|
|
4768
|
+
const finalScore = baseScore * (1 + 0.3 * frecency);
|
|
4279
4769
|
return { score: finalScore, record: e.record };
|
|
4280
4770
|
});
|
|
4281
4771
|
return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
|
|
@@ -4315,7 +4805,8 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
|
4315
4805
|
m.has_error, m.raw_text, m.vector, m.task_id,
|
|
4316
4806
|
m.importance, m.status, m.confidence, m.last_accessed,
|
|
4317
4807
|
m.workspace_id, m.document_id, m.user_id,
|
|
4318
|
-
m.char_offset, m.page_number
|
|
4808
|
+
m.char_offset, m.page_number,
|
|
4809
|
+
m.source_path, m.source_type
|
|
4319
4810
|
FROM memories m
|
|
4320
4811
|
JOIN memories_fts fts ON m.rowid = fts.rowid
|
|
4321
4812
|
WHERE memories_fts MATCH ?
|
|
@@ -4364,7 +4855,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
|
4364
4855
|
document_id: row.document_id ?? null,
|
|
4365
4856
|
user_id: row.user_id ?? null,
|
|
4366
4857
|
char_offset: row.char_offset ?? null,
|
|
4367
|
-
page_number: row.page_number ?? null
|
|
4858
|
+
page_number: row.page_number ?? null,
|
|
4859
|
+
source_path: row.source_path ?? null,
|
|
4860
|
+
source_type: row.source_type ?? null
|
|
4368
4861
|
}));
|
|
4369
4862
|
}
|
|
4370
4863
|
async function recentRecords(agentId, options, limit) {
|
|
@@ -4376,7 +4869,8 @@ async function recentRecords(agentId, options, limit) {
|
|
|
4376
4869
|
has_error, raw_text, vector, task_id,
|
|
4377
4870
|
importance, status, confidence, last_accessed,
|
|
4378
4871
|
workspace_id, document_id, user_id,
|
|
4379
|
-
char_offset, page_number
|
|
4872
|
+
char_offset, page_number,
|
|
4873
|
+
source_path, source_type
|
|
4380
4874
|
FROM memories
|
|
4381
4875
|
WHERE agent_id = ?${statusFilter}
|
|
4382
4876
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
@@ -4423,7 +4917,9 @@ async function recentRecords(agentId, options, limit) {
|
|
|
4423
4917
|
document_id: row.document_id ?? null,
|
|
4424
4918
|
user_id: row.user_id ?? null,
|
|
4425
4919
|
char_offset: row.char_offset ?? null,
|
|
4426
|
-
page_number: row.page_number ?? null
|
|
4920
|
+
page_number: row.page_number ?? null,
|
|
4921
|
+
source_path: row.source_path ?? null,
|
|
4922
|
+
source_type: row.source_type ?? null
|
|
4427
4923
|
}));
|
|
4428
4924
|
}
|
|
4429
4925
|
|