@askexenow/exe-os 0.9.16 → 0.9.18
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/dist/bin/backfill-conversations.js +1242 -909
- package/dist/bin/backfill-responses.js +1245 -912
- package/dist/bin/backfill-vectors.js +1244 -906
- package/dist/bin/cleanup-stale-review-tasks.js +1870 -417
- package/dist/bin/cli.js +217 -107
- package/dist/bin/exe-agent-config.js +2 -2
- package/dist/bin/exe-agent.js +62 -0
- package/dist/bin/exe-assign.js +346 -10
- package/dist/bin/exe-boot.js +387 -32
- package/dist/bin/exe-call.js +72 -2
- package/dist/bin/exe-cloud.js +8 -0
- package/dist/bin/exe-dispatch.js +1821 -225
- package/dist/bin/exe-doctor.js +720 -52
- package/dist/bin/exe-export-behaviors.js +1429 -148
- package/dist/bin/exe-forget.js +1408 -34
- package/dist/bin/exe-gateway.js +1629 -1295
- package/dist/bin/exe-heartbeat.js +1899 -448
- package/dist/bin/exe-kill.js +1624 -346
- package/dist/bin/exe-launch-agent.js +726 -90
- package/dist/bin/exe-link.js +27 -8
- package/dist/bin/exe-new-employee.js +75 -9
- package/dist/bin/exe-pending-messages.js +2769 -1316
- package/dist/bin/exe-pending-notifications.js +2829 -1376
- package/dist/bin/exe-pending-reviews.js +2847 -1392
- package/dist/bin/exe-rename.js +89 -8
- package/dist/bin/exe-review.js +1494 -312
- package/dist/bin/exe-search.js +1608 -1300
- package/dist/bin/exe-session-cleanup.js +194 -91
- package/dist/bin/exe-settings.js +10 -2
- package/dist/bin/exe-start-codex.js +769 -120
- package/dist/bin/exe-start-opencode.js +763 -108
- package/dist/bin/exe-status.js +1887 -434
- package/dist/bin/exe-team.js +1782 -324
- package/dist/bin/git-sweep.js +408 -33
- package/dist/bin/graph-backfill.js +681 -27
- package/dist/bin/graph-export.js +1419 -141
- package/dist/bin/install.js +4 -8
- package/dist/bin/intercom-check.js +8641 -0
- package/dist/bin/scan-tasks.js +553 -38
- package/dist/bin/setup.js +82 -10
- package/dist/bin/shard-migrate.js +682 -28
- package/dist/gateway/index.js +1629 -1295
- package/dist/hooks/bug-report-worker.js +1136 -183
- package/dist/hooks/codex-stop-task-finalizer.js +1060 -107
- package/dist/hooks/commit-complete.js +408 -33
- package/dist/hooks/error-recall.js +1608 -1300
- package/dist/hooks/ingest-worker.js +250 -7966
- package/dist/hooks/ingest.js +707 -119
- package/dist/hooks/instructions-loaded.js +383 -20
- package/dist/hooks/notification.js +383 -20
- package/dist/hooks/post-compact.js +384 -21
- package/dist/hooks/{response-ingest-worker.js → post-tool-combined.js} +4157 -1716
- package/dist/hooks/pre-compact.js +486 -45
- package/dist/hooks/pre-tool-use.js +385 -22
- package/dist/hooks/prompt-submit.js +291 -96
- package/dist/hooks/session-end.js +490 -48
- package/dist/hooks/session-start.js +236 -22
- package/dist/hooks/stop.js +192 -50
- package/dist/hooks/subagent-stop.js +95 -18
- package/dist/hooks/summary-worker.js +205 -361
- package/dist/index.js +221 -105
- package/dist/lib/agent-config.js +2 -2
- package/dist/lib/cloud-sync.js +27 -8
- package/dist/lib/consolidation.js +437 -41
- package/dist/lib/database.js +20 -8
- package/dist/lib/db-daemon-client.js +2 -2
- package/dist/lib/db.js +20 -8
- package/dist/lib/device-registry.js +27 -8
- package/dist/lib/employee-templates.js +62 -0
- package/dist/lib/employees.js +2 -2
- package/dist/lib/exe-daemon.js +6703 -6259
- package/dist/lib/hybrid-search.js +1608 -1300
- package/dist/lib/identity.js +1 -1
- package/dist/lib/messaging.js +11 -3
- package/dist/lib/reminders.js +1 -1
- package/dist/lib/schedules.js +718 -26
- package/dist/lib/session-registry.js +11 -0
- package/dist/lib/skill-learning.js +639 -9
- package/dist/lib/store.js +676 -27
- package/dist/lib/tasks.js +665 -27
- package/dist/lib/tmux-routing.js +732 -94
- package/dist/lib/token-spend.js +1 -1
- package/dist/mcp/server.js +856 -383
- package/dist/mcp/tools/complete-reminder.js +1 -1
- package/dist/mcp/tools/create-reminder.js +1 -1
- package/dist/mcp/tools/create-task.js +616 -46
- package/dist/mcp/tools/deactivate-behavior.js +9 -1
- package/dist/mcp/tools/list-reminders.js +1 -1
- package/dist/mcp/tools/list-tasks.js +10 -2
- package/dist/mcp/tools/send-message.js +11 -3
- package/dist/mcp/tools/update-task.js +677 -39
- package/dist/runtime/index.js +425 -37
- package/dist/tui/App.js +365 -34
- package/package.json +5 -2
- package/src/commands/exe/intercom.md +6 -17
- package/dist/bin/wiki-sync.js +0 -2991
- package/dist/hooks/prompt-ingest-worker.js +0 -3979
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
2
9
|
var __esm = (fn, res) => function __init() {
|
|
3
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
11
|
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
5
16
|
|
|
6
17
|
// src/lib/secure-files.ts
|
|
7
18
|
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
@@ -13,12 +24,25 @@ async function ensurePrivateDir(dirPath) {
|
|
|
13
24
|
} catch {
|
|
14
25
|
}
|
|
15
26
|
}
|
|
27
|
+
function ensurePrivateDirSync(dirPath) {
|
|
28
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
29
|
+
try {
|
|
30
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
16
34
|
async function enforcePrivateFile(filePath) {
|
|
17
35
|
try {
|
|
18
36
|
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
19
37
|
} catch {
|
|
20
38
|
}
|
|
21
39
|
}
|
|
40
|
+
function enforcePrivateFileSync(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
22
46
|
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
23
47
|
var init_secure_files = __esm({
|
|
24
48
|
"src/lib/secure-files.ts"() {
|
|
@@ -29,6 +53,21 @@ var init_secure_files = __esm({
|
|
|
29
53
|
});
|
|
30
54
|
|
|
31
55
|
// src/lib/config.ts
|
|
56
|
+
var config_exports = {};
|
|
57
|
+
__export(config_exports, {
|
|
58
|
+
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
59
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
60
|
+
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
61
|
+
DB_PATH: () => DB_PATH,
|
|
62
|
+
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
63
|
+
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
64
|
+
MODELS_DIR: () => MODELS_DIR,
|
|
65
|
+
loadConfig: () => loadConfig,
|
|
66
|
+
loadConfigFrom: () => loadConfigFrom,
|
|
67
|
+
loadConfigSync: () => loadConfigSync,
|
|
68
|
+
migrateConfig: () => migrateConfig,
|
|
69
|
+
saveConfig: () => saveConfig
|
|
70
|
+
});
|
|
32
71
|
import { readFile, writeFile } from "fs/promises";
|
|
33
72
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
34
73
|
import path from "path";
|
|
@@ -132,6 +171,46 @@ async function loadConfig() {
|
|
|
132
171
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
133
172
|
}
|
|
134
173
|
}
|
|
174
|
+
function loadConfigSync() {
|
|
175
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
176
|
+
const configPath = path.join(dir, "config.json");
|
|
177
|
+
if (!existsSync2(configPath)) {
|
|
178
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
182
|
+
let parsed = JSON.parse(raw);
|
|
183
|
+
parsed = migrateLegacyConfig(parsed);
|
|
184
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
185
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
186
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
187
|
+
normalizeAutoUpdate(migratedCfg);
|
|
188
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
189
|
+
} catch {
|
|
190
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function saveConfig(config) {
|
|
194
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
195
|
+
await ensurePrivateDir(dir);
|
|
196
|
+
const configPath = path.join(dir, "config.json");
|
|
197
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
198
|
+
await enforcePrivateFile(configPath);
|
|
199
|
+
}
|
|
200
|
+
async function loadConfigFrom(configPath) {
|
|
201
|
+
const raw = await readFile(configPath, "utf-8");
|
|
202
|
+
try {
|
|
203
|
+
let parsed = JSON.parse(raw);
|
|
204
|
+
parsed = migrateLegacyConfig(parsed);
|
|
205
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
206
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
207
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
208
|
+
normalizeAutoUpdate(migratedCfg);
|
|
209
|
+
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
210
|
+
} catch {
|
|
211
|
+
return { ...DEFAULT_CONFIG };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
135
214
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
136
215
|
var init_config = __esm({
|
|
137
216
|
"src/lib/config.ts"() {
|
|
@@ -213,8 +292,551 @@ var init_config = __esm({
|
|
|
213
292
|
}
|
|
214
293
|
});
|
|
215
294
|
|
|
295
|
+
// src/types/memory.ts
|
|
296
|
+
var EMBEDDING_DIM;
|
|
297
|
+
var init_memory = __esm({
|
|
298
|
+
"src/types/memory.ts"() {
|
|
299
|
+
"use strict";
|
|
300
|
+
EMBEDDING_DIM = 1024;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// src/lib/daemon-auth.ts
|
|
305
|
+
import crypto from "crypto";
|
|
306
|
+
import path4 from "path";
|
|
307
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
308
|
+
function normalizeToken(token) {
|
|
309
|
+
if (!token) return null;
|
|
310
|
+
const trimmed = token.trim();
|
|
311
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
312
|
+
}
|
|
313
|
+
function readDaemonToken() {
|
|
314
|
+
try {
|
|
315
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
316
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function ensureDaemonToken(seed) {
|
|
322
|
+
const existing = readDaemonToken();
|
|
323
|
+
if (existing) return existing;
|
|
324
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
325
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
326
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
327
|
+
`, "utf8");
|
|
328
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
329
|
+
return token;
|
|
330
|
+
}
|
|
331
|
+
var DAEMON_TOKEN_PATH;
|
|
332
|
+
var init_daemon_auth = __esm({
|
|
333
|
+
"src/lib/daemon-auth.ts"() {
|
|
334
|
+
"use strict";
|
|
335
|
+
init_config();
|
|
336
|
+
init_secure_files();
|
|
337
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// src/lib/exe-daemon-client.ts
|
|
342
|
+
import net from "net";
|
|
343
|
+
import os4 from "os";
|
|
344
|
+
import { spawn } from "child_process";
|
|
345
|
+
import { randomUUID } from "crypto";
|
|
346
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
347
|
+
import path5 from "path";
|
|
348
|
+
import { fileURLToPath } from "url";
|
|
349
|
+
function handleData(chunk) {
|
|
350
|
+
_buffer += chunk.toString();
|
|
351
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
352
|
+
_buffer = "";
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
let newlineIdx;
|
|
356
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
357
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
358
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
359
|
+
if (!line) continue;
|
|
360
|
+
try {
|
|
361
|
+
const response = JSON.parse(line);
|
|
362
|
+
const id = response.id;
|
|
363
|
+
if (!id) continue;
|
|
364
|
+
const entry = _pending.get(id);
|
|
365
|
+
if (entry) {
|
|
366
|
+
clearTimeout(entry.timer);
|
|
367
|
+
_pending.delete(id);
|
|
368
|
+
entry.resolve(response);
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function cleanupStaleFiles() {
|
|
375
|
+
if (existsSync5(PID_PATH)) {
|
|
376
|
+
try {
|
|
377
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
378
|
+
if (pid > 0) {
|
|
379
|
+
try {
|
|
380
|
+
process.kill(pid, 0);
|
|
381
|
+
return;
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
unlinkSync2(PID_PATH);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
unlinkSync2(SOCKET_PATH);
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function findPackageRoot() {
|
|
398
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
399
|
+
const { root } = path5.parse(dir);
|
|
400
|
+
while (dir !== root) {
|
|
401
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
402
|
+
dir = path5.dirname(dir);
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
function getAvailableMemoryGB() {
|
|
407
|
+
if (process.platform === "darwin") {
|
|
408
|
+
try {
|
|
409
|
+
const { execSync: execSync2 } = __require("child_process");
|
|
410
|
+
const vmstat = execSync2("vm_stat", { encoding: "utf8" });
|
|
411
|
+
const pageSize = 16384;
|
|
412
|
+
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
413
|
+
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
414
|
+
const free = vmstat.match(/Pages free:\s+(\d+)/);
|
|
415
|
+
const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
|
|
416
|
+
const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
|
|
417
|
+
const freePages = free ? parseInt(free[1], 10) : 0;
|
|
418
|
+
const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
|
|
419
|
+
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
420
|
+
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
421
|
+
} catch {
|
|
422
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
426
|
+
}
|
|
427
|
+
function spawnDaemon() {
|
|
428
|
+
const freeGB = getAvailableMemoryGB();
|
|
429
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
430
|
+
if (totalGB <= 8) {
|
|
431
|
+
process.stderr.write(
|
|
432
|
+
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
433
|
+
`
|
|
434
|
+
);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (totalGB <= 16 && freeGB < 2) {
|
|
438
|
+
process.stderr.write(
|
|
439
|
+
`[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
|
|
440
|
+
`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const pkgRoot = findPackageRoot();
|
|
445
|
+
if (!pkgRoot) {
|
|
446
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
450
|
+
if (!existsSync5(daemonPath)) {
|
|
451
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
452
|
+
`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const resolvedPath = daemonPath;
|
|
456
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
457
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
458
|
+
`);
|
|
459
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
460
|
+
let stderrFd = "ignore";
|
|
461
|
+
try {
|
|
462
|
+
stderrFd = openSync(logPath, "a");
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
const heapCapMB = totalGB <= 8 ? 256 : 512;
|
|
466
|
+
const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
|
|
467
|
+
const child = spawn(process.execPath, nodeArgs, {
|
|
468
|
+
detached: true,
|
|
469
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
470
|
+
env: {
|
|
471
|
+
...process.env,
|
|
472
|
+
TMUX: void 0,
|
|
473
|
+
// Daemon is global — must not inherit session scope
|
|
474
|
+
TMUX_PANE: void 0,
|
|
475
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
476
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
477
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
478
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
child.unref();
|
|
482
|
+
if (typeof stderrFd === "number") {
|
|
483
|
+
try {
|
|
484
|
+
closeSync(stderrFd);
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function acquireSpawnLock() {
|
|
490
|
+
try {
|
|
491
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
492
|
+
closeSync(fd);
|
|
493
|
+
return true;
|
|
494
|
+
} catch {
|
|
495
|
+
try {
|
|
496
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
497
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
498
|
+
try {
|
|
499
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
500
|
+
} catch {
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
504
|
+
closeSync(fd);
|
|
505
|
+
return true;
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function releaseSpawnLock() {
|
|
515
|
+
try {
|
|
516
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function connectToSocket() {
|
|
521
|
+
return new Promise((resolve) => {
|
|
522
|
+
if (_socket && _connected) {
|
|
523
|
+
resolve(true);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
527
|
+
const connectTimeout = setTimeout(() => {
|
|
528
|
+
socket.destroy();
|
|
529
|
+
resolve(false);
|
|
530
|
+
}, 2e3);
|
|
531
|
+
socket.on("connect", () => {
|
|
532
|
+
clearTimeout(connectTimeout);
|
|
533
|
+
_socket = socket;
|
|
534
|
+
_connected = true;
|
|
535
|
+
_buffer = "";
|
|
536
|
+
socket.on("data", handleData);
|
|
537
|
+
socket.on("close", () => {
|
|
538
|
+
_connected = false;
|
|
539
|
+
_socket = null;
|
|
540
|
+
for (const [id, entry] of _pending) {
|
|
541
|
+
clearTimeout(entry.timer);
|
|
542
|
+
_pending.delete(id);
|
|
543
|
+
entry.resolve({ error: "Connection closed" });
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
socket.on("error", () => {
|
|
547
|
+
_connected = false;
|
|
548
|
+
_socket = null;
|
|
549
|
+
});
|
|
550
|
+
resolve(true);
|
|
551
|
+
});
|
|
552
|
+
socket.on("error", () => {
|
|
553
|
+
clearTimeout(connectTimeout);
|
|
554
|
+
resolve(false);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
async function connectEmbedDaemon() {
|
|
559
|
+
if (_socket && _connected) return true;
|
|
560
|
+
if (await connectToSocket()) return true;
|
|
561
|
+
if (acquireSpawnLock()) {
|
|
562
|
+
try {
|
|
563
|
+
cleanupStaleFiles();
|
|
564
|
+
spawnDaemon();
|
|
565
|
+
} finally {
|
|
566
|
+
releaseSpawnLock();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const start = Date.now();
|
|
570
|
+
let delay = 100;
|
|
571
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
572
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
573
|
+
if (await connectToSocket()) return true;
|
|
574
|
+
delay = Math.min(delay * 2, 3e3);
|
|
575
|
+
}
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
function sendRequest(texts, priority) {
|
|
579
|
+
return sendDaemonRequest({ texts, priority });
|
|
580
|
+
}
|
|
581
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
582
|
+
return new Promise((resolve) => {
|
|
583
|
+
if (!_socket || !_connected) {
|
|
584
|
+
resolve({ error: "Not connected" });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const id = randomUUID();
|
|
588
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
589
|
+
const timer = setTimeout(() => {
|
|
590
|
+
_pending.delete(id);
|
|
591
|
+
resolve({ error: "Request timeout" });
|
|
592
|
+
}, timeoutMs);
|
|
593
|
+
_pending.set(id, { resolve, timer });
|
|
594
|
+
try {
|
|
595
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
596
|
+
} catch {
|
|
597
|
+
clearTimeout(timer);
|
|
598
|
+
_pending.delete(id);
|
|
599
|
+
resolve({ error: "Write failed" });
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async function pingDaemon() {
|
|
604
|
+
if (!_socket || !_connected) return null;
|
|
605
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
606
|
+
if (response.health) {
|
|
607
|
+
return response.health;
|
|
608
|
+
}
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
function killAndRespawnDaemon() {
|
|
612
|
+
if (!acquireSpawnLock()) {
|
|
613
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
614
|
+
if (_socket) {
|
|
615
|
+
_socket.destroy();
|
|
616
|
+
_socket = null;
|
|
617
|
+
}
|
|
618
|
+
_connected = false;
|
|
619
|
+
_buffer = "";
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
624
|
+
if (existsSync5(PID_PATH)) {
|
|
625
|
+
try {
|
|
626
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
627
|
+
if (pid > 0) {
|
|
628
|
+
try {
|
|
629
|
+
process.kill(pid, "SIGKILL");
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (_socket) {
|
|
637
|
+
_socket.destroy();
|
|
638
|
+
_socket = null;
|
|
639
|
+
}
|
|
640
|
+
_connected = false;
|
|
641
|
+
_buffer = "";
|
|
642
|
+
try {
|
|
643
|
+
unlinkSync2(PID_PATH);
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
unlinkSync2(SOCKET_PATH);
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
spawnDaemon();
|
|
651
|
+
} finally {
|
|
652
|
+
releaseSpawnLock();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function isDaemonTooYoung() {
|
|
656
|
+
try {
|
|
657
|
+
const stat = statSync(PID_PATH);
|
|
658
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
659
|
+
} catch {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function retryThenRestart(doRequest, label) {
|
|
664
|
+
const result = await doRequest();
|
|
665
|
+
if (!result.error) {
|
|
666
|
+
_consecutiveFailures = 0;
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
_consecutiveFailures++;
|
|
670
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
671
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
672
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
673
|
+
`);
|
|
674
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
675
|
+
if (!_connected) {
|
|
676
|
+
if (!await connectToSocket()) continue;
|
|
677
|
+
}
|
|
678
|
+
const retry = await doRequest();
|
|
679
|
+
if (!retry.error) {
|
|
680
|
+
_consecutiveFailures = 0;
|
|
681
|
+
return retry;
|
|
682
|
+
}
|
|
683
|
+
_consecutiveFailures++;
|
|
684
|
+
}
|
|
685
|
+
if (isDaemonTooYoung()) {
|
|
686
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
687
|
+
`);
|
|
688
|
+
return { error: result.error };
|
|
689
|
+
}
|
|
690
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
691
|
+
`);
|
|
692
|
+
killAndRespawnDaemon();
|
|
693
|
+
const start = Date.now();
|
|
694
|
+
let delay = 200;
|
|
695
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
696
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
697
|
+
if (await connectToSocket()) break;
|
|
698
|
+
delay = Math.min(delay * 2, 3e3);
|
|
699
|
+
}
|
|
700
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
701
|
+
const final = await doRequest();
|
|
702
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
703
|
+
return final;
|
|
704
|
+
}
|
|
705
|
+
async function embedViaClient(text, priority = "high") {
|
|
706
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
707
|
+
_requestCount++;
|
|
708
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
709
|
+
const health = await pingDaemon();
|
|
710
|
+
if (!health && !isDaemonTooYoung()) {
|
|
711
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
712
|
+
`);
|
|
713
|
+
killAndRespawnDaemon();
|
|
714
|
+
const start = Date.now();
|
|
715
|
+
let d = 200;
|
|
716
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
717
|
+
await new Promise((r) => setTimeout(r, d));
|
|
718
|
+
if (await connectToSocket()) break;
|
|
719
|
+
d = Math.min(d * 2, 3e3);
|
|
720
|
+
}
|
|
721
|
+
if (!_connected) return null;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const result = await retryThenRestart(
|
|
725
|
+
() => sendRequest([text], priority),
|
|
726
|
+
"Embed"
|
|
727
|
+
);
|
|
728
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
729
|
+
}
|
|
730
|
+
function disconnectClient() {
|
|
731
|
+
if (_socket) {
|
|
732
|
+
_socket.destroy();
|
|
733
|
+
_socket = null;
|
|
734
|
+
}
|
|
735
|
+
_connected = false;
|
|
736
|
+
_buffer = "";
|
|
737
|
+
for (const [id, entry] of _pending) {
|
|
738
|
+
clearTimeout(entry.timer);
|
|
739
|
+
_pending.delete(id);
|
|
740
|
+
entry.resolve({ error: "Client disconnected" });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
744
|
+
var init_exe_daemon_client = __esm({
|
|
745
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
746
|
+
"use strict";
|
|
747
|
+
init_config();
|
|
748
|
+
init_daemon_auth();
|
|
749
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
750
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
751
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
752
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
753
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
754
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
755
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
756
|
+
_socket = null;
|
|
757
|
+
_connected = false;
|
|
758
|
+
_buffer = "";
|
|
759
|
+
_requestCount = 0;
|
|
760
|
+
_consecutiveFailures = 0;
|
|
761
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
762
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
763
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
764
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
765
|
+
_pending = /* @__PURE__ */ new Map();
|
|
766
|
+
MAX_BUFFER = 1e7;
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// src/lib/embedder.ts
|
|
771
|
+
var embedder_exports = {};
|
|
772
|
+
__export(embedder_exports, {
|
|
773
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
774
|
+
embed: () => embed,
|
|
775
|
+
embedDirect: () => embedDirect,
|
|
776
|
+
getEmbedder: () => getEmbedder
|
|
777
|
+
});
|
|
778
|
+
async function getEmbedder() {
|
|
779
|
+
const ok = await connectEmbedDaemon();
|
|
780
|
+
if (!ok) {
|
|
781
|
+
throw new Error(
|
|
782
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
async function embed(text) {
|
|
787
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
788
|
+
const vector = await embedViaClient(text, priority);
|
|
789
|
+
if (!vector) {
|
|
790
|
+
throw new Error(
|
|
791
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
795
|
+
throw new Error(
|
|
796
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return vector;
|
|
800
|
+
}
|
|
801
|
+
async function disposeEmbedder() {
|
|
802
|
+
disconnectClient();
|
|
803
|
+
}
|
|
804
|
+
async function embedDirect(text) {
|
|
805
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
806
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
807
|
+
const { existsSync: existsSync6 } = await import("fs");
|
|
808
|
+
const path6 = await import("path");
|
|
809
|
+
const modelPath = path6.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
810
|
+
if (!existsSync6(modelPath)) {
|
|
811
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
812
|
+
}
|
|
813
|
+
const llama = await llamaCpp.getLlama();
|
|
814
|
+
const model = await llama.loadModel({ modelPath });
|
|
815
|
+
const context = await model.createEmbeddingContext();
|
|
816
|
+
try {
|
|
817
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
818
|
+
const vector = Array.from(embedding.vector);
|
|
819
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
return vector;
|
|
825
|
+
} finally {
|
|
826
|
+
await context.dispose();
|
|
827
|
+
await model.dispose();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
var init_embedder = __esm({
|
|
831
|
+
"src/lib/embedder.ts"() {
|
|
832
|
+
"use strict";
|
|
833
|
+
init_memory();
|
|
834
|
+
init_exe_daemon_client();
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
216
838
|
// src/lib/skill-learning.ts
|
|
217
|
-
import
|
|
839
|
+
import crypto3 from "crypto";
|
|
218
840
|
|
|
219
841
|
// src/lib/database.ts
|
|
220
842
|
import { createClient } from "@libsql/client";
|
|
@@ -245,6 +867,7 @@ var BOOLEAN_COLUMN_NAMES = new Set(
|
|
|
245
867
|
);
|
|
246
868
|
|
|
247
869
|
// src/lib/database.ts
|
|
870
|
+
init_memory();
|
|
248
871
|
var _resilientClient = null;
|
|
249
872
|
var _daemonClient = null;
|
|
250
873
|
var _adapterClient = null;
|
|
@@ -252,7 +875,7 @@ function getClient() {
|
|
|
252
875
|
if (!_adapterClient) {
|
|
253
876
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
254
877
|
}
|
|
255
|
-
if (process.env.DATABASE_URL) {
|
|
878
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
256
879
|
return _adapterClient;
|
|
257
880
|
}
|
|
258
881
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
@@ -265,15 +888,22 @@ function getClient() {
|
|
|
265
888
|
}
|
|
266
889
|
|
|
267
890
|
// src/lib/behaviors.ts
|
|
268
|
-
import
|
|
891
|
+
import crypto2 from "crypto";
|
|
269
892
|
async function storeBehavior(opts) {
|
|
270
893
|
const client = getClient();
|
|
271
|
-
const id =
|
|
894
|
+
const id = crypto2.randomUUID();
|
|
272
895
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
896
|
+
let vector = null;
|
|
897
|
+
try {
|
|
898
|
+
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
899
|
+
const vec = await embed2(opts.content);
|
|
900
|
+
vector = new Float32Array(vec);
|
|
901
|
+
} catch {
|
|
902
|
+
}
|
|
273
903
|
await client.execute({
|
|
274
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
275
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
276
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now]
|
|
904
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
905
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
906
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
277
907
|
});
|
|
278
908
|
return id;
|
|
279
909
|
}
|
|
@@ -309,11 +939,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
309
939
|
return signature;
|
|
310
940
|
}
|
|
311
941
|
function hashSignature(signature) {
|
|
312
|
-
return
|
|
942
|
+
return crypto3.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
313
943
|
}
|
|
314
944
|
async function storeTrajectory(opts) {
|
|
315
945
|
const client = getClient();
|
|
316
|
-
const id =
|
|
946
|
+
const id = crypto3.randomUUID();
|
|
317
947
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
318
948
|
const signatureHash = hashSignature(opts.signature);
|
|
319
949
|
await client.execute({
|