@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
|
@@ -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"() {
|
|
@@ -337,13 +405,22 @@ var init_database_adapter = __esm({
|
|
|
337
405
|
}
|
|
338
406
|
});
|
|
339
407
|
|
|
408
|
+
// src/types/memory.ts
|
|
409
|
+
var EMBEDDING_DIM;
|
|
410
|
+
var init_memory = __esm({
|
|
411
|
+
"src/types/memory.ts"() {
|
|
412
|
+
"use strict";
|
|
413
|
+
EMBEDDING_DIM = 1024;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
340
417
|
// src/lib/database.ts
|
|
341
418
|
import { createClient } from "@libsql/client";
|
|
342
419
|
function getClient() {
|
|
343
420
|
if (!_adapterClient) {
|
|
344
421
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
345
422
|
}
|
|
346
|
-
if (process.env.DATABASE_URL) {
|
|
423
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
347
424
|
return _adapterClient;
|
|
348
425
|
}
|
|
349
426
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
@@ -361,6 +438,7 @@ var init_database = __esm({
|
|
|
361
438
|
init_db_retry();
|
|
362
439
|
init_employees();
|
|
363
440
|
init_database_adapter();
|
|
441
|
+
init_memory();
|
|
364
442
|
_resilientClient = null;
|
|
365
443
|
_daemonClient = null;
|
|
366
444
|
_adapterClient = null;
|
|
@@ -714,7 +792,7 @@ var init_agent_config = __esm({
|
|
|
714
792
|
init_secure_files();
|
|
715
793
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
716
794
|
DEFAULT_MODELS = {
|
|
717
|
-
claude: "claude-opus-4",
|
|
795
|
+
claude: "claude-opus-4.6",
|
|
718
796
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
719
797
|
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
720
798
|
};
|
|
@@ -1948,7 +2026,7 @@ function isSessionBusy(sessionName) {
|
|
|
1948
2026
|
function isExeSession(sessionName) {
|
|
1949
2027
|
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
1950
2028
|
const coordinatorName = getCoordinatorName();
|
|
1951
|
-
return matchesBaseWithInstance(coordinatorName)
|
|
2029
|
+
return matchesBaseWithInstance(coordinatorName);
|
|
1952
2030
|
}
|
|
1953
2031
|
function sendIntercom(targetSession) {
|
|
1954
2032
|
const transport = getTransport();
|
|
@@ -2777,15 +2855,20 @@ async function createTaskCore(input) {
|
|
|
2777
2855
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2778
2856
|
const resolved = resolveExeSession2();
|
|
2779
2857
|
if (resolved && input.projectName) {
|
|
2780
|
-
const
|
|
2781
|
-
|
|
2782
|
-
if (sessionProject && sessionProject !== input.projectName) {
|
|
2783
|
-
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2784
|
-
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2785
|
-
`);
|
|
2786
|
-
earlySessionScope = null;
|
|
2787
|
-
} else {
|
|
2858
|
+
const isCoordinatorSession = !resolved.includes("-");
|
|
2859
|
+
if (isCoordinatorSession) {
|
|
2788
2860
|
earlySessionScope = resolved;
|
|
2861
|
+
} else {
|
|
2862
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
2863
|
+
const sessionProject = getSessionProject2(resolved);
|
|
2864
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
2865
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
2866
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
2867
|
+
`);
|
|
2868
|
+
earlySessionScope = null;
|
|
2869
|
+
} else {
|
|
2870
|
+
earlySessionScope = resolved;
|
|
2871
|
+
}
|
|
2789
2872
|
}
|
|
2790
2873
|
} else {
|
|
2791
2874
|
earlySessionScope = resolved;
|
|
@@ -3362,6 +3445,10 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3362
3445
|
if (transport.isAlive(sessionName)) {
|
|
3363
3446
|
const result = sendIntercom(sessionName);
|
|
3364
3447
|
const dispatched = result === "acknowledged" || result === "debounced" || result === "queued" ? "verified" : result === "delivered" ? "sent_unverified" : "session_dead";
|
|
3448
|
+
process.stderr.write(
|
|
3449
|
+
`[dispatch-audit] intercom \u2192 ${sessionName} | task="${input.title}" [${input.priority}] | result=${dispatched} (sendIntercom=${result})
|
|
3450
|
+
`
|
|
3451
|
+
);
|
|
3365
3452
|
return { dispatched, session: sessionName, crossProject };
|
|
3366
3453
|
} else {
|
|
3367
3454
|
const projectDir = input.projectDir ?? process.cwd();
|
|
@@ -3370,11 +3457,15 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3370
3457
|
});
|
|
3371
3458
|
if (result.status === "failed") {
|
|
3372
3459
|
process.stderr.write(
|
|
3373
|
-
`[dispatch]
|
|
3460
|
+
`[dispatch-audit] SPAWN FAILED \u2192 ${input.assignedTo} | task="${input.title}" [${input.priority}] | error=${result.error}
|
|
3374
3461
|
`
|
|
3375
3462
|
);
|
|
3376
3463
|
return { dispatched: "session_missing" };
|
|
3377
3464
|
}
|
|
3465
|
+
process.stderr.write(
|
|
3466
|
+
`[dispatch-audit] SPAWNED \u2192 ${result.sessionName} | task="${input.title}" [${input.priority}]
|
|
3467
|
+
`
|
|
3468
|
+
);
|
|
3378
3469
|
return { dispatched: "spawned", session: result.sessionName, crossProject };
|
|
3379
3470
|
}
|
|
3380
3471
|
} catch {
|
|
@@ -3384,7 +3475,13 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3384
3475
|
function notifyTaskDone() {
|
|
3385
3476
|
try {
|
|
3386
3477
|
const key = getSessionKey();
|
|
3387
|
-
if (key && !process.env.VITEST)
|
|
3478
|
+
if (key && !process.env.VITEST) {
|
|
3479
|
+
notifyParentExe(key);
|
|
3480
|
+
process.stderr.write(
|
|
3481
|
+
`[dispatch-audit] notifyTaskDone \u2192 parent coordinator (session=${key})
|
|
3482
|
+
`
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3388
3485
|
} catch {
|
|
3389
3486
|
}
|
|
3390
3487
|
}
|
|
@@ -3405,16 +3502,557 @@ var init_tasks_notify = __esm({
|
|
|
3405
3502
|
}
|
|
3406
3503
|
});
|
|
3407
3504
|
|
|
3408
|
-
// src/lib/
|
|
3505
|
+
// src/lib/daemon-auth.ts
|
|
3409
3506
|
import crypto4 from "crypto";
|
|
3507
|
+
import path15 from "path";
|
|
3508
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
3509
|
+
function normalizeToken(token) {
|
|
3510
|
+
if (!token) return null;
|
|
3511
|
+
const trimmed = token.trim();
|
|
3512
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3513
|
+
}
|
|
3514
|
+
function readDaemonToken() {
|
|
3515
|
+
try {
|
|
3516
|
+
if (!existsSync13(DAEMON_TOKEN_PATH)) return null;
|
|
3517
|
+
return normalizeToken(readFileSync11(DAEMON_TOKEN_PATH, "utf8"));
|
|
3518
|
+
} catch {
|
|
3519
|
+
return null;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
function ensureDaemonToken(seed) {
|
|
3523
|
+
const existing = readDaemonToken();
|
|
3524
|
+
if (existing) return existing;
|
|
3525
|
+
const token = normalizeToken(seed) ?? crypto4.randomBytes(32).toString("hex");
|
|
3526
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3527
|
+
writeFileSync7(DAEMON_TOKEN_PATH, `${token}
|
|
3528
|
+
`, "utf8");
|
|
3529
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3530
|
+
return token;
|
|
3531
|
+
}
|
|
3532
|
+
var DAEMON_TOKEN_PATH;
|
|
3533
|
+
var init_daemon_auth = __esm({
|
|
3534
|
+
"src/lib/daemon-auth.ts"() {
|
|
3535
|
+
"use strict";
|
|
3536
|
+
init_config();
|
|
3537
|
+
init_secure_files();
|
|
3538
|
+
DAEMON_TOKEN_PATH = path15.join(EXE_AI_DIR, "exed.token");
|
|
3539
|
+
}
|
|
3540
|
+
});
|
|
3541
|
+
|
|
3542
|
+
// src/lib/exe-daemon-client.ts
|
|
3543
|
+
import net from "net";
|
|
3544
|
+
import os10 from "os";
|
|
3545
|
+
import { spawn } from "child_process";
|
|
3546
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3547
|
+
import { existsSync as existsSync14, unlinkSync as unlinkSync5, readFileSync as readFileSync12, openSync, closeSync, statSync } from "fs";
|
|
3548
|
+
import path16 from "path";
|
|
3549
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3550
|
+
function handleData(chunk) {
|
|
3551
|
+
_buffer += chunk.toString();
|
|
3552
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
3553
|
+
_buffer = "";
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
let newlineIdx;
|
|
3557
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
3558
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
3559
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
3560
|
+
if (!line) continue;
|
|
3561
|
+
try {
|
|
3562
|
+
const response = JSON.parse(line);
|
|
3563
|
+
const id = response.id;
|
|
3564
|
+
if (!id) continue;
|
|
3565
|
+
const entry = _pending.get(id);
|
|
3566
|
+
if (entry) {
|
|
3567
|
+
clearTimeout(entry.timer);
|
|
3568
|
+
_pending.delete(id);
|
|
3569
|
+
entry.resolve(response);
|
|
3570
|
+
}
|
|
3571
|
+
} catch {
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
function cleanupStaleFiles() {
|
|
3576
|
+
if (existsSync14(PID_PATH)) {
|
|
3577
|
+
try {
|
|
3578
|
+
const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
|
|
3579
|
+
if (pid > 0) {
|
|
3580
|
+
try {
|
|
3581
|
+
process.kill(pid, 0);
|
|
3582
|
+
return;
|
|
3583
|
+
} catch {
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
} catch {
|
|
3587
|
+
}
|
|
3588
|
+
try {
|
|
3589
|
+
unlinkSync5(PID_PATH);
|
|
3590
|
+
} catch {
|
|
3591
|
+
}
|
|
3592
|
+
try {
|
|
3593
|
+
unlinkSync5(SOCKET_PATH);
|
|
3594
|
+
} catch {
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
function findPackageRoot() {
|
|
3599
|
+
let dir = path16.dirname(fileURLToPath2(import.meta.url));
|
|
3600
|
+
const { root } = path16.parse(dir);
|
|
3601
|
+
while (dir !== root) {
|
|
3602
|
+
if (existsSync14(path16.join(dir, "package.json"))) return dir;
|
|
3603
|
+
dir = path16.dirname(dir);
|
|
3604
|
+
}
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
function getAvailableMemoryGB() {
|
|
3608
|
+
if (process.platform === "darwin") {
|
|
3609
|
+
try {
|
|
3610
|
+
const { execSync: execSync8 } = __require("child_process");
|
|
3611
|
+
const vmstat = execSync8("vm_stat", { encoding: "utf8" });
|
|
3612
|
+
const pageSize = 16384;
|
|
3613
|
+
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
3614
|
+
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
3615
|
+
const free = vmstat.match(/Pages free:\s+(\d+)/);
|
|
3616
|
+
const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
|
|
3617
|
+
const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
|
|
3618
|
+
const freePages = free ? parseInt(free[1], 10) : 0;
|
|
3619
|
+
const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
|
|
3620
|
+
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
3621
|
+
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
3622
|
+
} catch {
|
|
3623
|
+
return os10.freemem() / (1024 * 1024 * 1024);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
return os10.freemem() / (1024 * 1024 * 1024);
|
|
3627
|
+
}
|
|
3628
|
+
function spawnDaemon() {
|
|
3629
|
+
const freeGB = getAvailableMemoryGB();
|
|
3630
|
+
const totalGB = os10.totalmem() / (1024 * 1024 * 1024);
|
|
3631
|
+
if (totalGB <= 8) {
|
|
3632
|
+
process.stderr.write(
|
|
3633
|
+
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
3634
|
+
`
|
|
3635
|
+
);
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
if (totalGB <= 16 && freeGB < 2) {
|
|
3639
|
+
process.stderr.write(
|
|
3640
|
+
`[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
|
|
3641
|
+
`
|
|
3642
|
+
);
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
const pkgRoot = findPackageRoot();
|
|
3646
|
+
if (!pkgRoot) {
|
|
3647
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
const daemonPath = path16.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3651
|
+
if (!existsSync14(daemonPath)) {
|
|
3652
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
3653
|
+
`);
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const resolvedPath = daemonPath;
|
|
3657
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
3658
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
3659
|
+
`);
|
|
3660
|
+
const logPath = path16.join(path16.dirname(SOCKET_PATH), "exed.log");
|
|
3661
|
+
let stderrFd = "ignore";
|
|
3662
|
+
try {
|
|
3663
|
+
stderrFd = openSync(logPath, "a");
|
|
3664
|
+
} catch {
|
|
3665
|
+
}
|
|
3666
|
+
const heapCapMB = totalGB <= 8 ? 256 : 512;
|
|
3667
|
+
const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
|
|
3668
|
+
const child = spawn(process.execPath, nodeArgs, {
|
|
3669
|
+
detached: true,
|
|
3670
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
3671
|
+
env: {
|
|
3672
|
+
...process.env,
|
|
3673
|
+
TMUX: void 0,
|
|
3674
|
+
// Daemon is global — must not inherit session scope
|
|
3675
|
+
TMUX_PANE: void 0,
|
|
3676
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
3677
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
3678
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3679
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
child.unref();
|
|
3683
|
+
if (typeof stderrFd === "number") {
|
|
3684
|
+
try {
|
|
3685
|
+
closeSync(stderrFd);
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
function acquireSpawnLock2() {
|
|
3691
|
+
try {
|
|
3692
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
3693
|
+
closeSync(fd);
|
|
3694
|
+
return true;
|
|
3695
|
+
} catch {
|
|
3696
|
+
try {
|
|
3697
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
3698
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
3699
|
+
try {
|
|
3700
|
+
unlinkSync5(SPAWN_LOCK_PATH);
|
|
3701
|
+
} catch {
|
|
3702
|
+
}
|
|
3703
|
+
try {
|
|
3704
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
3705
|
+
closeSync(fd);
|
|
3706
|
+
return true;
|
|
3707
|
+
} catch {
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3712
|
+
return false;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
function releaseSpawnLock2() {
|
|
3716
|
+
try {
|
|
3717
|
+
unlinkSync5(SPAWN_LOCK_PATH);
|
|
3718
|
+
} catch {
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
function connectToSocket() {
|
|
3722
|
+
return new Promise((resolve) => {
|
|
3723
|
+
if (_socket && _connected) {
|
|
3724
|
+
resolve(true);
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
3728
|
+
const connectTimeout = setTimeout(() => {
|
|
3729
|
+
socket.destroy();
|
|
3730
|
+
resolve(false);
|
|
3731
|
+
}, 2e3);
|
|
3732
|
+
socket.on("connect", () => {
|
|
3733
|
+
clearTimeout(connectTimeout);
|
|
3734
|
+
_socket = socket;
|
|
3735
|
+
_connected = true;
|
|
3736
|
+
_buffer = "";
|
|
3737
|
+
socket.on("data", handleData);
|
|
3738
|
+
socket.on("close", () => {
|
|
3739
|
+
_connected = false;
|
|
3740
|
+
_socket = null;
|
|
3741
|
+
for (const [id, entry] of _pending) {
|
|
3742
|
+
clearTimeout(entry.timer);
|
|
3743
|
+
_pending.delete(id);
|
|
3744
|
+
entry.resolve({ error: "Connection closed" });
|
|
3745
|
+
}
|
|
3746
|
+
});
|
|
3747
|
+
socket.on("error", () => {
|
|
3748
|
+
_connected = false;
|
|
3749
|
+
_socket = null;
|
|
3750
|
+
});
|
|
3751
|
+
resolve(true);
|
|
3752
|
+
});
|
|
3753
|
+
socket.on("error", () => {
|
|
3754
|
+
clearTimeout(connectTimeout);
|
|
3755
|
+
resolve(false);
|
|
3756
|
+
});
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
async function connectEmbedDaemon() {
|
|
3760
|
+
if (_socket && _connected) return true;
|
|
3761
|
+
if (await connectToSocket()) return true;
|
|
3762
|
+
if (acquireSpawnLock2()) {
|
|
3763
|
+
try {
|
|
3764
|
+
cleanupStaleFiles();
|
|
3765
|
+
spawnDaemon();
|
|
3766
|
+
} finally {
|
|
3767
|
+
releaseSpawnLock2();
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
const start = Date.now();
|
|
3771
|
+
let delay = 100;
|
|
3772
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3773
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
3774
|
+
if (await connectToSocket()) return true;
|
|
3775
|
+
delay = Math.min(delay * 2, 3e3);
|
|
3776
|
+
}
|
|
3777
|
+
return false;
|
|
3778
|
+
}
|
|
3779
|
+
function sendRequest(texts, priority) {
|
|
3780
|
+
return sendDaemonRequest({ texts, priority });
|
|
3781
|
+
}
|
|
3782
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
3783
|
+
return new Promise((resolve) => {
|
|
3784
|
+
if (!_socket || !_connected) {
|
|
3785
|
+
resolve({ error: "Not connected" });
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
const id = randomUUID2();
|
|
3789
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
3790
|
+
const timer = setTimeout(() => {
|
|
3791
|
+
_pending.delete(id);
|
|
3792
|
+
resolve({ error: "Request timeout" });
|
|
3793
|
+
}, timeoutMs);
|
|
3794
|
+
_pending.set(id, { resolve, timer });
|
|
3795
|
+
try {
|
|
3796
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
3797
|
+
} catch {
|
|
3798
|
+
clearTimeout(timer);
|
|
3799
|
+
_pending.delete(id);
|
|
3800
|
+
resolve({ error: "Write failed" });
|
|
3801
|
+
}
|
|
3802
|
+
});
|
|
3803
|
+
}
|
|
3804
|
+
async function pingDaemon() {
|
|
3805
|
+
if (!_socket || !_connected) return null;
|
|
3806
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
3807
|
+
if (response.health) {
|
|
3808
|
+
return response.health;
|
|
3809
|
+
}
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
function killAndRespawnDaemon() {
|
|
3813
|
+
if (!acquireSpawnLock2()) {
|
|
3814
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
3815
|
+
if (_socket) {
|
|
3816
|
+
_socket.destroy();
|
|
3817
|
+
_socket = null;
|
|
3818
|
+
}
|
|
3819
|
+
_connected = false;
|
|
3820
|
+
_buffer = "";
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
try {
|
|
3824
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
3825
|
+
if (existsSync14(PID_PATH)) {
|
|
3826
|
+
try {
|
|
3827
|
+
const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
|
|
3828
|
+
if (pid > 0) {
|
|
3829
|
+
try {
|
|
3830
|
+
process.kill(pid, "SIGKILL");
|
|
3831
|
+
} catch {
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
} catch {
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
if (_socket) {
|
|
3838
|
+
_socket.destroy();
|
|
3839
|
+
_socket = null;
|
|
3840
|
+
}
|
|
3841
|
+
_connected = false;
|
|
3842
|
+
_buffer = "";
|
|
3843
|
+
try {
|
|
3844
|
+
unlinkSync5(PID_PATH);
|
|
3845
|
+
} catch {
|
|
3846
|
+
}
|
|
3847
|
+
try {
|
|
3848
|
+
unlinkSync5(SOCKET_PATH);
|
|
3849
|
+
} catch {
|
|
3850
|
+
}
|
|
3851
|
+
spawnDaemon();
|
|
3852
|
+
} finally {
|
|
3853
|
+
releaseSpawnLock2();
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
function isDaemonTooYoung() {
|
|
3857
|
+
try {
|
|
3858
|
+
const stat = statSync(PID_PATH);
|
|
3859
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
3860
|
+
} catch {
|
|
3861
|
+
return false;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
async function retryThenRestart(doRequest, label) {
|
|
3865
|
+
const result = await doRequest();
|
|
3866
|
+
if (!result.error) {
|
|
3867
|
+
_consecutiveFailures = 0;
|
|
3868
|
+
return result;
|
|
3869
|
+
}
|
|
3870
|
+
_consecutiveFailures++;
|
|
3871
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
3872
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
3873
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
3874
|
+
`);
|
|
3875
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
3876
|
+
if (!_connected) {
|
|
3877
|
+
if (!await connectToSocket()) continue;
|
|
3878
|
+
}
|
|
3879
|
+
const retry = await doRequest();
|
|
3880
|
+
if (!retry.error) {
|
|
3881
|
+
_consecutiveFailures = 0;
|
|
3882
|
+
return retry;
|
|
3883
|
+
}
|
|
3884
|
+
_consecutiveFailures++;
|
|
3885
|
+
}
|
|
3886
|
+
if (isDaemonTooYoung()) {
|
|
3887
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
3888
|
+
`);
|
|
3889
|
+
return { error: result.error };
|
|
3890
|
+
}
|
|
3891
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
3892
|
+
`);
|
|
3893
|
+
killAndRespawnDaemon();
|
|
3894
|
+
const start = Date.now();
|
|
3895
|
+
let delay = 200;
|
|
3896
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3897
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
3898
|
+
if (await connectToSocket()) break;
|
|
3899
|
+
delay = Math.min(delay * 2, 3e3);
|
|
3900
|
+
}
|
|
3901
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
3902
|
+
const final = await doRequest();
|
|
3903
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
3904
|
+
return final;
|
|
3905
|
+
}
|
|
3906
|
+
async function embedViaClient(text, priority = "high") {
|
|
3907
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
3908
|
+
_requestCount++;
|
|
3909
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
3910
|
+
const health = await pingDaemon();
|
|
3911
|
+
if (!health && !isDaemonTooYoung()) {
|
|
3912
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
3913
|
+
`);
|
|
3914
|
+
killAndRespawnDaemon();
|
|
3915
|
+
const start = Date.now();
|
|
3916
|
+
let d = 200;
|
|
3917
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3918
|
+
await new Promise((r) => setTimeout(r, d));
|
|
3919
|
+
if (await connectToSocket()) break;
|
|
3920
|
+
d = Math.min(d * 2, 3e3);
|
|
3921
|
+
}
|
|
3922
|
+
if (!_connected) return null;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
const result = await retryThenRestart(
|
|
3926
|
+
() => sendRequest([text], priority),
|
|
3927
|
+
"Embed"
|
|
3928
|
+
);
|
|
3929
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
3930
|
+
}
|
|
3931
|
+
function disconnectClient() {
|
|
3932
|
+
if (_socket) {
|
|
3933
|
+
_socket.destroy();
|
|
3934
|
+
_socket = null;
|
|
3935
|
+
}
|
|
3936
|
+
_connected = false;
|
|
3937
|
+
_buffer = "";
|
|
3938
|
+
for (const [id, entry] of _pending) {
|
|
3939
|
+
clearTimeout(entry.timer);
|
|
3940
|
+
_pending.delete(id);
|
|
3941
|
+
entry.resolve({ error: "Client disconnected" });
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
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;
|
|
3945
|
+
var init_exe_daemon_client = __esm({
|
|
3946
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
3947
|
+
"use strict";
|
|
3948
|
+
init_config();
|
|
3949
|
+
init_daemon_auth();
|
|
3950
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path16.join(EXE_AI_DIR, "exed.sock");
|
|
3951
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path16.join(EXE_AI_DIR, "exed.pid");
|
|
3952
|
+
SPAWN_LOCK_PATH = path16.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3953
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3954
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
3955
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
3956
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3957
|
+
_socket = null;
|
|
3958
|
+
_connected = false;
|
|
3959
|
+
_buffer = "";
|
|
3960
|
+
_requestCount = 0;
|
|
3961
|
+
_consecutiveFailures = 0;
|
|
3962
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
3963
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
3964
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
3965
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3966
|
+
_pending = /* @__PURE__ */ new Map();
|
|
3967
|
+
MAX_BUFFER = 1e7;
|
|
3968
|
+
}
|
|
3969
|
+
});
|
|
3970
|
+
|
|
3971
|
+
// src/lib/embedder.ts
|
|
3972
|
+
var embedder_exports = {};
|
|
3973
|
+
__export(embedder_exports, {
|
|
3974
|
+
disposeEmbedder: () => disposeEmbedder,
|
|
3975
|
+
embed: () => embed,
|
|
3976
|
+
embedDirect: () => embedDirect,
|
|
3977
|
+
getEmbedder: () => getEmbedder
|
|
3978
|
+
});
|
|
3979
|
+
async function getEmbedder() {
|
|
3980
|
+
const ok = await connectEmbedDaemon();
|
|
3981
|
+
if (!ok) {
|
|
3982
|
+
throw new Error(
|
|
3983
|
+
"Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
|
|
3984
|
+
);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
async function embed(text) {
|
|
3988
|
+
const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
|
|
3989
|
+
const vector = await embedViaClient(text, priority);
|
|
3990
|
+
if (!vector) {
|
|
3991
|
+
throw new Error(
|
|
3992
|
+
"Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
3996
|
+
throw new Error(
|
|
3997
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
return vector;
|
|
4001
|
+
}
|
|
4002
|
+
async function disposeEmbedder() {
|
|
4003
|
+
disconnectClient();
|
|
4004
|
+
}
|
|
4005
|
+
async function embedDirect(text) {
|
|
4006
|
+
const llamaCpp = await import("node-llama-cpp");
|
|
4007
|
+
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4008
|
+
const { existsSync: existsSync15 } = await import("fs");
|
|
4009
|
+
const path19 = await import("path");
|
|
4010
|
+
const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
4011
|
+
if (!existsSync15(modelPath)) {
|
|
4012
|
+
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
4013
|
+
}
|
|
4014
|
+
const llama = await llamaCpp.getLlama();
|
|
4015
|
+
const model = await llama.loadModel({ modelPath });
|
|
4016
|
+
const context = await model.createEmbeddingContext();
|
|
4017
|
+
try {
|
|
4018
|
+
const embedding = await context.getEmbeddingFor(text);
|
|
4019
|
+
const vector = Array.from(embedding.vector);
|
|
4020
|
+
if (vector.length !== EMBEDDING_DIM) {
|
|
4021
|
+
throw new Error(
|
|
4022
|
+
`Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
|
|
4023
|
+
);
|
|
4024
|
+
}
|
|
4025
|
+
return vector;
|
|
4026
|
+
} finally {
|
|
4027
|
+
await context.dispose();
|
|
4028
|
+
await model.dispose();
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
var init_embedder = __esm({
|
|
4032
|
+
"src/lib/embedder.ts"() {
|
|
4033
|
+
"use strict";
|
|
4034
|
+
init_memory();
|
|
4035
|
+
init_exe_daemon_client();
|
|
4036
|
+
}
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
// src/lib/behaviors.ts
|
|
4040
|
+
import crypto5 from "crypto";
|
|
3410
4041
|
async function storeBehavior(opts) {
|
|
3411
4042
|
const client = getClient();
|
|
3412
|
-
const id =
|
|
4043
|
+
const id = crypto5.randomUUID();
|
|
3413
4044
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4045
|
+
let vector = null;
|
|
4046
|
+
try {
|
|
4047
|
+
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
4048
|
+
const vec = await embed2(opts.content);
|
|
4049
|
+
vector = new Float32Array(vec);
|
|
4050
|
+
} catch {
|
|
4051
|
+
}
|
|
3414
4052
|
await client.execute({
|
|
3415
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
3416
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
3417
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now]
|
|
4053
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
4054
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
4055
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
3418
4056
|
});
|
|
3419
4057
|
return id;
|
|
3420
4058
|
}
|
|
@@ -3438,7 +4076,7 @@ __export(skill_learning_exports, {
|
|
|
3438
4076
|
storeTrajectory: () => storeTrajectory,
|
|
3439
4077
|
sweepTrajectories: () => sweepTrajectories
|
|
3440
4078
|
});
|
|
3441
|
-
import
|
|
4079
|
+
import crypto6 from "crypto";
|
|
3442
4080
|
async function extractTrajectory(taskId, agentId) {
|
|
3443
4081
|
const client = getClient();
|
|
3444
4082
|
const result = await client.execute({
|
|
@@ -3467,11 +4105,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3467
4105
|
return signature;
|
|
3468
4106
|
}
|
|
3469
4107
|
function hashSignature(signature) {
|
|
3470
|
-
return
|
|
4108
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3471
4109
|
}
|
|
3472
4110
|
async function storeTrajectory(opts) {
|
|
3473
4111
|
const client = getClient();
|
|
3474
|
-
const id =
|
|
4112
|
+
const id = crypto6.randomUUID();
|
|
3475
4113
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3476
4114
|
const signatureHash = hashSignature(opts.signature);
|
|
3477
4115
|
await client.execute({
|
|
@@ -3736,8 +4374,8 @@ __export(tasks_exports, {
|
|
|
3736
4374
|
updateTaskStatus: () => updateTaskStatus,
|
|
3737
4375
|
writeCheckpoint: () => writeCheckpoint
|
|
3738
4376
|
});
|
|
3739
|
-
import
|
|
3740
|
-
import { writeFileSync as
|
|
4377
|
+
import path17 from "path";
|
|
4378
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
3741
4379
|
async function createTask(input) {
|
|
3742
4380
|
const result = await createTaskCore(input);
|
|
3743
4381
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3756,14 +4394,14 @@ async function updateTask(input) {
|
|
|
3756
4394
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3757
4395
|
try {
|
|
3758
4396
|
const agent = String(row.assigned_to);
|
|
3759
|
-
const cacheDir =
|
|
3760
|
-
const cachePath =
|
|
4397
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4398
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
3761
4399
|
if (input.status === "in_progress") {
|
|
3762
4400
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3763
|
-
|
|
4401
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3764
4402
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3765
4403
|
try {
|
|
3766
|
-
|
|
4404
|
+
unlinkSync6(cachePath);
|
|
3767
4405
|
} catch {
|
|
3768
4406
|
}
|
|
3769
4407
|
}
|
|
@@ -3927,9 +4565,9 @@ __export(active_agent_exports, {
|
|
|
3927
4565
|
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
3928
4566
|
writeActiveAgent: () => writeActiveAgent
|
|
3929
4567
|
});
|
|
3930
|
-
import { readFileSync as
|
|
4568
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync7, readdirSync as readdirSync4 } from "fs";
|
|
3931
4569
|
import { execSync as execSync7 } from "child_process";
|
|
3932
|
-
import
|
|
4570
|
+
import path18 from "path";
|
|
3933
4571
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
3934
4572
|
if (candidate === baseName) return true;
|
|
3935
4573
|
if (!candidate.startsWith(baseName)) return false;
|
|
@@ -3973,12 +4611,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
3973
4611
|
return null;
|
|
3974
4612
|
}
|
|
3975
4613
|
function getMarkerPath() {
|
|
3976
|
-
return
|
|
4614
|
+
return path18.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
3977
4615
|
}
|
|
3978
4616
|
function writeActiveAgent(agentId, agentRole) {
|
|
3979
4617
|
try {
|
|
3980
4618
|
mkdirSync7(CACHE_DIR, { recursive: true });
|
|
3981
|
-
|
|
4619
|
+
writeFileSync9(
|
|
3982
4620
|
getMarkerPath(),
|
|
3983
4621
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
3984
4622
|
);
|
|
@@ -3987,21 +4625,21 @@ function writeActiveAgent(agentId, agentRole) {
|
|
|
3987
4625
|
}
|
|
3988
4626
|
function clearActiveAgent() {
|
|
3989
4627
|
try {
|
|
3990
|
-
|
|
4628
|
+
unlinkSync7(getMarkerPath());
|
|
3991
4629
|
} catch {
|
|
3992
4630
|
}
|
|
3993
4631
|
}
|
|
3994
4632
|
function getActiveAgent() {
|
|
3995
4633
|
try {
|
|
3996
4634
|
const markerPath = getMarkerPath();
|
|
3997
|
-
const raw =
|
|
4635
|
+
const raw = readFileSync13(markerPath, "utf8");
|
|
3998
4636
|
const data = JSON.parse(raw);
|
|
3999
4637
|
if (data.agentId) {
|
|
4000
4638
|
if (data.startedAt) {
|
|
4001
4639
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
4002
4640
|
if (age > STALE_MS) {
|
|
4003
4641
|
try {
|
|
4004
|
-
|
|
4642
|
+
unlinkSync7(markerPath);
|
|
4005
4643
|
} catch {
|
|
4006
4644
|
}
|
|
4007
4645
|
} else {
|
|
@@ -4042,14 +4680,14 @@ function getAllActiveAgents() {
|
|
|
4042
4680
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
4043
4681
|
if (key === "undefined") continue;
|
|
4044
4682
|
try {
|
|
4045
|
-
const raw =
|
|
4683
|
+
const raw = readFileSync13(path18.join(CACHE_DIR, file), "utf8");
|
|
4046
4684
|
const data = JSON.parse(raw);
|
|
4047
4685
|
if (!data.agentId) continue;
|
|
4048
4686
|
if (data.startedAt) {
|
|
4049
4687
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
4050
4688
|
if (age > STALE_MS) {
|
|
4051
4689
|
try {
|
|
4052
|
-
|
|
4690
|
+
unlinkSync7(path18.join(CACHE_DIR, file));
|
|
4053
4691
|
} catch {
|
|
4054
4692
|
}
|
|
4055
4693
|
continue;
|
|
@@ -4072,11 +4710,11 @@ function getAllActiveAgents() {
|
|
|
4072
4710
|
function cleanupSessionMarkers() {
|
|
4073
4711
|
const key = getSessionKey();
|
|
4074
4712
|
try {
|
|
4075
|
-
|
|
4713
|
+
unlinkSync7(path18.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
4076
4714
|
} catch {
|
|
4077
4715
|
}
|
|
4078
4716
|
try {
|
|
4079
|
-
|
|
4717
|
+
unlinkSync7(path18.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
4080
4718
|
} catch {
|
|
4081
4719
|
}
|
|
4082
4720
|
}
|
|
@@ -4087,7 +4725,7 @@ var init_active_agent = __esm({
|
|
|
4087
4725
|
init_config();
|
|
4088
4726
|
init_session_key();
|
|
4089
4727
|
init_employees();
|
|
4090
|
-
CACHE_DIR =
|
|
4728
|
+
CACHE_DIR = path18.join(EXE_AI_DIR, "session-cache");
|
|
4091
4729
|
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4092
4730
|
}
|
|
4093
4731
|
});
|