@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
package/dist/lib/tasks.js
CHANGED
|
@@ -42,12 +42,25 @@ async function ensurePrivateDir(dirPath) {
|
|
|
42
42
|
} catch {
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
function ensurePrivateDirSync(dirPath) {
|
|
46
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
47
|
+
try {
|
|
48
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
45
52
|
async function enforcePrivateFile(filePath) {
|
|
46
53
|
try {
|
|
47
54
|
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
55
|
} catch {
|
|
49
56
|
}
|
|
50
57
|
}
|
|
58
|
+
function enforcePrivateFileSync(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
52
65
|
var init_secure_files = __esm({
|
|
53
66
|
"src/lib/secure-files.ts"() {
|
|
@@ -58,6 +71,21 @@ var init_secure_files = __esm({
|
|
|
58
71
|
});
|
|
59
72
|
|
|
60
73
|
// src/lib/config.ts
|
|
74
|
+
var config_exports = {};
|
|
75
|
+
__export(config_exports, {
|
|
76
|
+
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
77
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
78
|
+
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
79
|
+
DB_PATH: () => DB_PATH,
|
|
80
|
+
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
81
|
+
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
82
|
+
MODELS_DIR: () => MODELS_DIR,
|
|
83
|
+
loadConfig: () => loadConfig,
|
|
84
|
+
loadConfigFrom: () => loadConfigFrom,
|
|
85
|
+
loadConfigSync: () => loadConfigSync,
|
|
86
|
+
migrateConfig: () => migrateConfig,
|
|
87
|
+
saveConfig: () => saveConfig
|
|
88
|
+
});
|
|
61
89
|
import { readFile, writeFile } from "fs/promises";
|
|
62
90
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
63
91
|
import path from "path";
|
|
@@ -161,6 +189,46 @@ async function loadConfig() {
|
|
|
161
189
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
162
190
|
}
|
|
163
191
|
}
|
|
192
|
+
function loadConfigSync() {
|
|
193
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
194
|
+
const configPath = path.join(dir, "config.json");
|
|
195
|
+
if (!existsSync2(configPath)) {
|
|
196
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
200
|
+
let parsed = JSON.parse(raw);
|
|
201
|
+
parsed = migrateLegacyConfig(parsed);
|
|
202
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
203
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
204
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
205
|
+
normalizeAutoUpdate(migratedCfg);
|
|
206
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
207
|
+
} catch {
|
|
208
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function saveConfig(config) {
|
|
212
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
213
|
+
await ensurePrivateDir(dir);
|
|
214
|
+
const configPath = path.join(dir, "config.json");
|
|
215
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
216
|
+
await enforcePrivateFile(configPath);
|
|
217
|
+
}
|
|
218
|
+
async function loadConfigFrom(configPath) {
|
|
219
|
+
const raw = await readFile(configPath, "utf-8");
|
|
220
|
+
try {
|
|
221
|
+
let parsed = JSON.parse(raw);
|
|
222
|
+
parsed = migrateLegacyConfig(parsed);
|
|
223
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
224
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
225
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
226
|
+
normalizeAutoUpdate(migratedCfg);
|
|
227
|
+
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
228
|
+
} catch {
|
|
229
|
+
return { ...DEFAULT_CONFIG };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
164
232
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
165
233
|
var init_config = __esm({
|
|
166
234
|
"src/lib/config.ts"() {
|
|
@@ -334,13 +402,22 @@ var init_database_adapter = __esm({
|
|
|
334
402
|
}
|
|
335
403
|
});
|
|
336
404
|
|
|
405
|
+
// src/types/memory.ts
|
|
406
|
+
var EMBEDDING_DIM;
|
|
407
|
+
var init_memory = __esm({
|
|
408
|
+
"src/types/memory.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
EMBEDDING_DIM = 1024;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
337
414
|
// src/lib/database.ts
|
|
338
415
|
import { createClient } from "@libsql/client";
|
|
339
416
|
function getClient() {
|
|
340
417
|
if (!_adapterClient) {
|
|
341
418
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
342
419
|
}
|
|
343
|
-
if (process.env.DATABASE_URL) {
|
|
420
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
344
421
|
return _adapterClient;
|
|
345
422
|
}
|
|
346
423
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
@@ -358,6 +435,7 @@ var init_database = __esm({
|
|
|
358
435
|
init_db_retry();
|
|
359
436
|
init_employees();
|
|
360
437
|
init_database_adapter();
|
|
438
|
+
init_memory();
|
|
361
439
|
_resilientClient = null;
|
|
362
440
|
_daemonClient = null;
|
|
363
441
|
_adapterClient = null;
|
|
@@ -711,7 +789,7 @@ var init_agent_config = __esm({
|
|
|
711
789
|
init_secure_files();
|
|
712
790
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
713
791
|
DEFAULT_MODELS = {
|
|
714
|
-
claude: "claude-opus-4",
|
|
792
|
+
claude: "claude-opus-4.6",
|
|
715
793
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
716
794
|
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
717
795
|
};
|
|
@@ -1945,7 +2023,7 @@ function isSessionBusy(sessionName) {
|
|
|
1945
2023
|
function isExeSession(sessionName) {
|
|
1946
2024
|
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
1947
2025
|
const coordinatorName = getCoordinatorName();
|
|
1948
|
-
return matchesBaseWithInstance(coordinatorName)
|
|
2026
|
+
return matchesBaseWithInstance(coordinatorName);
|
|
1949
2027
|
}
|
|
1950
2028
|
function sendIntercom(targetSession) {
|
|
1951
2029
|
const transport = getTransport();
|
|
@@ -2774,15 +2852,20 @@ async function createTaskCore(input) {
|
|
|
2774
2852
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2775
2853
|
const resolved = resolveExeSession2();
|
|
2776
2854
|
if (resolved && input.projectName) {
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
if (sessionProject && sessionProject !== input.projectName) {
|
|
2780
|
-
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2781
|
-
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2782
|
-
`);
|
|
2783
|
-
earlySessionScope = null;
|
|
2784
|
-
} else {
|
|
2855
|
+
const isCoordinatorSession = !resolved.includes("-");
|
|
2856
|
+
if (isCoordinatorSession) {
|
|
2785
2857
|
earlySessionScope = resolved;
|
|
2858
|
+
} else {
|
|
2859
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
2860
|
+
const sessionProject = getSessionProject2(resolved);
|
|
2861
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
2862
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2863
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2864
|
+
`);
|
|
2865
|
+
earlySessionScope = null;
|
|
2866
|
+
} else {
|
|
2867
|
+
earlySessionScope = resolved;
|
|
2868
|
+
}
|
|
2786
2869
|
}
|
|
2787
2870
|
} else {
|
|
2788
2871
|
earlySessionScope = resolved;
|
|
@@ -3359,6 +3442,10 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3359
3442
|
if (transport.isAlive(sessionName)) {
|
|
3360
3443
|
const result = sendIntercom(sessionName);
|
|
3361
3444
|
const dispatched = result === "acknowledged" || result === "debounced" || result === "queued" ? "verified" : result === "delivered" ? "sent_unverified" : "session_dead";
|
|
3445
|
+
process.stderr.write(
|
|
3446
|
+
`[dispatch-audit] intercom \u2192 ${sessionName} | task="${input.title}" [${input.priority}] | result=${dispatched} (sendIntercom=${result})
|
|
3447
|
+
`
|
|
3448
|
+
);
|
|
3362
3449
|
return { dispatched, session: sessionName, crossProject };
|
|
3363
3450
|
} else {
|
|
3364
3451
|
const projectDir = input.projectDir ?? process.cwd();
|
|
@@ -3367,11 +3454,15 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3367
3454
|
});
|
|
3368
3455
|
if (result.status === "failed") {
|
|
3369
3456
|
process.stderr.write(
|
|
3370
|
-
`[dispatch]
|
|
3457
|
+
`[dispatch-audit] SPAWN FAILED \u2192 ${input.assignedTo} | task="${input.title}" [${input.priority}] | error=${result.error}
|
|
3371
3458
|
`
|
|
3372
3459
|
);
|
|
3373
3460
|
return { dispatched: "session_missing" };
|
|
3374
3461
|
}
|
|
3462
|
+
process.stderr.write(
|
|
3463
|
+
`[dispatch-audit] SPAWNED \u2192 ${result.sessionName} | task="${input.title}" [${input.priority}]
|
|
3464
|
+
`
|
|
3465
|
+
);
|
|
3375
3466
|
return { dispatched: "spawned", session: result.sessionName, crossProject };
|
|
3376
3467
|
}
|
|
3377
3468
|
} catch {
|
|
@@ -3381,7 +3472,13 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3381
3472
|
function notifyTaskDone() {
|
|
3382
3473
|
try {
|
|
3383
3474
|
const key = getSessionKey();
|
|
3384
|
-
if (key && !process.env.VITEST)
|
|
3475
|
+
if (key && !process.env.VITEST) {
|
|
3476
|
+
notifyParentExe(key);
|
|
3477
|
+
process.stderr.write(
|
|
3478
|
+
`[dispatch-audit] notifyTaskDone \u2192 parent coordinator (session=${key})
|
|
3479
|
+
`
|
|
3480
|
+
);
|
|
3481
|
+
}
|
|
3385
3482
|
} catch {
|
|
3386
3483
|
}
|
|
3387
3484
|
}
|
|
@@ -3402,16 +3499,557 @@ var init_tasks_notify = __esm({
|
|
|
3402
3499
|
}
|
|
3403
3500
|
});
|
|
3404
3501
|
|
|
3405
|
-
// src/lib/
|
|
3502
|
+
// src/lib/daemon-auth.ts
|
|
3406
3503
|
import crypto4 from "crypto";
|
|
3504
|
+
import path15 from "path";
|
|
3505
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
3506
|
+
function normalizeToken(token) {
|
|
3507
|
+
if (!token) return null;
|
|
3508
|
+
const trimmed = token.trim();
|
|
3509
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3510
|
+
}
|
|
3511
|
+
function readDaemonToken() {
|
|
3512
|
+
try {
|
|
3513
|
+
if (!existsSync13(DAEMON_TOKEN_PATH)) return null;
|
|
3514
|
+
return normalizeToken(readFileSync11(DAEMON_TOKEN_PATH, "utf8"));
|
|
3515
|
+
} catch {
|
|
3516
|
+
return null;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
function ensureDaemonToken(seed) {
|
|
3520
|
+
const existing = readDaemonToken();
|
|
3521
|
+
if (existing) return existing;
|
|
3522
|
+
const token = normalizeToken(seed) ?? crypto4.randomBytes(32).toString("hex");
|
|
3523
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3524
|
+
writeFileSync7(DAEMON_TOKEN_PATH, `${token}
|
|
3525
|
+
`, "utf8");
|
|
3526
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3527
|
+
return token;
|
|
3528
|
+
}
|
|
3529
|
+
var DAEMON_TOKEN_PATH;
|
|
3530
|
+
var init_daemon_auth = __esm({
|
|
3531
|
+
"src/lib/daemon-auth.ts"() {
|
|
3532
|
+
"use strict";
|
|
3533
|
+
init_config();
|
|
3534
|
+
init_secure_files();
|
|
3535
|
+
DAEMON_TOKEN_PATH = path15.join(EXE_AI_DIR, "exed.token");
|
|
3536
|
+
}
|
|
3537
|
+
});
|
|
3538
|
+
|
|
3539
|
+
// src/lib/exe-daemon-client.ts
|
|
3540
|
+
import net from "net";
|
|
3541
|
+
import os10 from "os";
|
|
3542
|
+
import { spawn } from "child_process";
|
|
3543
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3544
|
+
import { existsSync as existsSync14, unlinkSync as unlinkSync5, readFileSync as readFileSync12, openSync, closeSync, statSync } from "fs";
|
|
3545
|
+
import path16 from "path";
|
|
3546
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3547
|
+
function handleData(chunk) {
|
|
3548
|
+
_buffer += chunk.toString();
|
|
3549
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
3550
|
+
_buffer = "";
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
let newlineIdx;
|
|
3554
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
3555
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
3556
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
3557
|
+
if (!line) continue;
|
|
3558
|
+
try {
|
|
3559
|
+
const response = JSON.parse(line);
|
|
3560
|
+
const id = response.id;
|
|
3561
|
+
if (!id) continue;
|
|
3562
|
+
const entry = _pending.get(id);
|
|
3563
|
+
if (entry) {
|
|
3564
|
+
clearTimeout(entry.timer);
|
|
3565
|
+
_pending.delete(id);
|
|
3566
|
+
entry.resolve(response);
|
|
3567
|
+
}
|
|
3568
|
+
} catch {
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
function cleanupStaleFiles() {
|
|
3573
|
+
if (existsSync14(PID_PATH)) {
|
|
3574
|
+
try {
|
|
3575
|
+
const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
|
|
3576
|
+
if (pid > 0) {
|
|
3577
|
+
try {
|
|
3578
|
+
process.kill(pid, 0);
|
|
3579
|
+
return;
|
|
3580
|
+
} catch {
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
} catch {
|
|
3584
|
+
}
|
|
3585
|
+
try {
|
|
3586
|
+
unlinkSync5(PID_PATH);
|
|
3587
|
+
} catch {
|
|
3588
|
+
}
|
|
3589
|
+
try {
|
|
3590
|
+
unlinkSync5(SOCKET_PATH);
|
|
3591
|
+
} catch {
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
function findPackageRoot() {
|
|
3596
|
+
let dir = path16.dirname(fileURLToPath2(import.meta.url));
|
|
3597
|
+
const { root } = path16.parse(dir);
|
|
3598
|
+
while (dir !== root) {
|
|
3599
|
+
if (existsSync14(path16.join(dir, "package.json"))) return dir;
|
|
3600
|
+
dir = path16.dirname(dir);
|
|
3601
|
+
}
|
|
3602
|
+
return null;
|
|
3603
|
+
}
|
|
3604
|
+
function getAvailableMemoryGB() {
|
|
3605
|
+
if (process.platform === "darwin") {
|
|
3606
|
+
try {
|
|
3607
|
+
const { execSync: execSync7 } = __require("child_process");
|
|
3608
|
+
const vmstat = execSync7("vm_stat", { encoding: "utf8" });
|
|
3609
|
+
const pageSize = 16384;
|
|
3610
|
+
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
3611
|
+
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
3612
|
+
const free = vmstat.match(/Pages free:\s+(\d+)/);
|
|
3613
|
+
const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
|
|
3614
|
+
const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
|
|
3615
|
+
const freePages = free ? parseInt(free[1], 10) : 0;
|
|
3616
|
+
const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
|
|
3617
|
+
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
3618
|
+
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
3619
|
+
} catch {
|
|
3620
|
+
return os10.freemem() / (1024 * 1024 * 1024);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
return os10.freemem() / (1024 * 1024 * 1024);
|
|
3624
|
+
}
|
|
3625
|
+
function spawnDaemon() {
|
|
3626
|
+
const freeGB = getAvailableMemoryGB();
|
|
3627
|
+
const totalGB = os10.totalmem() / (1024 * 1024 * 1024);
|
|
3628
|
+
if (totalGB <= 8) {
|
|
3629
|
+
process.stderr.write(
|
|
3630
|
+
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
3631
|
+
`
|
|
3632
|
+
);
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
if (totalGB <= 16 && freeGB < 2) {
|
|
3636
|
+
process.stderr.write(
|
|
3637
|
+
`[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
|
|
3638
|
+
`
|
|
3639
|
+
);
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
const pkgRoot = findPackageRoot();
|
|
3643
|
+
if (!pkgRoot) {
|
|
3644
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
const daemonPath = path16.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3648
|
+
if (!existsSync14(daemonPath)) {
|
|
3649
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
3650
|
+
`);
|
|
3651
|
+
return;
|
|
3652
|
+
}
|
|
3653
|
+
const resolvedPath = daemonPath;
|
|
3654
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
3655
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
3656
|
+
`);
|
|
3657
|
+
const logPath = path16.join(path16.dirname(SOCKET_PATH), "exed.log");
|
|
3658
|
+
let stderrFd = "ignore";
|
|
3659
|
+
try {
|
|
3660
|
+
stderrFd = openSync(logPath, "a");
|
|
3661
|
+
} catch {
|
|
3662
|
+
}
|
|
3663
|
+
const heapCapMB = totalGB <= 8 ? 256 : 512;
|
|
3664
|
+
const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
|
|
3665
|
+
const child = spawn(process.execPath, nodeArgs, {
|
|
3666
|
+
detached: true,
|
|
3667
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
3668
|
+
env: {
|
|
3669
|
+
...process.env,
|
|
3670
|
+
TMUX: void 0,
|
|
3671
|
+
// Daemon is global — must not inherit session scope
|
|
3672
|
+
TMUX_PANE: void 0,
|
|
3673
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
3674
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
3675
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3676
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
3677
|
+
}
|
|
3678
|
+
});
|
|
3679
|
+
child.unref();
|
|
3680
|
+
if (typeof stderrFd === "number") {
|
|
3681
|
+
try {
|
|
3682
|
+
closeSync(stderrFd);
|
|
3683
|
+
} catch {
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function acquireSpawnLock2() {
|
|
3688
|
+
try {
|
|
3689
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
3690
|
+
closeSync(fd);
|
|
3691
|
+
return true;
|
|
3692
|
+
} catch {
|
|
3693
|
+
try {
|
|
3694
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
3695
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
3696
|
+
try {
|
|
3697
|
+
unlinkSync5(SPAWN_LOCK_PATH);
|
|
3698
|
+
} catch {
|
|
3699
|
+
}
|
|
3700
|
+
try {
|
|
3701
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
3702
|
+
closeSync(fd);
|
|
3703
|
+
return true;
|
|
3704
|
+
} catch {
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
} catch {
|
|
3708
|
+
}
|
|
3709
|
+
return false;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
function releaseSpawnLock2() {
|
|
3713
|
+
try {
|
|
3714
|
+
unlinkSync5(SPAWN_LOCK_PATH);
|
|
3715
|
+
} catch {
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
function connectToSocket() {
|
|
3719
|
+
return new Promise((resolve) => {
|
|
3720
|
+
if (_socket && _connected) {
|
|
3721
|
+
resolve(true);
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
3725
|
+
const connectTimeout = setTimeout(() => {
|
|
3726
|
+
socket.destroy();
|
|
3727
|
+
resolve(false);
|
|
3728
|
+
}, 2e3);
|
|
3729
|
+
socket.on("connect", () => {
|
|
3730
|
+
clearTimeout(connectTimeout);
|
|
3731
|
+
_socket = socket;
|
|
3732
|
+
_connected = true;
|
|
3733
|
+
_buffer = "";
|
|
3734
|
+
socket.on("data", handleData);
|
|
3735
|
+
socket.on("close", () => {
|
|
3736
|
+
_connected = false;
|
|
3737
|
+
_socket = null;
|
|
3738
|
+
for (const [id, entry] of _pending) {
|
|
3739
|
+
clearTimeout(entry.timer);
|
|
3740
|
+
_pending.delete(id);
|
|
3741
|
+
entry.resolve({ error: "Connection closed" });
|
|
3742
|
+
}
|
|
3743
|
+
});
|
|
3744
|
+
socket.on("error", () => {
|
|
3745
|
+
_connected = false;
|
|
3746
|
+
_socket = null;
|
|
3747
|
+
});
|
|
3748
|
+
resolve(true);
|
|
3749
|
+
});
|
|
3750
|
+
socket.on("error", () => {
|
|
3751
|
+
clearTimeout(connectTimeout);
|
|
3752
|
+
resolve(false);
|
|
3753
|
+
});
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
async function connectEmbedDaemon() {
|
|
3757
|
+
if (_socket && _connected) return true;
|
|
3758
|
+
if (await connectToSocket()) return true;
|
|
3759
|
+
if (acquireSpawnLock2()) {
|
|
3760
|
+
try {
|
|
3761
|
+
cleanupStaleFiles();
|
|
3762
|
+
spawnDaemon();
|
|
3763
|
+
} finally {
|
|
3764
|
+
releaseSpawnLock2();
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
const start = Date.now();
|
|
3768
|
+
let delay = 100;
|
|
3769
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3770
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
3771
|
+
if (await connectToSocket()) return true;
|
|
3772
|
+
delay = Math.min(delay * 2, 3e3);
|
|
3773
|
+
}
|
|
3774
|
+
return false;
|
|
3775
|
+
}
|
|
3776
|
+
function sendRequest(texts, priority) {
|
|
3777
|
+
return sendDaemonRequest({ texts, priority });
|
|
3778
|
+
}
|
|
3779
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
3780
|
+
return new Promise((resolve) => {
|
|
3781
|
+
if (!_socket || !_connected) {
|
|
3782
|
+
resolve({ error: "Not connected" });
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
const id = randomUUID2();
|
|
3786
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
3787
|
+
const timer = setTimeout(() => {
|
|
3788
|
+
_pending.delete(id);
|
|
3789
|
+
resolve({ error: "Request timeout" });
|
|
3790
|
+
}, timeoutMs);
|
|
3791
|
+
_pending.set(id, { resolve, timer });
|
|
3792
|
+
try {
|
|
3793
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
3794
|
+
} catch {
|
|
3795
|
+
clearTimeout(timer);
|
|
3796
|
+
_pending.delete(id);
|
|
3797
|
+
resolve({ error: "Write failed" });
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
async function pingDaemon() {
|
|
3802
|
+
if (!_socket || !_connected) return null;
|
|
3803
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
3804
|
+
if (response.health) {
|
|
3805
|
+
return response.health;
|
|
3806
|
+
}
|
|
3807
|
+
return null;
|
|
3808
|
+
}
|
|
3809
|
+
function killAndRespawnDaemon() {
|
|
3810
|
+
if (!acquireSpawnLock2()) {
|
|
3811
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
3812
|
+
if (_socket) {
|
|
3813
|
+
_socket.destroy();
|
|
3814
|
+
_socket = null;
|
|
3815
|
+
}
|
|
3816
|
+
_connected = false;
|
|
3817
|
+
_buffer = "";
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
try {
|
|
3821
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3822
|
+
if (existsSync14(PID_PATH)) {
|
|
3823
|
+
try {
|
|
3824
|
+
const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
|
|
3825
|
+
if (pid > 0) {
|
|
3826
|
+
try {
|
|
3827
|
+
process.kill(pid, "SIGKILL");
|
|
3828
|
+
} catch {
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
} catch {
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
if (_socket) {
|
|
3835
|
+
_socket.destroy();
|
|
3836
|
+
_socket = null;
|
|
3837
|
+
}
|
|
3838
|
+
_connected = false;
|
|
3839
|
+
_buffer = "";
|
|
3840
|
+
try {
|
|
3841
|
+
unlinkSync5(PID_PATH);
|
|
3842
|
+
} catch {
|
|
3843
|
+
}
|
|
3844
|
+
try {
|
|
3845
|
+
unlinkSync5(SOCKET_PATH);
|
|
3846
|
+
} catch {
|
|
3847
|
+
}
|
|
3848
|
+
spawnDaemon();
|
|
3849
|
+
} finally {
|
|
3850
|
+
releaseSpawnLock2();
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
function isDaemonTooYoung() {
|
|
3854
|
+
try {
|
|
3855
|
+
const stat = statSync(PID_PATH);
|
|
3856
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
3857
|
+
} catch {
|
|
3858
|
+
return false;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
async function retryThenRestart(doRequest, label) {
|
|
3862
|
+
const result = await doRequest();
|
|
3863
|
+
if (!result.error) {
|
|
3864
|
+
_consecutiveFailures = 0;
|
|
3865
|
+
return result;
|
|
3866
|
+
}
|
|
3867
|
+
_consecutiveFailures++;
|
|
3868
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3869
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3870
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3871
|
+
`);
|
|
3872
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3873
|
+
if (!_connected) {
|
|
3874
|
+
if (!await connectToSocket()) continue;
|
|
3875
|
+
}
|
|
3876
|
+
const retry = await doRequest();
|
|
3877
|
+
if (!retry.error) {
|
|
3878
|
+
_consecutiveFailures = 0;
|
|
3879
|
+
return retry;
|
|
3880
|
+
}
|
|
3881
|
+
_consecutiveFailures++;
|
|
3882
|
+
}
|
|
3883
|
+
if (isDaemonTooYoung()) {
|
|
3884
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3885
|
+
`);
|
|
3886
|
+
return { error: result.error };
|
|
3887
|
+
}
|
|
3888
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3889
|
+
`);
|
|
3890
|
+
killAndRespawnDaemon();
|
|
3891
|
+
const start = Date.now();
|
|
3892
|
+
let delay = 200;
|
|
3893
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3894
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
3895
|
+
if (await connectToSocket()) break;
|
|
3896
|
+
delay = Math.min(delay * 2, 3e3);
|
|
3897
|
+
}
|
|
3898
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3899
|
+
const final = await doRequest();
|
|
3900
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3901
|
+
return final;
|
|
3902
|
+
}
|
|
3903
|
+
async function embedViaClient(text, priority = "high") {
|
|
3904
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
3905
|
+
_requestCount++;
|
|
3906
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
3907
|
+
const health = await pingDaemon();
|
|
3908
|
+
if (!health && !isDaemonTooYoung()) {
|
|
3909
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
3910
|
+
`);
|
|
3911
|
+
killAndRespawnDaemon();
|
|
3912
|
+
const start = Date.now();
|
|
3913
|
+
let d = 200;
|
|
3914
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3915
|
+
await new Promise((r) => setTimeout(r, d));
|
|
3916
|
+
if (await connectToSocket()) break;
|
|
3917
|
+
d = Math.min(d * 2, 3e3);
|
|
3918
|
+
}
|
|
3919
|
+
if (!_connected) return null;
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
const result = await retryThenRestart(
|
|
3923
|
+
() => sendRequest([text], priority),
|
|
3924
|
+
"Embed"
|
|
3925
|
+
);
|
|
3926
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
3927
|
+
}
|
|
3928
|
+
function disconnectClient() {
|
|
3929
|
+
if (_socket) {
|
|
3930
|
+
_socket.destroy();
|
|
3931
|
+
_socket = null;
|
|
3932
|
+
}
|
|
3933
|
+
_connected = false;
|
|
3934
|
+
_buffer = "";
|
|
3935
|
+
for (const [id, entry] of _pending) {
|
|
3936
|
+
clearTimeout(entry.timer);
|
|
3937
|
+
_pending.delete(id);
|
|
3938
|
+
entry.resolve({ error: "Client disconnected" });
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
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;
|
|
3942
|
+
var init_exe_daemon_client = __esm({
|
|
3943
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
3944
|
+
"use strict";
|
|
3945
|
+
init_config();
|
|
3946
|
+
init_daemon_auth();
|
|
3947
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path16.join(EXE_AI_DIR, "exed.sock");
|
|
3948
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path16.join(EXE_AI_DIR, "exed.pid");
|
|
3949
|
+
SPAWN_LOCK_PATH = path16.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3950
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3951
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
3952
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
3953
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3954
|
+
_socket = null;
|
|
3955
|
+
_connected = false;
|
|
3956
|
+
_buffer = "";
|
|
3957
|
+
_requestCount = 0;
|
|
3958
|
+
_consecutiveFailures = 0;
|
|
3959
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
3960
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
3961
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
3962
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3963
|
+
_pending = /* @__PURE__ */ new Map();
|
|
3964
|
+
MAX_BUFFER = 1e7;
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
|
|
3968
|
+
// src/lib/embedder.ts
|
|
3969
|
+
var embedder_exports = {};
|
|
3970
|
+
__export(embedder_exports, {
|
|
3971
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
3972
|
+
embed: () => embed,
|
|
3973
|
+
embedDirect: () => embedDirect,
|
|
3974
|
+
getEmbedder: () => getEmbedder
|
|
3975
|
+
});
|
|
3976
|
+
async function getEmbedder() {
|
|
3977
|
+
const ok = await connectEmbedDaemon();
|
|
3978
|
+
if (!ok) {
|
|
3979
|
+
throw new Error(
|
|
3980
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
3981
|
+
);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
async function embed(text) {
|
|
3985
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
3986
|
+
const vector = await embedViaClient(text, priority);
|
|
3987
|
+
if (!vector) {
|
|
3988
|
+
throw new Error(
|
|
3989
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
3993
|
+
throw new Error(
|
|
3994
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
return vector;
|
|
3998
|
+
}
|
|
3999
|
+
async function disposeEmbedder() {
|
|
4000
|
+
disconnectClient();
|
|
4001
|
+
}
|
|
4002
|
+
async function embedDirect(text) {
|
|
4003
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
4004
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4005
|
+
const { existsSync: existsSync15 } = await import("fs");
|
|
4006
|
+
const path18 = await import("path");
|
|
4007
|
+
const modelPath = path18.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
4008
|
+
if (!existsSync15(modelPath)) {
|
|
4009
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
4010
|
+
}
|
|
4011
|
+
const llama = await llamaCpp.getLlama();
|
|
4012
|
+
const model = await llama.loadModel({ modelPath });
|
|
4013
|
+
const context = await model.createEmbeddingContext();
|
|
4014
|
+
try {
|
|
4015
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
4016
|
+
const vector = Array.from(embedding.vector);
|
|
4017
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
4018
|
+
throw new Error(
|
|
4019
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
4022
|
+
return vector;
|
|
4023
|
+
} finally {
|
|
4024
|
+
await context.dispose();
|
|
4025
|
+
await model.dispose();
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
var init_embedder = __esm({
|
|
4029
|
+
"src/lib/embedder.ts"() {
|
|
4030
|
+
"use strict";
|
|
4031
|
+
init_memory();
|
|
4032
|
+
init_exe_daemon_client();
|
|
4033
|
+
}
|
|
4034
|
+
});
|
|
4035
|
+
|
|
4036
|
+
// src/lib/behaviors.ts
|
|
4037
|
+
import crypto5 from "crypto";
|
|
3407
4038
|
async function storeBehavior(opts) {
|
|
3408
4039
|
const client = getClient();
|
|
3409
|
-
const id =
|
|
4040
|
+
const id = crypto5.randomUUID();
|
|
3410
4041
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4042
|
+
let vector = null;
|
|
4043
|
+
try {
|
|
4044
|
+
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
4045
|
+
const vec = await embed2(opts.content);
|
|
4046
|
+
vector = new Float32Array(vec);
|
|
4047
|
+
} catch {
|
|
4048
|
+
}
|
|
3411
4049
|
await client.execute({
|
|
3412
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
3413
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
3414
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now]
|
|
4050
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
4051
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
4052
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
3415
4053
|
});
|
|
3416
4054
|
return id;
|
|
3417
4055
|
}
|
|
@@ -3435,7 +4073,7 @@ __export(skill_learning_exports, {
|
|
|
3435
4073
|
storeTrajectory: () => storeTrajectory,
|
|
3436
4074
|
sweepTrajectories: () => sweepTrajectories
|
|
3437
4075
|
});
|
|
3438
|
-
import
|
|
4076
|
+
import crypto6 from "crypto";
|
|
3439
4077
|
async function extractTrajectory(taskId, agentId) {
|
|
3440
4078
|
const client = getClient();
|
|
3441
4079
|
const result = await client.execute({
|
|
@@ -3464,11 +4102,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3464
4102
|
return signature;
|
|
3465
4103
|
}
|
|
3466
4104
|
function hashSignature(signature) {
|
|
3467
|
-
return
|
|
4105
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3468
4106
|
}
|
|
3469
4107
|
async function storeTrajectory(opts) {
|
|
3470
4108
|
const client = getClient();
|
|
3471
|
-
const id =
|
|
4109
|
+
const id = crypto6.randomUUID();
|
|
3472
4110
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3473
4111
|
const signatureHash = hashSignature(opts.signature);
|
|
3474
4112
|
await client.execute({
|
|
@@ -3733,8 +4371,8 @@ __export(tasks_exports, {
|
|
|
3733
4371
|
updateTaskStatus: () => updateTaskStatus,
|
|
3734
4372
|
writeCheckpoint: () => writeCheckpoint
|
|
3735
4373
|
});
|
|
3736
|
-
import
|
|
3737
|
-
import { writeFileSync as
|
|
4374
|
+
import path17 from "path";
|
|
4375
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
3738
4376
|
async function createTask(input) {
|
|
3739
4377
|
const result = await createTaskCore(input);
|
|
3740
4378
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3753,14 +4391,14 @@ async function updateTask(input) {
|
|
|
3753
4391
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3754
4392
|
try {
|
|
3755
4393
|
const agent = String(row.assigned_to);
|
|
3756
|
-
const cacheDir =
|
|
3757
|
-
const cachePath =
|
|
4394
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4395
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
3758
4396
|
if (input.status === "in_progress") {
|
|
3759
4397
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3760
|
-
|
|
4398
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3761
4399
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3762
4400
|
try {
|
|
3763
|
-
|
|
4401
|
+
unlinkSync6(cachePath);
|
|
3764
4402
|
} catch {
|
|
3765
4403
|
}
|
|
3766
4404
|
}
|