@askexenow/exe-os 0.8.0

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/dist/bin/backfill-responses.js +1912 -0
  4. package/dist/bin/backfill-vectors.js +1642 -0
  5. package/dist/bin/cleanup-stale-review-tasks.js +1339 -0
  6. package/dist/bin/cli.js +18800 -0
  7. package/dist/bin/exe-agent.js +1858 -0
  8. package/dist/bin/exe-assign.js +1957 -0
  9. package/dist/bin/exe-boot.js +6460 -0
  10. package/dist/bin/exe-call.js +197 -0
  11. package/dist/bin/exe-cloud.js +850 -0
  12. package/dist/bin/exe-dispatch.js +1146 -0
  13. package/dist/bin/exe-doctor.js +1657 -0
  14. package/dist/bin/exe-export-behaviors.js +1494 -0
  15. package/dist/bin/exe-forget.js +1627 -0
  16. package/dist/bin/exe-gateway.js +7732 -0
  17. package/dist/bin/exe-healthcheck.js +207 -0
  18. package/dist/bin/exe-heartbeat.js +1647 -0
  19. package/dist/bin/exe-kill.js +1479 -0
  20. package/dist/bin/exe-launch-agent.js +1704 -0
  21. package/dist/bin/exe-link.js +192 -0
  22. package/dist/bin/exe-new-employee.js +852 -0
  23. package/dist/bin/exe-pending-messages.js +1446 -0
  24. package/dist/bin/exe-pending-notifications.js +1321 -0
  25. package/dist/bin/exe-pending-reviews.js +1468 -0
  26. package/dist/bin/exe-repo-drift.js +95 -0
  27. package/dist/bin/exe-review.js +1590 -0
  28. package/dist/bin/exe-search.js +2651 -0
  29. package/dist/bin/exe-session-cleanup.js +3173 -0
  30. package/dist/bin/exe-settings.js +354 -0
  31. package/dist/bin/exe-status.js +1532 -0
  32. package/dist/bin/exe-team.js +1324 -0
  33. package/dist/bin/git-sweep.js +2185 -0
  34. package/dist/bin/graph-backfill.js +1968 -0
  35. package/dist/bin/graph-export.js +1604 -0
  36. package/dist/bin/install.js +656 -0
  37. package/dist/bin/list-providers.js +140 -0
  38. package/dist/bin/scan-tasks.js +1820 -0
  39. package/dist/bin/setup.js +951 -0
  40. package/dist/bin/shard-migrate.js +1494 -0
  41. package/dist/bin/update.js +95 -0
  42. package/dist/bin/wiki-sync.js +1514 -0
  43. package/dist/gateway/index.js +8848 -0
  44. package/dist/hooks/bug-report-worker.js +2743 -0
  45. package/dist/hooks/commit-complete.js +2108 -0
  46. package/dist/hooks/error-recall.js +2861 -0
  47. package/dist/hooks/exe-heartbeat-hook.js +232 -0
  48. package/dist/hooks/ingest-worker.js +4793 -0
  49. package/dist/hooks/ingest.js +684 -0
  50. package/dist/hooks/instructions-loaded.js +1880 -0
  51. package/dist/hooks/notification.js +1726 -0
  52. package/dist/hooks/post-compact.js +1751 -0
  53. package/dist/hooks/pre-compact.js +1746 -0
  54. package/dist/hooks/pre-tool-use.js +2191 -0
  55. package/dist/hooks/prompt-ingest-worker.js +2126 -0
  56. package/dist/hooks/prompt-submit.js +4693 -0
  57. package/dist/hooks/response-ingest-worker.js +1936 -0
  58. package/dist/hooks/session-end.js +1752 -0
  59. package/dist/hooks/session-start.js +2795 -0
  60. package/dist/hooks/stop.js +1835 -0
  61. package/dist/hooks/subagent-stop.js +1726 -0
  62. package/dist/hooks/summary-worker.js +2661 -0
  63. package/dist/index.js +11834 -0
  64. package/dist/lib/cloud-sync.js +495 -0
  65. package/dist/lib/config.js +222 -0
  66. package/dist/lib/consolidation.js +476 -0
  67. package/dist/lib/crypto.js +51 -0
  68. package/dist/lib/database.js +730 -0
  69. package/dist/lib/device-registry.js +900 -0
  70. package/dist/lib/embedder.js +632 -0
  71. package/dist/lib/employee-templates.js +543 -0
  72. package/dist/lib/employees.js +177 -0
  73. package/dist/lib/error-detector.js +156 -0
  74. package/dist/lib/exe-daemon-client.js +451 -0
  75. package/dist/lib/exe-daemon.js +8285 -0
  76. package/dist/lib/file-grep.js +199 -0
  77. package/dist/lib/hybrid-search.js +1819 -0
  78. package/dist/lib/identity-templates.js +320 -0
  79. package/dist/lib/identity.js +223 -0
  80. package/dist/lib/keychain.js +145 -0
  81. package/dist/lib/license.js +377 -0
  82. package/dist/lib/messaging.js +1376 -0
  83. package/dist/lib/reminders.js +63 -0
  84. package/dist/lib/schedules.js +1396 -0
  85. package/dist/lib/session-registry.js +52 -0
  86. package/dist/lib/skill-learning.js +477 -0
  87. package/dist/lib/status-brief.js +235 -0
  88. package/dist/lib/store.js +1551 -0
  89. package/dist/lib/task-router.js +62 -0
  90. package/dist/lib/tasks.js +2456 -0
  91. package/dist/lib/tmux-routing.js +2836 -0
  92. package/dist/lib/tmux-status.js +261 -0
  93. package/dist/lib/tmux-transport.js +83 -0
  94. package/dist/lib/transport.js +128 -0
  95. package/dist/lib/ws-auth.js +19 -0
  96. package/dist/lib/ws-client.js +160 -0
  97. package/dist/mcp/server.js +10538 -0
  98. package/dist/mcp/tools/complete-reminder.js +67 -0
  99. package/dist/mcp/tools/create-reminder.js +52 -0
  100. package/dist/mcp/tools/create-task.js +1853 -0
  101. package/dist/mcp/tools/deactivate-behavior.js +263 -0
  102. package/dist/mcp/tools/list-reminders.js +62 -0
  103. package/dist/mcp/tools/list-tasks.js +463 -0
  104. package/dist/mcp/tools/send-message.js +1382 -0
  105. package/dist/mcp/tools/update-task.js +1692 -0
  106. package/dist/runtime/index.js +6809 -0
  107. package/dist/tui/App.js +17479 -0
  108. package/package.json +104 -0
  109. package/src/commands/exe/assign.md +17 -0
  110. package/src/commands/exe/build-adv.md +381 -0
  111. package/src/commands/exe/call.md +133 -0
  112. package/src/commands/exe/cloud.md +17 -0
  113. package/src/commands/exe/employee-heartbeat.md +44 -0
  114. package/src/commands/exe/forget.md +15 -0
  115. package/src/commands/exe/heartbeat.md +92 -0
  116. package/src/commands/exe/intercom.md +81 -0
  117. package/src/commands/exe/kill.md +34 -0
  118. package/src/commands/exe/launch.md +52 -0
  119. package/src/commands/exe/link.md +17 -0
  120. package/src/commands/exe/logs.md +22 -0
  121. package/src/commands/exe/new-employee.md +12 -0
  122. package/src/commands/exe/review.md +14 -0
  123. package/src/commands/exe/schedule.md +108 -0
  124. package/src/commands/exe/search.md +13 -0
  125. package/src/commands/exe/sessions.md +25 -0
  126. package/src/commands/exe/settings.md +13 -0
  127. package/src/commands/exe/setup.md +171 -0
  128. package/src/commands/exe/status.md +15 -0
  129. package/src/commands/exe/team.md +11 -0
  130. package/src/commands/exe/update.md +11 -0
  131. package/src/commands/exe.md +181 -0
@@ -0,0 +1,632 @@
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
+ };