@askexenow/exe-os 0.8.0 → 0.8.2

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.
Files changed (107) hide show
  1. package/README.md +178 -79
  2. package/package.json +1 -1
  3. package/dist/bin/backfill-responses.js +0 -1912
  4. package/dist/bin/backfill-vectors.js +0 -1642
  5. package/dist/bin/cleanup-stale-review-tasks.js +0 -1339
  6. package/dist/bin/cli.js +0 -18800
  7. package/dist/bin/exe-agent.js +0 -1858
  8. package/dist/bin/exe-assign.js +0 -1957
  9. package/dist/bin/exe-boot.js +0 -6460
  10. package/dist/bin/exe-call.js +0 -197
  11. package/dist/bin/exe-cloud.js +0 -850
  12. package/dist/bin/exe-dispatch.js +0 -1146
  13. package/dist/bin/exe-doctor.js +0 -1657
  14. package/dist/bin/exe-export-behaviors.js +0 -1494
  15. package/dist/bin/exe-forget.js +0 -1627
  16. package/dist/bin/exe-gateway.js +0 -7732
  17. package/dist/bin/exe-healthcheck.js +0 -207
  18. package/dist/bin/exe-heartbeat.js +0 -1647
  19. package/dist/bin/exe-kill.js +0 -1479
  20. package/dist/bin/exe-launch-agent.js +0 -1704
  21. package/dist/bin/exe-link.js +0 -192
  22. package/dist/bin/exe-new-employee.js +0 -852
  23. package/dist/bin/exe-pending-messages.js +0 -1446
  24. package/dist/bin/exe-pending-notifications.js +0 -1321
  25. package/dist/bin/exe-pending-reviews.js +0 -1468
  26. package/dist/bin/exe-repo-drift.js +0 -95
  27. package/dist/bin/exe-review.js +0 -1590
  28. package/dist/bin/exe-search.js +0 -2651
  29. package/dist/bin/exe-session-cleanup.js +0 -3173
  30. package/dist/bin/exe-settings.js +0 -354
  31. package/dist/bin/exe-status.js +0 -1532
  32. package/dist/bin/exe-team.js +0 -1324
  33. package/dist/bin/git-sweep.js +0 -2185
  34. package/dist/bin/graph-backfill.js +0 -1968
  35. package/dist/bin/graph-export.js +0 -1604
  36. package/dist/bin/install.js +0 -656
  37. package/dist/bin/list-providers.js +0 -140
  38. package/dist/bin/scan-tasks.js +0 -1820
  39. package/dist/bin/setup.js +0 -951
  40. package/dist/bin/shard-migrate.js +0 -1494
  41. package/dist/bin/update.js +0 -95
  42. package/dist/bin/wiki-sync.js +0 -1514
  43. package/dist/gateway/index.js +0 -8848
  44. package/dist/hooks/bug-report-worker.js +0 -2743
  45. package/dist/hooks/commit-complete.js +0 -2108
  46. package/dist/hooks/error-recall.js +0 -2861
  47. package/dist/hooks/exe-heartbeat-hook.js +0 -232
  48. package/dist/hooks/ingest-worker.js +0 -4793
  49. package/dist/hooks/ingest.js +0 -684
  50. package/dist/hooks/instructions-loaded.js +0 -1880
  51. package/dist/hooks/notification.js +0 -1726
  52. package/dist/hooks/post-compact.js +0 -1751
  53. package/dist/hooks/pre-compact.js +0 -1746
  54. package/dist/hooks/pre-tool-use.js +0 -2191
  55. package/dist/hooks/prompt-ingest-worker.js +0 -2126
  56. package/dist/hooks/prompt-submit.js +0 -4693
  57. package/dist/hooks/response-ingest-worker.js +0 -1936
  58. package/dist/hooks/session-end.js +0 -1752
  59. package/dist/hooks/session-start.js +0 -2795
  60. package/dist/hooks/stop.js +0 -1835
  61. package/dist/hooks/subagent-stop.js +0 -1726
  62. package/dist/hooks/summary-worker.js +0 -2661
  63. package/dist/index.js +0 -11834
  64. package/dist/lib/cloud-sync.js +0 -495
  65. package/dist/lib/config.js +0 -222
  66. package/dist/lib/consolidation.js +0 -476
  67. package/dist/lib/crypto.js +0 -51
  68. package/dist/lib/database.js +0 -730
  69. package/dist/lib/device-registry.js +0 -900
  70. package/dist/lib/embedder.js +0 -632
  71. package/dist/lib/employee-templates.js +0 -543
  72. package/dist/lib/employees.js +0 -177
  73. package/dist/lib/error-detector.js +0 -156
  74. package/dist/lib/exe-daemon-client.js +0 -451
  75. package/dist/lib/exe-daemon.js +0 -8285
  76. package/dist/lib/file-grep.js +0 -199
  77. package/dist/lib/hybrid-search.js +0 -1819
  78. package/dist/lib/identity-templates.js +0 -320
  79. package/dist/lib/identity.js +0 -223
  80. package/dist/lib/keychain.js +0 -145
  81. package/dist/lib/license.js +0 -377
  82. package/dist/lib/messaging.js +0 -1376
  83. package/dist/lib/reminders.js +0 -63
  84. package/dist/lib/schedules.js +0 -1396
  85. package/dist/lib/session-registry.js +0 -52
  86. package/dist/lib/skill-learning.js +0 -477
  87. package/dist/lib/status-brief.js +0 -235
  88. package/dist/lib/store.js +0 -1551
  89. package/dist/lib/task-router.js +0 -62
  90. package/dist/lib/tasks.js +0 -2456
  91. package/dist/lib/tmux-routing.js +0 -2836
  92. package/dist/lib/tmux-status.js +0 -261
  93. package/dist/lib/tmux-transport.js +0 -83
  94. package/dist/lib/transport.js +0 -128
  95. package/dist/lib/ws-auth.js +0 -19
  96. package/dist/lib/ws-client.js +0 -160
  97. package/dist/mcp/server.js +0 -10538
  98. package/dist/mcp/tools/complete-reminder.js +0 -67
  99. package/dist/mcp/tools/create-reminder.js +0 -52
  100. package/dist/mcp/tools/create-task.js +0 -1853
  101. package/dist/mcp/tools/deactivate-behavior.js +0 -263
  102. package/dist/mcp/tools/list-reminders.js +0 -62
  103. package/dist/mcp/tools/list-tasks.js +0 -463
  104. package/dist/mcp/tools/send-message.js +0 -1382
  105. package/dist/mcp/tools/update-task.js +0 -1692
  106. package/dist/runtime/index.js +0 -6809
  107. package/dist/tui/App.js +0 -17479
@@ -1,632 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
-
11
- // src/lib/config.ts
12
- var config_exports = {};
13
- __export(config_exports, {
14
- CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
15
- CONFIG_PATH: () => CONFIG_PATH,
16
- CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
17
- DB_PATH: () => DB_PATH,
18
- EXE_AI_DIR: () => EXE_AI_DIR,
19
- LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
20
- MODELS_DIR: () => MODELS_DIR,
21
- loadConfig: () => loadConfig,
22
- loadConfigFrom: () => loadConfigFrom,
23
- loadConfigSync: () => loadConfigSync,
24
- migrateConfig: () => migrateConfig,
25
- saveConfig: () => saveConfig
26
- });
27
- import { readFile, writeFile, mkdir } from "fs/promises";
28
- import { readFileSync, existsSync, renameSync } from "fs";
29
- import path from "path";
30
- import os from "os";
31
- function resolveDataDir() {
32
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
33
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
34
- const newDir = path.join(os.homedir(), ".exe-os");
35
- const legacyDir = path.join(os.homedir(), ".exe-mem");
36
- if (!existsSync(newDir) && existsSync(legacyDir)) {
37
- try {
38
- renameSync(legacyDir, newDir);
39
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
40
- `);
41
- } catch {
42
- return legacyDir;
43
- }
44
- }
45
- return newDir;
46
- }
47
- function migrateLegacyConfig(raw) {
48
- if ("r2" in raw) {
49
- process.stderr.write(
50
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
51
- );
52
- delete raw.r2;
53
- }
54
- if ("syncIntervalMs" in raw) {
55
- delete raw.syncIntervalMs;
56
- }
57
- return raw;
58
- }
59
- function migrateConfig(raw) {
60
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
61
- let currentVersion = fromVersion;
62
- let migrated = false;
63
- if (currentVersion > CURRENT_CONFIG_VERSION) {
64
- return { config: raw, migrated: false, fromVersion };
65
- }
66
- for (const migration of CONFIG_MIGRATIONS) {
67
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
68
- raw = migration.migrate(raw);
69
- currentVersion = migration.to;
70
- migrated = true;
71
- }
72
- }
73
- return { config: raw, migrated, fromVersion };
74
- }
75
- function normalizeScalingRoadmap(raw) {
76
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
77
- const userRoadmap = raw.scalingRoadmap ?? {};
78
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
79
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
80
- userAuto.enabled = raw.rerankerEnabled;
81
- }
82
- raw.scalingRoadmap = {
83
- ...userRoadmap,
84
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
85
- };
86
- }
87
- function normalizeSessionLifecycle(raw) {
88
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
89
- const userSL = raw.sessionLifecycle ?? {};
90
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
91
- }
92
- async function loadConfig() {
93
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
94
- await mkdir(dir, { recursive: true });
95
- const configPath = path.join(dir, "config.json");
96
- if (!existsSync(configPath)) {
97
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
98
- }
99
- const raw = await readFile(configPath, "utf-8");
100
- try {
101
- let parsed = JSON.parse(raw);
102
- parsed = migrateLegacyConfig(parsed);
103
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
104
- if (migrated) {
105
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
106
- `);
107
- try {
108
- await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
109
- } catch {
110
- }
111
- }
112
- normalizeScalingRoadmap(migratedCfg);
113
- normalizeSessionLifecycle(migratedCfg);
114
- const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
115
- if (config.dbPath.startsWith("~")) {
116
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
117
- }
118
- return config;
119
- } catch {
120
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
121
- }
122
- }
123
- function loadConfigSync() {
124
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
125
- const configPath = path.join(dir, "config.json");
126
- if (!existsSync(configPath)) {
127
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
128
- }
129
- try {
130
- const raw = readFileSync(configPath, "utf-8");
131
- let parsed = JSON.parse(raw);
132
- parsed = migrateLegacyConfig(parsed);
133
- const { config: migratedCfg } = migrateConfig(parsed);
134
- normalizeScalingRoadmap(migratedCfg);
135
- normalizeSessionLifecycle(migratedCfg);
136
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
137
- } catch {
138
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
139
- }
140
- }
141
- async function saveConfig(config) {
142
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
143
- await mkdir(dir, { recursive: true });
144
- const configPath = path.join(dir, "config.json");
145
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
146
- }
147
- async function loadConfigFrom(configPath) {
148
- const raw = await readFile(configPath, "utf-8");
149
- try {
150
- let parsed = JSON.parse(raw);
151
- parsed = migrateLegacyConfig(parsed);
152
- const { config: migratedCfg } = migrateConfig(parsed);
153
- normalizeScalingRoadmap(migratedCfg);
154
- normalizeSessionLifecycle(migratedCfg);
155
- return { ...DEFAULT_CONFIG, ...migratedCfg };
156
- } catch {
157
- return { ...DEFAULT_CONFIG };
158
- }
159
- }
160
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
161
- var init_config = __esm({
162
- "src/lib/config.ts"() {
163
- "use strict";
164
- EXE_AI_DIR = resolveDataDir();
165
- DB_PATH = path.join(EXE_AI_DIR, "memories.db");
166
- MODELS_DIR = path.join(EXE_AI_DIR, "models");
167
- CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
168
- LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
169
- CURRENT_CONFIG_VERSION = 1;
170
- DEFAULT_CONFIG = {
171
- config_version: CURRENT_CONFIG_VERSION,
172
- dbPath: DB_PATH,
173
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
174
- embeddingDim: 1024,
175
- batchSize: 20,
176
- flushIntervalMs: 1e4,
177
- autoIngestion: true,
178
- autoRetrieval: true,
179
- searchMode: "hybrid",
180
- hookSearchMode: "hybrid",
181
- fileGrepEnabled: true,
182
- splashEffect: true,
183
- consolidationEnabled: true,
184
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
185
- consolidationModel: "claude-haiku-4-5-20251001",
186
- consolidationMaxCallsPerRun: 20,
187
- selfQueryRouter: true,
188
- selfQueryModel: "claude-haiku-4-5-20251001",
189
- rerankerEnabled: true,
190
- scalingRoadmap: {
191
- rerankerAutoTrigger: {
192
- enabled: true,
193
- broadQueryMinCardinality: 5e4,
194
- fetchTopK: 150,
195
- returnTopK: 5
196
- }
197
- },
198
- graphRagEnabled: true,
199
- wikiEnabled: false,
200
- wikiUrl: "",
201
- wikiApiKey: "",
202
- wikiSyncIntervalMs: 30 * 60 * 1e3,
203
- wikiWorkspaceMapping: {
204
- exe: "Executive",
205
- yoshi: "Engineering",
206
- mari: "Marketing",
207
- tom: "Engineering",
208
- sasha: "Production"
209
- },
210
- wikiAutoUpdate: true,
211
- wikiAutoUpdateThreshold: 0.5,
212
- wikiAutoUpdateCreateNew: true,
213
- skillLearning: true,
214
- skillThreshold: 3,
215
- skillModel: "claude-haiku-4-5-20251001",
216
- exeHeartbeat: {
217
- enabled: true,
218
- intervalSeconds: 60,
219
- staleInProgressThresholdHours: 2
220
- },
221
- sessionLifecycle: {
222
- idleKillEnabled: true,
223
- idleKillTicksRequired: 3,
224
- idleKillIntercomAckWindowMs: 1e4,
225
- maxAutoInstances: 10
226
- }
227
- };
228
- CONFIG_MIGRATIONS = [
229
- {
230
- from: 0,
231
- to: 1,
232
- migrate: (cfg) => {
233
- cfg.config_version = 1;
234
- return cfg;
235
- }
236
- }
237
- ];
238
- }
239
- });
240
-
241
- // src/types/memory.ts
242
- var EMBEDDING_DIM = 1024;
243
-
244
- // src/lib/exe-daemon-client.ts
245
- init_config();
246
- import net from "net";
247
- import { spawn } from "child_process";
248
- import { randomUUID } from "crypto";
249
- import { existsSync as existsSync2, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
250
- import path2 from "path";
251
- import { fileURLToPath } from "url";
252
- var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path2.join(EXE_AI_DIR, "exed.sock");
253
- var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path2.join(EXE_AI_DIR, "exed.pid");
254
- var SPAWN_LOCK_PATH = path2.join(EXE_AI_DIR, "exed-spawn.lock");
255
- var SPAWN_LOCK_STALE_MS = 3e4;
256
- var CONNECT_TIMEOUT_MS = 15e3;
257
- var REQUEST_TIMEOUT_MS = 3e4;
258
- var _socket = null;
259
- var _connected = false;
260
- var _buffer = "";
261
- var _requestCount = 0;
262
- var HEALTH_CHECK_INTERVAL = 100;
263
- var _pending = /* @__PURE__ */ new Map();
264
- function handleData(chunk) {
265
- _buffer += chunk.toString();
266
- let newlineIdx;
267
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
268
- const line = _buffer.slice(0, newlineIdx).trim();
269
- _buffer = _buffer.slice(newlineIdx + 1);
270
- if (!line) continue;
271
- try {
272
- const response = JSON.parse(line);
273
- const entry = _pending.get(response.id);
274
- if (entry) {
275
- clearTimeout(entry.timer);
276
- _pending.delete(response.id);
277
- entry.resolve(response);
278
- }
279
- } catch {
280
- }
281
- }
282
- }
283
- function cleanupStaleFiles() {
284
- if (existsSync2(PID_PATH)) {
285
- try {
286
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
287
- if (pid > 0) {
288
- try {
289
- process.kill(pid, 0);
290
- return;
291
- } catch {
292
- }
293
- }
294
- } catch {
295
- }
296
- try {
297
- unlinkSync(PID_PATH);
298
- } catch {
299
- }
300
- try {
301
- unlinkSync(SOCKET_PATH);
302
- } catch {
303
- }
304
- }
305
- }
306
- function findPackageRoot() {
307
- let dir = path2.dirname(fileURLToPath(import.meta.url));
308
- const { root } = path2.parse(dir);
309
- while (dir !== root) {
310
- if (existsSync2(path2.join(dir, "package.json"))) return dir;
311
- dir = path2.dirname(dir);
312
- }
313
- return null;
314
- }
315
- function spawnDaemon() {
316
- const pkgRoot = findPackageRoot();
317
- if (!pkgRoot) {
318
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
319
- return;
320
- }
321
- const daemonPath = path2.join(pkgRoot, "dist", "lib", "exe-daemon.js");
322
- if (!existsSync2(daemonPath)) {
323
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
324
- `);
325
- return;
326
- }
327
- const resolvedPath = daemonPath;
328
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
329
- `);
330
- const logPath = path2.join(path2.dirname(SOCKET_PATH), "exed.log");
331
- let stderrFd = "ignore";
332
- try {
333
- stderrFd = openSync(logPath, "a");
334
- } catch {
335
- }
336
- const child = spawn(process.execPath, [resolvedPath], {
337
- detached: true,
338
- stdio: ["ignore", "ignore", stderrFd],
339
- env: {
340
- ...process.env,
341
- EXE_DAEMON_SOCK: SOCKET_PATH,
342
- EXE_DAEMON_PID: PID_PATH
343
- }
344
- });
345
- child.unref();
346
- if (typeof stderrFd === "number") {
347
- try {
348
- closeSync(stderrFd);
349
- } catch {
350
- }
351
- }
352
- }
353
- function acquireSpawnLock() {
354
- try {
355
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
356
- closeSync(fd);
357
- return true;
358
- } catch {
359
- try {
360
- const stat = statSync(SPAWN_LOCK_PATH);
361
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
362
- try {
363
- unlinkSync(SPAWN_LOCK_PATH);
364
- } catch {
365
- }
366
- try {
367
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
368
- closeSync(fd);
369
- return true;
370
- } catch {
371
- }
372
- }
373
- } catch {
374
- }
375
- return false;
376
- }
377
- }
378
- function releaseSpawnLock() {
379
- try {
380
- unlinkSync(SPAWN_LOCK_PATH);
381
- } catch {
382
- }
383
- }
384
- function connectToSocket() {
385
- return new Promise((resolve) => {
386
- if (_socket && _connected) {
387
- resolve(true);
388
- return;
389
- }
390
- const socket = net.createConnection({ path: SOCKET_PATH });
391
- const connectTimeout = setTimeout(() => {
392
- socket.destroy();
393
- resolve(false);
394
- }, 2e3);
395
- socket.on("connect", () => {
396
- clearTimeout(connectTimeout);
397
- _socket = socket;
398
- _connected = true;
399
- _buffer = "";
400
- socket.on("data", handleData);
401
- socket.on("close", () => {
402
- _connected = false;
403
- _socket = null;
404
- for (const [id, entry] of _pending) {
405
- clearTimeout(entry.timer);
406
- _pending.delete(id);
407
- entry.resolve({ error: "Connection closed" });
408
- }
409
- });
410
- socket.on("error", () => {
411
- _connected = false;
412
- _socket = null;
413
- });
414
- resolve(true);
415
- });
416
- socket.on("error", () => {
417
- clearTimeout(connectTimeout);
418
- resolve(false);
419
- });
420
- });
421
- }
422
- async function connectEmbedDaemon() {
423
- if (_socket && _connected) return true;
424
- if (await connectToSocket()) return true;
425
- if (acquireSpawnLock()) {
426
- try {
427
- cleanupStaleFiles();
428
- spawnDaemon();
429
- } finally {
430
- releaseSpawnLock();
431
- }
432
- }
433
- const start = Date.now();
434
- let delay = 100;
435
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
436
- await new Promise((r) => setTimeout(r, delay));
437
- if (await connectToSocket()) return true;
438
- delay = Math.min(delay * 2, 3e3);
439
- }
440
- return false;
441
- }
442
- function sendRequest(texts, priority) {
443
- return new Promise((resolve) => {
444
- if (!_socket || !_connected) {
445
- resolve({ error: "Not connected" });
446
- return;
447
- }
448
- const id = randomUUID();
449
- const timer = setTimeout(() => {
450
- _pending.delete(id);
451
- resolve({ error: "Request timeout" });
452
- }, REQUEST_TIMEOUT_MS);
453
- _pending.set(id, { resolve, timer });
454
- try {
455
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
456
- } catch {
457
- clearTimeout(timer);
458
- _pending.delete(id);
459
- resolve({ error: "Write failed" });
460
- }
461
- });
462
- }
463
- async function pingDaemon() {
464
- if (!_socket || !_connected) return null;
465
- return new Promise((resolve) => {
466
- const id = randomUUID();
467
- const timer = setTimeout(() => {
468
- _pending.delete(id);
469
- resolve(null);
470
- }, 5e3);
471
- _pending.set(id, {
472
- resolve: (data) => {
473
- if (data.health) {
474
- resolve(data.health);
475
- } else {
476
- resolve(null);
477
- }
478
- },
479
- timer
480
- });
481
- try {
482
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
483
- } catch {
484
- clearTimeout(timer);
485
- _pending.delete(id);
486
- resolve(null);
487
- }
488
- });
489
- }
490
- function killAndRespawnDaemon() {
491
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
492
- if (existsSync2(PID_PATH)) {
493
- try {
494
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
495
- if (pid > 0) {
496
- try {
497
- process.kill(pid, "SIGKILL");
498
- } catch {
499
- }
500
- }
501
- } catch {
502
- }
503
- }
504
- if (_socket) {
505
- _socket.destroy();
506
- _socket = null;
507
- }
508
- _connected = false;
509
- _buffer = "";
510
- try {
511
- unlinkSync(PID_PATH);
512
- } catch {
513
- }
514
- try {
515
- unlinkSync(SOCKET_PATH);
516
- } catch {
517
- }
518
- spawnDaemon();
519
- }
520
- async function embedViaClient(text, priority = "high") {
521
- if (!_connected && !await connectEmbedDaemon()) return null;
522
- _requestCount++;
523
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
524
- const health = await pingDaemon();
525
- if (!health) {
526
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
527
- `);
528
- killAndRespawnDaemon();
529
- const start = Date.now();
530
- let delay = 200;
531
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
532
- await new Promise((r) => setTimeout(r, delay));
533
- if (await connectToSocket()) break;
534
- delay = Math.min(delay * 2, 3e3);
535
- }
536
- if (!_connected) return null;
537
- }
538
- }
539
- const result = await sendRequest([text], priority);
540
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
541
- if (result.error) {
542
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
543
- `);
544
- killAndRespawnDaemon();
545
- const start = Date.now();
546
- let delay = 200;
547
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
548
- await new Promise((r) => setTimeout(r, delay));
549
- if (await connectToSocket()) break;
550
- delay = Math.min(delay * 2, 3e3);
551
- }
552
- if (!_connected) return null;
553
- const retry = await sendRequest([text], priority);
554
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
555
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
556
- `);
557
- }
558
- return null;
559
- }
560
- function disconnectClient() {
561
- if (_socket) {
562
- _socket.destroy();
563
- _socket = null;
564
- }
565
- _connected = false;
566
- _buffer = "";
567
- for (const [id, entry] of _pending) {
568
- clearTimeout(entry.timer);
569
- _pending.delete(id);
570
- entry.resolve({ error: "Client disconnected" });
571
- }
572
- }
573
-
574
- // src/lib/embedder.ts
575
- async function getEmbedder() {
576
- const ok = await connectEmbedDaemon();
577
- if (!ok) {
578
- throw new Error(
579
- "Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
580
- );
581
- }
582
- }
583
- async function embed(text) {
584
- const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
585
- const vector = await embedViaClient(text, priority);
586
- if (!vector) {
587
- throw new Error(
588
- "Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
589
- );
590
- }
591
- if (vector.length !== EMBEDDING_DIM) {
592
- throw new Error(
593
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
594
- );
595
- }
596
- return vector;
597
- }
598
- async function disposeEmbedder() {
599
- disconnectClient();
600
- }
601
- async function embedDirect(text) {
602
- const llamaCpp = await import("node-llama-cpp");
603
- const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
604
- const { existsSync: existsSync3 } = await import("fs");
605
- const path3 = await import("path");
606
- const modelPath = path3.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
607
- if (!existsSync3(modelPath)) {
608
- throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
609
- }
610
- const llama = await llamaCpp.getLlama();
611
- const model = await llama.loadModel({ modelPath });
612
- const context = await model.createEmbeddingContext();
613
- try {
614
- const embedding = await context.getEmbeddingFor(text);
615
- const vector = Array.from(embedding.vector);
616
- if (vector.length !== EMBEDDING_DIM) {
617
- throw new Error(
618
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
619
- );
620
- }
621
- return vector;
622
- } finally {
623
- await context.dispose();
624
- await model.dispose();
625
- }
626
- }
627
- export {
628
- disposeEmbedder,
629
- embed,
630
- embedDirect,
631
- getEmbedder
632
- };