@askexenow/exe-os 0.8.83 → 0.8.86
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -428,6 +428,539 @@ var init_employees = __esm({
|
|
|
428
428
|
}
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
// src/lib/exe-daemon-client.ts
|
|
432
|
+
import net from "net";
|
|
433
|
+
import { spawn } from "child_process";
|
|
434
|
+
import { randomUUID } from "crypto";
|
|
435
|
+
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
436
|
+
import path3 from "path";
|
|
437
|
+
import { fileURLToPath } from "url";
|
|
438
|
+
function handleData(chunk) {
|
|
439
|
+
_buffer += chunk.toString();
|
|
440
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
441
|
+
_buffer = "";
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
let newlineIdx;
|
|
445
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
446
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
447
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
448
|
+
if (!line) continue;
|
|
449
|
+
try {
|
|
450
|
+
const response = JSON.parse(line);
|
|
451
|
+
const id = response.id;
|
|
452
|
+
if (!id) continue;
|
|
453
|
+
const entry = _pending.get(id);
|
|
454
|
+
if (entry) {
|
|
455
|
+
clearTimeout(entry.timer);
|
|
456
|
+
_pending.delete(id);
|
|
457
|
+
entry.resolve(response);
|
|
458
|
+
}
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function cleanupStaleFiles() {
|
|
464
|
+
if (existsSync3(PID_PATH)) {
|
|
465
|
+
try {
|
|
466
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
467
|
+
if (pid > 0) {
|
|
468
|
+
try {
|
|
469
|
+
process.kill(pid, 0);
|
|
470
|
+
return;
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
unlinkSync2(PID_PATH);
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
unlinkSync2(SOCKET_PATH);
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function findPackageRoot() {
|
|
487
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
488
|
+
const { root } = path3.parse(dir);
|
|
489
|
+
while (dir !== root) {
|
|
490
|
+
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
491
|
+
dir = path3.dirname(dir);
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
function spawnDaemon() {
|
|
496
|
+
const pkgRoot = findPackageRoot();
|
|
497
|
+
if (!pkgRoot) {
|
|
498
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
502
|
+
if (!existsSync3(daemonPath)) {
|
|
503
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
504
|
+
`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const resolvedPath = daemonPath;
|
|
508
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
509
|
+
`);
|
|
510
|
+
const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
|
|
511
|
+
let stderrFd = "ignore";
|
|
512
|
+
try {
|
|
513
|
+
stderrFd = openSync(logPath, "a");
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
517
|
+
detached: true,
|
|
518
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
519
|
+
env: {
|
|
520
|
+
...process.env,
|
|
521
|
+
TMUX: void 0,
|
|
522
|
+
// Daemon is global — must not inherit session scope
|
|
523
|
+
TMUX_PANE: void 0,
|
|
524
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
525
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
526
|
+
EXE_DAEMON_PID: PID_PATH
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
child.unref();
|
|
530
|
+
if (typeof stderrFd === "number") {
|
|
531
|
+
try {
|
|
532
|
+
closeSync(stderrFd);
|
|
533
|
+
} catch {
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function acquireSpawnLock() {
|
|
538
|
+
try {
|
|
539
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
540
|
+
closeSync(fd);
|
|
541
|
+
return true;
|
|
542
|
+
} catch {
|
|
543
|
+
try {
|
|
544
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
545
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
546
|
+
try {
|
|
547
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
552
|
+
closeSync(fd);
|
|
553
|
+
return true;
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function releaseSpawnLock() {
|
|
563
|
+
try {
|
|
564
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
565
|
+
} catch {
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function connectToSocket() {
|
|
569
|
+
return new Promise((resolve) => {
|
|
570
|
+
if (_socket && _connected) {
|
|
571
|
+
resolve(true);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
575
|
+
const connectTimeout = setTimeout(() => {
|
|
576
|
+
socket.destroy();
|
|
577
|
+
resolve(false);
|
|
578
|
+
}, 2e3);
|
|
579
|
+
socket.on("connect", () => {
|
|
580
|
+
clearTimeout(connectTimeout);
|
|
581
|
+
_socket = socket;
|
|
582
|
+
_connected = true;
|
|
583
|
+
_buffer = "";
|
|
584
|
+
socket.on("data", handleData);
|
|
585
|
+
socket.on("close", () => {
|
|
586
|
+
_connected = false;
|
|
587
|
+
_socket = null;
|
|
588
|
+
for (const [id, entry] of _pending) {
|
|
589
|
+
clearTimeout(entry.timer);
|
|
590
|
+
_pending.delete(id);
|
|
591
|
+
entry.resolve({ error: "Connection closed" });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
socket.on("error", () => {
|
|
595
|
+
_connected = false;
|
|
596
|
+
_socket = null;
|
|
597
|
+
});
|
|
598
|
+
resolve(true);
|
|
599
|
+
});
|
|
600
|
+
socket.on("error", () => {
|
|
601
|
+
clearTimeout(connectTimeout);
|
|
602
|
+
resolve(false);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
async function connectEmbedDaemon() {
|
|
607
|
+
if (_socket && _connected) return true;
|
|
608
|
+
if (await connectToSocket()) return true;
|
|
609
|
+
if (acquireSpawnLock()) {
|
|
610
|
+
try {
|
|
611
|
+
cleanupStaleFiles();
|
|
612
|
+
spawnDaemon();
|
|
613
|
+
} finally {
|
|
614
|
+
releaseSpawnLock();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const start = Date.now();
|
|
618
|
+
let delay2 = 100;
|
|
619
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
620
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
621
|
+
if (await connectToSocket()) return true;
|
|
622
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
623
|
+
}
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
function sendRequest(texts, priority) {
|
|
627
|
+
return sendDaemonRequest({ texts, priority });
|
|
628
|
+
}
|
|
629
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
630
|
+
return new Promise((resolve) => {
|
|
631
|
+
if (!_socket || !_connected) {
|
|
632
|
+
resolve({ error: "Not connected" });
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const id = randomUUID();
|
|
636
|
+
const timer = setTimeout(() => {
|
|
637
|
+
_pending.delete(id);
|
|
638
|
+
resolve({ error: "Request timeout" });
|
|
639
|
+
}, timeoutMs);
|
|
640
|
+
_pending.set(id, { resolve, timer });
|
|
641
|
+
try {
|
|
642
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
643
|
+
} catch {
|
|
644
|
+
clearTimeout(timer);
|
|
645
|
+
_pending.delete(id);
|
|
646
|
+
resolve({ error: "Write failed" });
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async function pingDaemon() {
|
|
651
|
+
if (!_socket || !_connected) return null;
|
|
652
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
653
|
+
if (response.health) {
|
|
654
|
+
return response.health;
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
function killAndRespawnDaemon() {
|
|
659
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
660
|
+
if (existsSync3(PID_PATH)) {
|
|
661
|
+
try {
|
|
662
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
663
|
+
if (pid > 0) {
|
|
664
|
+
try {
|
|
665
|
+
process.kill(pid, "SIGKILL");
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (_socket) {
|
|
673
|
+
_socket.destroy();
|
|
674
|
+
_socket = null;
|
|
675
|
+
}
|
|
676
|
+
_connected = false;
|
|
677
|
+
_buffer = "";
|
|
678
|
+
try {
|
|
679
|
+
unlinkSync2(PID_PATH);
|
|
680
|
+
} catch {
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
unlinkSync2(SOCKET_PATH);
|
|
684
|
+
} catch {
|
|
685
|
+
}
|
|
686
|
+
spawnDaemon();
|
|
687
|
+
}
|
|
688
|
+
async function embedViaClient(text, priority = "high") {
|
|
689
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
690
|
+
_requestCount++;
|
|
691
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
692
|
+
const health = await pingDaemon();
|
|
693
|
+
if (!health) {
|
|
694
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
695
|
+
`);
|
|
696
|
+
killAndRespawnDaemon();
|
|
697
|
+
const start = Date.now();
|
|
698
|
+
let delay2 = 200;
|
|
699
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
700
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
701
|
+
if (await connectToSocket()) break;
|
|
702
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
703
|
+
}
|
|
704
|
+
if (!_connected) return null;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const result = await sendRequest([text], priority);
|
|
708
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
709
|
+
if (result.error) {
|
|
710
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
711
|
+
`);
|
|
712
|
+
killAndRespawnDaemon();
|
|
713
|
+
const start = Date.now();
|
|
714
|
+
let delay2 = 200;
|
|
715
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
716
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
717
|
+
if (await connectToSocket()) break;
|
|
718
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
719
|
+
}
|
|
720
|
+
if (!_connected) return null;
|
|
721
|
+
const retry = await sendRequest([text], priority);
|
|
722
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
723
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
724
|
+
`);
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
function disconnectClient() {
|
|
729
|
+
if (_socket) {
|
|
730
|
+
_socket.destroy();
|
|
731
|
+
_socket = null;
|
|
732
|
+
}
|
|
733
|
+
_connected = false;
|
|
734
|
+
_buffer = "";
|
|
735
|
+
for (const [id, entry] of _pending) {
|
|
736
|
+
clearTimeout(entry.timer);
|
|
737
|
+
_pending.delete(id);
|
|
738
|
+
entry.resolve({ error: "Client disconnected" });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function isClientConnected() {
|
|
742
|
+
return _connected;
|
|
743
|
+
}
|
|
744
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
745
|
+
var init_exe_daemon_client = __esm({
|
|
746
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
747
|
+
"use strict";
|
|
748
|
+
init_config();
|
|
749
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
750
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
751
|
+
SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
752
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
753
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
754
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
755
|
+
_socket = null;
|
|
756
|
+
_connected = false;
|
|
757
|
+
_buffer = "";
|
|
758
|
+
_requestCount = 0;
|
|
759
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
760
|
+
_pending = /* @__PURE__ */ new Map();
|
|
761
|
+
MAX_BUFFER = 1e7;
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// src/lib/daemon-protocol.ts
|
|
766
|
+
function serializeValue(v) {
|
|
767
|
+
if (v === null || v === void 0) return null;
|
|
768
|
+
if (typeof v === "bigint") return Number(v);
|
|
769
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
770
|
+
if (v instanceof Uint8Array) {
|
|
771
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
772
|
+
}
|
|
773
|
+
if (ArrayBuffer.isView(v)) {
|
|
774
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
775
|
+
}
|
|
776
|
+
if (v instanceof ArrayBuffer) {
|
|
777
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
778
|
+
}
|
|
779
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
780
|
+
return String(v);
|
|
781
|
+
}
|
|
782
|
+
function deserializeValue(v) {
|
|
783
|
+
if (v === null) return null;
|
|
784
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
785
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
786
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
787
|
+
}
|
|
788
|
+
return v;
|
|
789
|
+
}
|
|
790
|
+
function deserializeResultSet(srs) {
|
|
791
|
+
const rows = srs.rows.map((obj) => {
|
|
792
|
+
const values = srs.columns.map(
|
|
793
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
794
|
+
);
|
|
795
|
+
const row = values;
|
|
796
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
797
|
+
const col = srs.columns[i];
|
|
798
|
+
if (col !== void 0) {
|
|
799
|
+
row[col] = values[i] ?? null;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
Object.defineProperty(row, "length", {
|
|
803
|
+
value: values.length,
|
|
804
|
+
enumerable: false
|
|
805
|
+
});
|
|
806
|
+
return row;
|
|
807
|
+
});
|
|
808
|
+
return {
|
|
809
|
+
columns: srs.columns,
|
|
810
|
+
columnTypes: srs.columnTypes ?? [],
|
|
811
|
+
rows,
|
|
812
|
+
rowsAffected: srs.rowsAffected,
|
|
813
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
814
|
+
toJSON: () => ({
|
|
815
|
+
columns: srs.columns,
|
|
816
|
+
columnTypes: srs.columnTypes ?? [],
|
|
817
|
+
rows: srs.rows,
|
|
818
|
+
rowsAffected: srs.rowsAffected,
|
|
819
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
820
|
+
})
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
var init_daemon_protocol = __esm({
|
|
824
|
+
"src/lib/daemon-protocol.ts"() {
|
|
825
|
+
"use strict";
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// src/lib/db-daemon-client.ts
|
|
830
|
+
var db_daemon_client_exports = {};
|
|
831
|
+
__export(db_daemon_client_exports, {
|
|
832
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
833
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
834
|
+
});
|
|
835
|
+
function normalizeStatement(stmt) {
|
|
836
|
+
if (typeof stmt === "string") {
|
|
837
|
+
return { sql: stmt, args: [] };
|
|
838
|
+
}
|
|
839
|
+
const sql = stmt.sql;
|
|
840
|
+
let args = [];
|
|
841
|
+
if (Array.isArray(stmt.args)) {
|
|
842
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
843
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
844
|
+
const named = {};
|
|
845
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
846
|
+
named[key] = serializeValue(val);
|
|
847
|
+
}
|
|
848
|
+
return { sql, args: named };
|
|
849
|
+
}
|
|
850
|
+
return { sql, args };
|
|
851
|
+
}
|
|
852
|
+
function createDaemonDbClient(fallbackClient) {
|
|
853
|
+
let _useDaemon = false;
|
|
854
|
+
const client = {
|
|
855
|
+
async execute(stmt) {
|
|
856
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
857
|
+
return fallbackClient.execute(stmt);
|
|
858
|
+
}
|
|
859
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
860
|
+
const response = await sendDaemonRequest({
|
|
861
|
+
type: "db-execute",
|
|
862
|
+
sql,
|
|
863
|
+
args
|
|
864
|
+
});
|
|
865
|
+
if (response.error) {
|
|
866
|
+
const errMsg = String(response.error);
|
|
867
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
868
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
869
|
+
`);
|
|
870
|
+
return fallbackClient.execute(stmt);
|
|
871
|
+
}
|
|
872
|
+
throw new Error(errMsg);
|
|
873
|
+
}
|
|
874
|
+
if (response.db) {
|
|
875
|
+
return deserializeResultSet(response.db);
|
|
876
|
+
}
|
|
877
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
878
|
+
return fallbackClient.execute(stmt);
|
|
879
|
+
},
|
|
880
|
+
async batch(stmts, mode) {
|
|
881
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
882
|
+
return fallbackClient.batch(stmts, mode);
|
|
883
|
+
}
|
|
884
|
+
const statements = stmts.map(normalizeStatement);
|
|
885
|
+
const response = await sendDaemonRequest({
|
|
886
|
+
type: "db-batch",
|
|
887
|
+
statements,
|
|
888
|
+
mode: mode ?? "deferred"
|
|
889
|
+
});
|
|
890
|
+
if (response.error) {
|
|
891
|
+
const errMsg = String(response.error);
|
|
892
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
893
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
894
|
+
`);
|
|
895
|
+
return fallbackClient.batch(stmts, mode);
|
|
896
|
+
}
|
|
897
|
+
throw new Error(errMsg);
|
|
898
|
+
}
|
|
899
|
+
const batchResults = response["db-batch"];
|
|
900
|
+
if (batchResults) {
|
|
901
|
+
return batchResults.map(deserializeResultSet);
|
|
902
|
+
}
|
|
903
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
904
|
+
return fallbackClient.batch(stmts, mode);
|
|
905
|
+
},
|
|
906
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
907
|
+
async transaction(mode) {
|
|
908
|
+
return fallbackClient.transaction(mode);
|
|
909
|
+
},
|
|
910
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
911
|
+
async executeMultiple(sql) {
|
|
912
|
+
return fallbackClient.executeMultiple(sql);
|
|
913
|
+
},
|
|
914
|
+
// migrate — delegate to fallback
|
|
915
|
+
async migrate(stmts) {
|
|
916
|
+
return fallbackClient.migrate(stmts);
|
|
917
|
+
},
|
|
918
|
+
// Sync mode — delegate to fallback
|
|
919
|
+
sync() {
|
|
920
|
+
return fallbackClient.sync();
|
|
921
|
+
},
|
|
922
|
+
close() {
|
|
923
|
+
_useDaemon = false;
|
|
924
|
+
},
|
|
925
|
+
get closed() {
|
|
926
|
+
return fallbackClient.closed;
|
|
927
|
+
},
|
|
928
|
+
get protocol() {
|
|
929
|
+
return fallbackClient.protocol;
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
return {
|
|
933
|
+
...client,
|
|
934
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
935
|
+
_enableDaemon() {
|
|
936
|
+
_useDaemon = true;
|
|
937
|
+
},
|
|
938
|
+
/** Check if daemon routing is active */
|
|
939
|
+
_isDaemonActive() {
|
|
940
|
+
return _useDaemon && isClientConnected();
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
945
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
946
|
+
const connected = await connectEmbedDaemon();
|
|
947
|
+
if (!connected) {
|
|
948
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
952
|
+
client._enableDaemon();
|
|
953
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
954
|
+
return client;
|
|
955
|
+
}
|
|
956
|
+
var init_db_daemon_client = __esm({
|
|
957
|
+
"src/lib/db-daemon-client.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
init_exe_daemon_client();
|
|
960
|
+
init_daemon_protocol();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
|
|
431
964
|
// src/lib/database.ts
|
|
432
965
|
var database_exports = {};
|
|
433
966
|
__export(database_exports, {
|
|
@@ -436,6 +969,7 @@ __export(database_exports, {
|
|
|
436
969
|
ensureSchema: () => ensureSchema,
|
|
437
970
|
getClient: () => getClient,
|
|
438
971
|
getRawClient: () => getRawClient,
|
|
972
|
+
initDaemonClient: () => initDaemonClient,
|
|
439
973
|
initDatabase: () => initDatabase,
|
|
440
974
|
initTurso: () => initTurso,
|
|
441
975
|
isInitialized: () => isInitialized
|
|
@@ -463,8 +997,27 @@ function getClient() {
|
|
|
463
997
|
if (!_resilientClient) {
|
|
464
998
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
465
999
|
}
|
|
1000
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1001
|
+
return _resilientClient;
|
|
1002
|
+
}
|
|
1003
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1004
|
+
return _daemonClient;
|
|
1005
|
+
}
|
|
466
1006
|
return _resilientClient;
|
|
467
1007
|
}
|
|
1008
|
+
async function initDaemonClient() {
|
|
1009
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1010
|
+
if (!_resilientClient) return;
|
|
1011
|
+
try {
|
|
1012
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1013
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
process.stderr.write(
|
|
1016
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1017
|
+
`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
468
1021
|
function getRawClient() {
|
|
469
1022
|
if (!_client) {
|
|
470
1023
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -951,6 +1504,12 @@ async function ensureSchema() {
|
|
|
951
1504
|
} catch {
|
|
952
1505
|
}
|
|
953
1506
|
}
|
|
1507
|
+
try {
|
|
1508
|
+
await client.execute(
|
|
1509
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1510
|
+
);
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
954
1513
|
await client.executeMultiple(`
|
|
955
1514
|
CREATE TABLE IF NOT EXISTS entities (
|
|
956
1515
|
id TEXT PRIMARY KEY,
|
|
@@ -1003,7 +1562,30 @@ async function ensureSchema() {
|
|
|
1003
1562
|
entity_id TEXT NOT NULL,
|
|
1004
1563
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1005
1564
|
);
|
|
1565
|
+
|
|
1566
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1567
|
+
name,
|
|
1568
|
+
content=entities,
|
|
1569
|
+
content_rowid=rowid
|
|
1570
|
+
);
|
|
1571
|
+
|
|
1572
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1573
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1574
|
+
END;
|
|
1575
|
+
|
|
1576
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1577
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1578
|
+
END;
|
|
1579
|
+
|
|
1580
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1581
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1582
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1583
|
+
END;
|
|
1006
1584
|
`);
|
|
1585
|
+
try {
|
|
1586
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1587
|
+
} catch {
|
|
1588
|
+
}
|
|
1007
1589
|
await client.executeMultiple(`
|
|
1008
1590
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1009
1591
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1184,6 +1766,33 @@ async function ensureSchema() {
|
|
|
1184
1766
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1185
1767
|
ON conversations(channel_id);
|
|
1186
1768
|
`);
|
|
1769
|
+
await client.executeMultiple(`
|
|
1770
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1771
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1772
|
+
agent_id TEXT NOT NULL,
|
|
1773
|
+
session_name TEXT,
|
|
1774
|
+
task_id TEXT,
|
|
1775
|
+
project_name TEXT,
|
|
1776
|
+
started_at TEXT NOT NULL
|
|
1777
|
+
);
|
|
1778
|
+
|
|
1779
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1780
|
+
ON session_agent_map(agent_id);
|
|
1781
|
+
`);
|
|
1782
|
+
try {
|
|
1783
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1784
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1785
|
+
await client.execute({
|
|
1786
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1787
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1788
|
+
FROM memories
|
|
1789
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1790
|
+
GROUP BY session_id, agent_id`,
|
|
1791
|
+
args: []
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1187
1796
|
try {
|
|
1188
1797
|
await client.execute({
|
|
1189
1798
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1317,15 +1926,41 @@ async function ensureSchema() {
|
|
|
1317
1926
|
});
|
|
1318
1927
|
} catch {
|
|
1319
1928
|
}
|
|
1929
|
+
for (const col of [
|
|
1930
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1931
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1932
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1933
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1934
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1935
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1936
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1937
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1938
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1939
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1940
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1941
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1942
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1943
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1944
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1945
|
+
]) {
|
|
1946
|
+
try {
|
|
1947
|
+
await client.execute(col);
|
|
1948
|
+
} catch {
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1320
1951
|
}
|
|
1321
1952
|
async function disposeDatabase() {
|
|
1953
|
+
if (_daemonClient) {
|
|
1954
|
+
_daemonClient.close();
|
|
1955
|
+
_daemonClient = null;
|
|
1956
|
+
}
|
|
1322
1957
|
if (_client) {
|
|
1323
1958
|
_client.close();
|
|
1324
1959
|
_client = null;
|
|
1325
1960
|
_resilientClient = null;
|
|
1326
1961
|
}
|
|
1327
1962
|
}
|
|
1328
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1963
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1329
1964
|
var init_database = __esm({
|
|
1330
1965
|
"src/lib/database.ts"() {
|
|
1331
1966
|
"use strict";
|
|
@@ -1333,6 +1968,7 @@ var init_database = __esm({
|
|
|
1333
1968
|
init_employees();
|
|
1334
1969
|
_client = null;
|
|
1335
1970
|
_resilientClient = null;
|
|
1971
|
+
_daemonClient = null;
|
|
1336
1972
|
initTurso = initDatabase;
|
|
1337
1973
|
disposeTurso = disposeDatabase;
|
|
1338
1974
|
}
|
|
@@ -1348,14 +1984,14 @@ __export(keychain_exports, {
|
|
|
1348
1984
|
setMasterKey: () => setMasterKey
|
|
1349
1985
|
});
|
|
1350
1986
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1351
|
-
import { existsSync as
|
|
1352
|
-
import
|
|
1987
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1988
|
+
import path4 from "path";
|
|
1353
1989
|
import os3 from "os";
|
|
1354
1990
|
function getKeyDir() {
|
|
1355
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
1991
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os3.homedir(), ".exe-os");
|
|
1356
1992
|
}
|
|
1357
1993
|
function getKeyPath() {
|
|
1358
|
-
return
|
|
1994
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1359
1995
|
}
|
|
1360
1996
|
async function tryKeytar() {
|
|
1361
1997
|
try {
|
|
@@ -1376,13 +2012,21 @@ async function getMasterKey() {
|
|
|
1376
2012
|
}
|
|
1377
2013
|
}
|
|
1378
2014
|
const keyPath = getKeyPath();
|
|
1379
|
-
if (!
|
|
2015
|
+
if (!existsSync4(keyPath)) {
|
|
2016
|
+
process.stderr.write(
|
|
2017
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2018
|
+
`
|
|
2019
|
+
);
|
|
1380
2020
|
return null;
|
|
1381
2021
|
}
|
|
1382
2022
|
try {
|
|
1383
2023
|
const content = await readFile3(keyPath, "utf-8");
|
|
1384
2024
|
return Buffer.from(content.trim(), "base64");
|
|
1385
|
-
} catch {
|
|
2025
|
+
} catch (err) {
|
|
2026
|
+
process.stderr.write(
|
|
2027
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2028
|
+
`
|
|
2029
|
+
);
|
|
1386
2030
|
return null;
|
|
1387
2031
|
}
|
|
1388
2032
|
}
|
|
@@ -1411,7 +2055,7 @@ async function deleteMasterKey() {
|
|
|
1411
2055
|
}
|
|
1412
2056
|
}
|
|
1413
2057
|
const keyPath = getKeyPath();
|
|
1414
|
-
if (
|
|
2058
|
+
if (existsSync4(keyPath)) {
|
|
1415
2059
|
await unlink(keyPath);
|
|
1416
2060
|
}
|
|
1417
2061
|
}
|
|
@@ -1521,12 +2165,12 @@ __export(shard_manager_exports, {
|
|
|
1521
2165
|
listShards: () => listShards,
|
|
1522
2166
|
shardExists: () => shardExists
|
|
1523
2167
|
});
|
|
1524
|
-
import
|
|
1525
|
-
import { existsSync as
|
|
2168
|
+
import path5 from "path";
|
|
2169
|
+
import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
|
|
1526
2170
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1527
2171
|
function initShardManager(encryptionKey) {
|
|
1528
2172
|
_encryptionKey = encryptionKey;
|
|
1529
|
-
if (!
|
|
2173
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1530
2174
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1531
2175
|
}
|
|
1532
2176
|
_shardingEnabled = true;
|
|
@@ -1547,7 +2191,7 @@ function getShardClient(projectName) {
|
|
|
1547
2191
|
}
|
|
1548
2192
|
const cached = _shards.get(safeName);
|
|
1549
2193
|
if (cached) return cached;
|
|
1550
|
-
const dbPath =
|
|
2194
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1551
2195
|
const client = createClient2({
|
|
1552
2196
|
url: `file:${dbPath}`,
|
|
1553
2197
|
encryptionKey: _encryptionKey
|
|
@@ -1557,10 +2201,10 @@ function getShardClient(projectName) {
|
|
|
1557
2201
|
}
|
|
1558
2202
|
function shardExists(projectName) {
|
|
1559
2203
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1560
|
-
return
|
|
2204
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1561
2205
|
}
|
|
1562
2206
|
function listShards() {
|
|
1563
|
-
if (!
|
|
2207
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1564
2208
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1565
2209
|
}
|
|
1566
2210
|
async function ensureShardSchema(client) {
|
|
@@ -1746,7 +2390,7 @@ var init_shard_manager = __esm({
|
|
|
1746
2390
|
"src/lib/shard-manager.ts"() {
|
|
1747
2391
|
"use strict";
|
|
1748
2392
|
init_config();
|
|
1749
|
-
SHARDS_DIR =
|
|
2393
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
1750
2394
|
_shards = /* @__PURE__ */ new Map();
|
|
1751
2395
|
_encryptionKey = null;
|
|
1752
2396
|
_shardingEnabled = false;
|
|
@@ -1871,7 +2515,7 @@ __export(global_procedures_exports, {
|
|
|
1871
2515
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
1872
2516
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
1873
2517
|
});
|
|
1874
|
-
import { randomUUID } from "crypto";
|
|
2518
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1875
2519
|
async function loadGlobalProcedures() {
|
|
1876
2520
|
const client = getClient();
|
|
1877
2521
|
const result = await client.execute({
|
|
@@ -1900,7 +2544,7 @@ ${sections.join("\n\n")}
|
|
|
1900
2544
|
`;
|
|
1901
2545
|
}
|
|
1902
2546
|
async function storeGlobalProcedure(input) {
|
|
1903
|
-
const id =
|
|
2547
|
+
const id = randomUUID2();
|
|
1904
2548
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1905
2549
|
const client = getClient();
|
|
1906
2550
|
await client.execute({
|
|
@@ -1936,13 +2580,13 @@ ${p.content}`).join("\n\n");
|
|
|
1936
2580
|
|
|
1937
2581
|
// src/lib/notifications.ts
|
|
1938
2582
|
import crypto from "crypto";
|
|
1939
|
-
import
|
|
2583
|
+
import path6 from "path";
|
|
1940
2584
|
import os4 from "os";
|
|
1941
2585
|
import {
|
|
1942
|
-
readFileSync as
|
|
2586
|
+
readFileSync as readFileSync4,
|
|
1943
2587
|
readdirSync as readdirSync2,
|
|
1944
|
-
unlinkSync as
|
|
1945
|
-
existsSync as
|
|
2588
|
+
unlinkSync as unlinkSync3,
|
|
2589
|
+
existsSync as existsSync6,
|
|
1946
2590
|
rmdirSync
|
|
1947
2591
|
} from "fs";
|
|
1948
2592
|
async function writeNotification(notification) {
|
|
@@ -1977,13 +2621,13 @@ var init_notifications = __esm({
|
|
|
1977
2621
|
});
|
|
1978
2622
|
|
|
1979
2623
|
// src/lib/session-registry.ts
|
|
1980
|
-
import
|
|
2624
|
+
import path7 from "path";
|
|
1981
2625
|
import os5 from "os";
|
|
1982
2626
|
var REGISTRY_PATH;
|
|
1983
2627
|
var init_session_registry = __esm({
|
|
1984
2628
|
"src/lib/session-registry.ts"() {
|
|
1985
2629
|
"use strict";
|
|
1986
|
-
REGISTRY_PATH =
|
|
2630
|
+
REGISTRY_PATH = path7.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
1987
2631
|
}
|
|
1988
2632
|
});
|
|
1989
2633
|
|
|
@@ -2158,17 +2802,53 @@ var init_provider_table = __esm({
|
|
|
2158
2802
|
}
|
|
2159
2803
|
});
|
|
2160
2804
|
|
|
2805
|
+
// src/lib/runtime-table.ts
|
|
2806
|
+
var RUNTIME_TABLE;
|
|
2807
|
+
var init_runtime_table = __esm({
|
|
2808
|
+
"src/lib/runtime-table.ts"() {
|
|
2809
|
+
"use strict";
|
|
2810
|
+
RUNTIME_TABLE = {
|
|
2811
|
+
codex: {
|
|
2812
|
+
binary: "codex",
|
|
2813
|
+
launchMode: "exec",
|
|
2814
|
+
autoApproveFlag: "--full-auto",
|
|
2815
|
+
inlineFlag: "--no-alt-screen",
|
|
2816
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
2817
|
+
defaultModel: "gpt-5.4"
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
// src/lib/agent-config.ts
|
|
2824
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
2825
|
+
import path8 from "path";
|
|
2826
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
2827
|
+
var init_agent_config = __esm({
|
|
2828
|
+
"src/lib/agent-config.ts"() {
|
|
2829
|
+
"use strict";
|
|
2830
|
+
init_config();
|
|
2831
|
+
init_runtime_table();
|
|
2832
|
+
AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
|
|
2833
|
+
DEFAULT_MODELS = {
|
|
2834
|
+
claude: "claude-opus-4",
|
|
2835
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
2836
|
+
opencode: "minimax-m2.7"
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
|
|
2161
2841
|
// src/lib/intercom-queue.ts
|
|
2162
|
-
import { readFileSync as
|
|
2163
|
-
import
|
|
2842
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
2843
|
+
import path9 from "path";
|
|
2164
2844
|
import os6 from "os";
|
|
2165
2845
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
2166
2846
|
var init_intercom_queue = __esm({
|
|
2167
2847
|
"src/lib/intercom-queue.ts"() {
|
|
2168
2848
|
"use strict";
|
|
2169
|
-
QUEUE_PATH =
|
|
2849
|
+
QUEUE_PATH = path9.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
2170
2850
|
TTL_MS = 60 * 60 * 1e3;
|
|
2171
|
-
INTERCOM_LOG =
|
|
2851
|
+
INTERCOM_LOG = path9.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
2172
2852
|
}
|
|
2173
2853
|
});
|
|
2174
2854
|
|
|
@@ -2189,923 +2869,580 @@ __export(license_exports, {
|
|
|
2189
2869
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2190
2870
|
validateLicense: () => validateLicense
|
|
2191
2871
|
});
|
|
2192
|
-
import { readFileSync as
|
|
2193
|
-
import { randomUUID as
|
|
2194
|
-
import
|
|
2872
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2873
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2874
|
+
import path10 from "path";
|
|
2195
2875
|
import { jwtVerify, importSPKI } from "jose";
|
|
2196
2876
|
async function fetchRetry(url, init) {
|
|
2197
|
-
try {
|
|
2198
|
-
return await fetch(url, init);
|
|
2199
|
-
} catch {
|
|
2200
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
2201
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
function loadDeviceId() {
|
|
2205
|
-
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
2206
|
-
try {
|
|
2207
|
-
if (existsSync7(deviceJsonPath)) {
|
|
2208
|
-
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2209
|
-
if (data.deviceId) return data.deviceId;
|
|
2210
|
-
}
|
|
2211
|
-
} catch {
|
|
2212
|
-
}
|
|
2213
|
-
try {
|
|
2214
|
-
if (existsSync7(DEVICE_ID_PATH)) {
|
|
2215
|
-
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2216
|
-
if (id2) return id2;
|
|
2217
|
-
}
|
|
2218
|
-
} catch {
|
|
2219
|
-
}
|
|
2220
|
-
const id = randomUUID2();
|
|
2221
|
-
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2222
|
-
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2223
|
-
return id;
|
|
2224
|
-
}
|
|
2225
|
-
function loadLicense() {
|
|
2226
|
-
try {
|
|
2227
|
-
if (!existsSync7(LICENSE_PATH)) return null;
|
|
2228
|
-
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
2229
|
-
} catch {
|
|
2230
|
-
return null;
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
function saveLicense(apiKey) {
|
|
2234
|
-
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2235
|
-
writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2236
|
-
}
|
|
2237
|
-
async function verifyLicenseJwt(token) {
|
|
2238
|
-
try {
|
|
2239
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2240
|
-
const { payload } = await jwtVerify(token, key, {
|
|
2241
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
2242
|
-
});
|
|
2243
|
-
const plan = payload.plan ?? "free";
|
|
2244
|
-
const email = payload.sub ?? "";
|
|
2245
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2246
|
-
return {
|
|
2247
|
-
valid: true,
|
|
2248
|
-
plan,
|
|
2249
|
-
email,
|
|
2250
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2251
|
-
deviceLimit: limits.devices,
|
|
2252
|
-
employeeLimit: limits.employees,
|
|
2253
|
-
memoryLimit: limits.memories
|
|
2254
|
-
};
|
|
2255
|
-
} catch {
|
|
2256
|
-
return null;
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
async function getCachedLicense() {
|
|
2260
|
-
try {
|
|
2261
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
2262
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2263
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2264
|
-
return await verifyLicenseJwt(raw.token);
|
|
2265
|
-
} catch {
|
|
2266
|
-
return null;
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
function readCachedToken() {
|
|
2270
|
-
try {
|
|
2271
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
2272
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2273
|
-
return typeof raw.token === "string" ? raw.token : null;
|
|
2274
|
-
} catch {
|
|
2275
|
-
return null;
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
function getRawCachedPlan() {
|
|
2279
|
-
try {
|
|
2280
|
-
const token = readCachedToken();
|
|
2281
|
-
if (!token) return null;
|
|
2282
|
-
const parts = token.split(".");
|
|
2283
|
-
if (parts.length !== 3) return null;
|
|
2284
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2285
|
-
const plan = payload.plan ?? "free";
|
|
2286
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2287
|
-
process.stderr.write(
|
|
2288
|
-
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2289
|
-
`
|
|
2290
|
-
);
|
|
2291
|
-
return {
|
|
2292
|
-
valid: true,
|
|
2293
|
-
plan,
|
|
2294
|
-
email: payload.sub ?? "",
|
|
2295
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2296
|
-
deviceLimit: limits.devices,
|
|
2297
|
-
employeeLimit: limits.employees,
|
|
2298
|
-
memoryLimit: limits.memories
|
|
2299
|
-
};
|
|
2300
|
-
} catch {
|
|
2301
|
-
return null;
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
function cacheResponse(token) {
|
|
2305
|
-
try {
|
|
2306
|
-
writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2307
|
-
} catch {
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
async function validateLicense(apiKey, deviceId) {
|
|
2311
|
-
const did = deviceId ?? loadDeviceId();
|
|
2312
|
-
try {
|
|
2313
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2314
|
-
method: "POST",
|
|
2315
|
-
headers: { "Content-Type": "application/json" },
|
|
2316
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2317
|
-
signal: AbortSignal.timeout(1e4)
|
|
2318
|
-
});
|
|
2319
|
-
if (res.ok) {
|
|
2320
|
-
const data = await res.json();
|
|
2321
|
-
if (data.error === "device_limit_exceeded") {
|
|
2322
|
-
const cached2 = await getCachedLicense();
|
|
2323
|
-
if (cached2) return cached2;
|
|
2324
|
-
const raw2 = getRawCachedPlan();
|
|
2325
|
-
if (raw2) return { ...raw2, valid: false };
|
|
2326
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2327
|
-
}
|
|
2328
|
-
if (data.token) {
|
|
2329
|
-
cacheResponse(data.token);
|
|
2330
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
2331
|
-
if (verified) return verified;
|
|
2332
|
-
}
|
|
2333
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2334
|
-
return {
|
|
2335
|
-
valid: data.valid,
|
|
2336
|
-
plan: data.plan,
|
|
2337
|
-
email: data.email,
|
|
2338
|
-
expiresAt: data.expiresAt,
|
|
2339
|
-
deviceLimit: limits.devices,
|
|
2340
|
-
employeeLimit: limits.employees,
|
|
2341
|
-
memoryLimit: limits.memories
|
|
2342
|
-
};
|
|
2343
|
-
}
|
|
2344
|
-
const cached = await getCachedLicense();
|
|
2345
|
-
if (cached) return cached;
|
|
2346
|
-
const raw = getRawCachedPlan();
|
|
2347
|
-
if (raw) return raw;
|
|
2348
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2349
|
-
} catch {
|
|
2350
|
-
const cached = await getCachedLicense();
|
|
2351
|
-
if (cached) return cached;
|
|
2352
|
-
const rawFallback = getRawCachedPlan();
|
|
2353
|
-
if (rawFallback) return rawFallback;
|
|
2354
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
function getCacheAgeMs() {
|
|
2358
|
-
try {
|
|
2359
|
-
const { statSync: statSync2 } = __require("fs");
|
|
2360
|
-
const s = statSync2(CACHE_PATH);
|
|
2361
|
-
return Date.now() - s.mtimeMs;
|
|
2362
|
-
} catch {
|
|
2363
|
-
return Infinity;
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
async function checkLicense() {
|
|
2367
|
-
let key = loadLicense();
|
|
2368
|
-
if (!key) {
|
|
2369
|
-
try {
|
|
2370
|
-
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
2371
|
-
if (existsSync7(configPath)) {
|
|
2372
|
-
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
2373
|
-
const cloud = raw.cloud;
|
|
2374
|
-
if (cloud?.apiKey) {
|
|
2375
|
-
key = cloud.apiKey;
|
|
2376
|
-
saveLicense(key);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
} catch {
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
if (!key) return FREE_LICENSE;
|
|
2383
|
-
const cached = await getCachedLicense();
|
|
2384
|
-
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
2385
|
-
const deviceId = loadDeviceId();
|
|
2386
|
-
return validateLicense(key, deviceId);
|
|
2387
|
-
}
|
|
2388
|
-
function isFeatureAllowed(license, feature) {
|
|
2389
|
-
switch (feature) {
|
|
2390
|
-
case "cloud_sync":
|
|
2391
|
-
case "external_agents":
|
|
2392
|
-
case "wiki":
|
|
2393
|
-
return license.plan !== "free";
|
|
2394
|
-
case "unlimited_employees":
|
|
2395
|
-
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
function mirrorLicenseKey(apiKey) {
|
|
2399
|
-
const trimmed = apiKey.trim();
|
|
2400
|
-
if (!trimmed) return;
|
|
2401
|
-
saveLicense(trimmed);
|
|
2402
|
-
}
|
|
2403
|
-
async function assertVpsLicense(opts) {
|
|
2404
|
-
const env = opts?.env ?? process.env;
|
|
2405
|
-
const inProduction = env.NODE_ENV === "production";
|
|
2406
|
-
if (!opts?.force && !inProduction) {
|
|
2407
|
-
return { ...FREE_LICENSE, plan: "free" };
|
|
2408
|
-
}
|
|
2409
|
-
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
2410
|
-
if (envKey) {
|
|
2411
|
-
saveLicense(envKey);
|
|
2412
|
-
}
|
|
2413
|
-
const apiKey = envKey ?? loadLicense();
|
|
2414
|
-
if (!apiKey) {
|
|
2415
|
-
throw new Error(
|
|
2416
|
-
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
2417
|
-
);
|
|
2418
|
-
}
|
|
2419
|
-
const deviceId = loadDeviceId();
|
|
2420
|
-
let backendResponse = null;
|
|
2421
|
-
let explicitRejection = false;
|
|
2422
|
-
let transientFailure = false;
|
|
2423
|
-
try {
|
|
2424
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2425
|
-
method: "POST",
|
|
2426
|
-
headers: { "Content-Type": "application/json" },
|
|
2427
|
-
body: JSON.stringify({ apiKey, deviceId }),
|
|
2428
|
-
signal: AbortSignal.timeout(1e4)
|
|
2429
|
-
});
|
|
2430
|
-
if (res.ok) {
|
|
2431
|
-
backendResponse = await res.json();
|
|
2432
|
-
if (!backendResponse.valid) explicitRejection = true;
|
|
2433
|
-
} else if (res.status === 401 || res.status === 403) {
|
|
2434
|
-
explicitRejection = true;
|
|
2435
|
-
} else {
|
|
2436
|
-
transientFailure = true;
|
|
2437
|
-
}
|
|
2438
|
-
} catch {
|
|
2439
|
-
transientFailure = true;
|
|
2440
|
-
}
|
|
2441
|
-
if (backendResponse?.valid) {
|
|
2442
|
-
if (backendResponse.token) {
|
|
2443
|
-
cacheResponse(backendResponse.token);
|
|
2444
|
-
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
2445
|
-
if (verified) return verified;
|
|
2446
|
-
}
|
|
2447
|
-
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
2448
|
-
return {
|
|
2449
|
-
valid: true,
|
|
2450
|
-
plan: backendResponse.plan,
|
|
2451
|
-
email: backendResponse.email,
|
|
2452
|
-
expiresAt: backendResponse.expiresAt,
|
|
2453
|
-
deviceLimit: limits.devices,
|
|
2454
|
-
employeeLimit: limits.employees,
|
|
2455
|
-
memoryLimit: limits.memories
|
|
2456
|
-
};
|
|
2457
|
-
}
|
|
2458
|
-
if (explicitRejection) {
|
|
2459
|
-
throw new Error(
|
|
2460
|
-
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
2461
|
-
);
|
|
2462
|
-
}
|
|
2463
|
-
if (!transientFailure) {
|
|
2464
|
-
throw new Error(
|
|
2465
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
2466
|
-
);
|
|
2877
|
+
try {
|
|
2878
|
+
return await fetch(url, init);
|
|
2879
|
+
} catch {
|
|
2880
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
2881
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
2467
2882
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const
|
|
2471
|
-
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
2883
|
+
}
|
|
2884
|
+
function loadDeviceId() {
|
|
2885
|
+
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
2472
2886
|
try {
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
if (payloadB64) {
|
|
2477
|
-
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
2478
|
-
const expMs = (payload.exp ?? 0) * 1e3;
|
|
2479
|
-
if (Date.now() < expMs + graceMs) {
|
|
2480
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2481
|
-
const { payload: verified } = await jwtVerify(token, key, {
|
|
2482
|
-
algorithms: [LICENSE_JWT_ALG],
|
|
2483
|
-
clockTolerance: graceDays * 24 * 60 * 60
|
|
2484
|
-
});
|
|
2485
|
-
const plan = verified.plan ?? "free";
|
|
2486
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2487
|
-
return {
|
|
2488
|
-
valid: true,
|
|
2489
|
-
plan,
|
|
2490
|
-
email: verified.sub ?? "",
|
|
2491
|
-
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
2492
|
-
deviceLimit: limits.devices,
|
|
2493
|
-
employeeLimit: limits.employees,
|
|
2494
|
-
memoryLimit: limits.memories
|
|
2495
|
-
};
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2887
|
+
if (existsSync9(deviceJsonPath)) {
|
|
2888
|
+
const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
|
|
2889
|
+
if (data.deviceId) return data.deviceId;
|
|
2498
2890
|
}
|
|
2499
2891
|
} catch {
|
|
2500
2892
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
2506
|
-
if (_revalTimer) return;
|
|
2507
|
-
_revalTimer = setInterval(async () => {
|
|
2508
|
-
try {
|
|
2509
|
-
const license = await checkLicense();
|
|
2510
|
-
if (!license.valid) {
|
|
2511
|
-
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
2512
|
-
}
|
|
2513
|
-
} catch {
|
|
2893
|
+
try {
|
|
2894
|
+
if (existsSync9(DEVICE_ID_PATH)) {
|
|
2895
|
+
const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
|
|
2896
|
+
if (id2) return id2;
|
|
2514
2897
|
}
|
|
2515
|
-
}
|
|
2516
|
-
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
2517
|
-
_revalTimer.unref();
|
|
2898
|
+
} catch {
|
|
2518
2899
|
}
|
|
2900
|
+
const id = randomUUID3();
|
|
2901
|
+
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2902
|
+
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
2903
|
+
return id;
|
|
2519
2904
|
}
|
|
2520
|
-
function
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2905
|
+
function loadLicense() {
|
|
2906
|
+
try {
|
|
2907
|
+
if (!existsSync9(LICENSE_PATH)) return null;
|
|
2908
|
+
return readFileSync7(LICENSE_PATH, "utf8").trim();
|
|
2909
|
+
} catch {
|
|
2910
|
+
return null;
|
|
2524
2911
|
}
|
|
2525
2912
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2532
|
-
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2533
|
-
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2534
|
-
API_BASE = "https://askexe.com/cloud";
|
|
2535
|
-
RETRY_DELAY_MS = 500;
|
|
2536
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
2537
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
2538
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
2539
|
-
-----END PUBLIC KEY-----`;
|
|
2540
|
-
LICENSE_JWT_ALG = "ES256";
|
|
2541
|
-
PLAN_LIMITS = {
|
|
2542
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2543
|
-
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
2544
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
2545
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
2546
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
2547
|
-
};
|
|
2548
|
-
FREE_LICENSE = {
|
|
2549
|
-
valid: true,
|
|
2550
|
-
plan: "free",
|
|
2551
|
-
email: "",
|
|
2552
|
-
expiresAt: null,
|
|
2553
|
-
deviceLimit: 1,
|
|
2554
|
-
employeeLimit: 1,
|
|
2555
|
-
memoryLimit: 5e3
|
|
2556
|
-
};
|
|
2557
|
-
CACHE_MAX_AGE_MS = 36e5;
|
|
2558
|
-
_revalTimer = null;
|
|
2559
|
-
}
|
|
2560
|
-
});
|
|
2561
|
-
|
|
2562
|
-
// src/lib/plan-limits.ts
|
|
2563
|
-
var plan_limits_exports = {};
|
|
2564
|
-
__export(plan_limits_exports, {
|
|
2565
|
-
PlanLimitError: () => PlanLimitError,
|
|
2566
|
-
assertEmployeeLimit: () => assertEmployeeLimit,
|
|
2567
|
-
assertEmployeeLimitSync: () => assertEmployeeLimitSync,
|
|
2568
|
-
assertFeature: () => assertFeature,
|
|
2569
|
-
assertMemoryLimit: () => assertMemoryLimit,
|
|
2570
|
-
countActiveMemories: () => countActiveMemories,
|
|
2571
|
-
getLicenseSync: () => getLicenseSync
|
|
2572
|
-
});
|
|
2573
|
-
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
2574
|
-
import path9 from "path";
|
|
2575
|
-
function getLicenseSync() {
|
|
2913
|
+
function saveLicense(apiKey) {
|
|
2914
|
+
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2915
|
+
writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2916
|
+
}
|
|
2917
|
+
async function verifyLicenseJwt(token) {
|
|
2576
2918
|
try {
|
|
2577
|
-
|
|
2578
|
-
const
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
if (parts.length !== 3) return freeLicense();
|
|
2582
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2919
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2920
|
+
const { payload } = await jwtVerify(token, key, {
|
|
2921
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
2922
|
+
});
|
|
2583
2923
|
const plan = payload.plan ?? "free";
|
|
2924
|
+
const email = payload.sub ?? "";
|
|
2584
2925
|
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2585
2926
|
return {
|
|
2586
2927
|
valid: true,
|
|
2587
2928
|
plan,
|
|
2588
|
-
email
|
|
2929
|
+
email,
|
|
2589
2930
|
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2590
2931
|
deviceLimit: limits.devices,
|
|
2591
2932
|
employeeLimit: limits.employees,
|
|
2592
2933
|
memoryLimit: limits.memories
|
|
2593
2934
|
};
|
|
2594
2935
|
} catch {
|
|
2595
|
-
return
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
function freeLicense() {
|
|
2599
|
-
const limits = PLAN_LIMITS.free;
|
|
2600
|
-
return {
|
|
2601
|
-
valid: true,
|
|
2602
|
-
plan: "free",
|
|
2603
|
-
email: "",
|
|
2604
|
-
expiresAt: null,
|
|
2605
|
-
deviceLimit: limits.devices,
|
|
2606
|
-
employeeLimit: limits.employees,
|
|
2607
|
-
memoryLimit: limits.memories
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
|
-
async function countActiveMemories() {
|
|
2611
|
-
if (!isInitialized()) return 0;
|
|
2612
|
-
const client = getClient();
|
|
2613
|
-
const result = await client.execute(
|
|
2614
|
-
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
2615
|
-
);
|
|
2616
|
-
const row = result.rows[0];
|
|
2617
|
-
return Number(row?.cnt ?? 0);
|
|
2618
|
-
}
|
|
2619
|
-
async function assertMemoryLimit() {
|
|
2620
|
-
const license = await checkLicense();
|
|
2621
|
-
if (license.memoryLimit < 0) return;
|
|
2622
|
-
const count = await countActiveMemories();
|
|
2623
|
-
if (count >= license.memoryLimit) {
|
|
2624
|
-
throw new PlanLimitError(
|
|
2625
|
-
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
2626
|
-
);
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
async function assertEmployeeLimit(license, rosterPath) {
|
|
2630
|
-
const lic = license ?? await checkLicense();
|
|
2631
|
-
if (lic.employeeLimit < 0) return;
|
|
2632
|
-
const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
|
|
2633
|
-
if (employees.length >= lic.employeeLimit) {
|
|
2634
|
-
throw new PlanLimitError(
|
|
2635
|
-
`Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2636
|
-
);
|
|
2936
|
+
return null;
|
|
2637
2937
|
}
|
|
2638
2938
|
}
|
|
2639
|
-
function
|
|
2640
|
-
const license = getLicenseSync();
|
|
2641
|
-
if (license.employeeLimit < 0) return;
|
|
2642
|
-
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2643
|
-
let count = 0;
|
|
2939
|
+
async function getCachedLicense() {
|
|
2644
2940
|
try {
|
|
2645
|
-
if (
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
}
|
|
2941
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
2942
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
2943
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2944
|
+
return await verifyLicenseJwt(raw.token);
|
|
2650
2945
|
} catch {
|
|
2651
|
-
|
|
2652
|
-
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
2653
|
-
);
|
|
2654
|
-
}
|
|
2655
|
-
if (count >= license.employeeLimit) {
|
|
2656
|
-
throw new PlanLimitError(
|
|
2657
|
-
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2658
|
-
);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
async function assertFeature(feature) {
|
|
2662
|
-
const license = await checkLicense();
|
|
2663
|
-
if (!isFeatureAllowed(license, feature)) {
|
|
2664
|
-
throw new PlanLimitError(
|
|
2665
|
-
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
2666
|
-
);
|
|
2667
|
-
}
|
|
2668
|
-
}
|
|
2669
|
-
var PlanLimitError, CACHE_PATH2;
|
|
2670
|
-
var init_plan_limits = __esm({
|
|
2671
|
-
"src/lib/plan-limits.ts"() {
|
|
2672
|
-
"use strict";
|
|
2673
|
-
init_database();
|
|
2674
|
-
init_employees();
|
|
2675
|
-
init_license();
|
|
2676
|
-
init_config();
|
|
2677
|
-
PlanLimitError = class extends Error {
|
|
2678
|
-
constructor(message) {
|
|
2679
|
-
super(message);
|
|
2680
|
-
this.name = "PlanLimitError";
|
|
2681
|
-
}
|
|
2682
|
-
};
|
|
2683
|
-
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2946
|
+
return null;
|
|
2684
2947
|
}
|
|
2685
|
-
});
|
|
2686
|
-
|
|
2687
|
-
// src/lib/tmux-routing.ts
|
|
2688
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9, appendFileSync } from "fs";
|
|
2689
|
-
import path10 from "path";
|
|
2690
|
-
import os7 from "os";
|
|
2691
|
-
import { fileURLToPath } from "url";
|
|
2692
|
-
function getMySession() {
|
|
2693
|
-
return getTransport().getMySession();
|
|
2694
|
-
}
|
|
2695
|
-
function extractRootExe(name) {
|
|
2696
|
-
if (!name) return null;
|
|
2697
|
-
if (!name.includes("-")) return name;
|
|
2698
|
-
const parts = name.split("-").filter(Boolean);
|
|
2699
|
-
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2700
2948
|
}
|
|
2701
|
-
function
|
|
2949
|
+
function readCachedToken() {
|
|
2702
2950
|
try {
|
|
2703
|
-
|
|
2704
|
-
|
|
2951
|
+
if (!existsSync9(CACHE_PATH)) return null;
|
|
2952
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
|
|
2953
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
2705
2954
|
} catch {
|
|
2706
2955
|
return null;
|
|
2707
2956
|
}
|
|
2708
2957
|
}
|
|
2709
|
-
function
|
|
2710
|
-
const mySession = getMySession();
|
|
2711
|
-
if (!mySession) return null;
|
|
2958
|
+
function getRawCachedPlan() {
|
|
2712
2959
|
try {
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2960
|
+
const token = readCachedToken();
|
|
2961
|
+
if (!token) return null;
|
|
2962
|
+
const parts = token.split(".");
|
|
2963
|
+
if (parts.length !== 3) return null;
|
|
2964
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2965
|
+
const plan = payload.plan ?? "free";
|
|
2966
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2967
|
+
process.stderr.write(
|
|
2968
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2969
|
+
`
|
|
2970
|
+
);
|
|
2971
|
+
return {
|
|
2972
|
+
valid: true,
|
|
2973
|
+
plan,
|
|
2974
|
+
email: payload.sub ?? "",
|
|
2975
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2976
|
+
deviceLimit: limits.devices,
|
|
2977
|
+
employeeLimit: limits.employees,
|
|
2978
|
+
memoryLimit: limits.memories
|
|
2979
|
+
};
|
|
2718
2980
|
} catch {
|
|
2981
|
+
return null;
|
|
2719
2982
|
}
|
|
2720
|
-
return extractRootExe(mySession) ?? mySession;
|
|
2721
2983
|
}
|
|
2722
|
-
|
|
2723
|
-
var init_tmux_routing = __esm({
|
|
2724
|
-
"src/lib/tmux-routing.ts"() {
|
|
2725
|
-
"use strict";
|
|
2726
|
-
init_session_registry();
|
|
2727
|
-
init_session_key();
|
|
2728
|
-
init_transport();
|
|
2729
|
-
init_cc_agent_support();
|
|
2730
|
-
init_mcp_prefix();
|
|
2731
|
-
init_provider_table();
|
|
2732
|
-
init_intercom_queue();
|
|
2733
|
-
init_plan_limits();
|
|
2734
|
-
init_employees();
|
|
2735
|
-
SPAWN_LOCK_DIR = path10.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
2736
|
-
SESSION_CACHE = path10.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2737
|
-
INTERCOM_LOG2 = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2738
|
-
DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2739
|
-
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2740
|
-
}
|
|
2741
|
-
});
|
|
2742
|
-
|
|
2743
|
-
// src/lib/task-scope.ts
|
|
2744
|
-
function getCurrentSessionScope() {
|
|
2984
|
+
function cacheResponse(token) {
|
|
2745
2985
|
try {
|
|
2746
|
-
|
|
2986
|
+
writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2747
2987
|
} catch {
|
|
2748
|
-
return null;
|
|
2749
2988
|
}
|
|
2750
2989
|
}
|
|
2751
|
-
function
|
|
2752
|
-
const
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
import net from "net";
|
|
2769
|
-
import { spawn } from "child_process";
|
|
2770
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
2771
|
-
import { existsSync as existsSync10, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
2772
|
-
import path11 from "path";
|
|
2773
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2774
|
-
function handleData(chunk) {
|
|
2775
|
-
_buffer += chunk.toString();
|
|
2776
|
-
if (_buffer.length > MAX_BUFFER) {
|
|
2777
|
-
_buffer = "";
|
|
2778
|
-
return;
|
|
2779
|
-
}
|
|
2780
|
-
let newlineIdx;
|
|
2781
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
2782
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
2783
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
2784
|
-
if (!line) continue;
|
|
2785
|
-
try {
|
|
2786
|
-
const response = JSON.parse(line);
|
|
2787
|
-
const entry = _pending.get(response.id);
|
|
2788
|
-
if (entry) {
|
|
2789
|
-
clearTimeout(entry.timer);
|
|
2790
|
-
_pending.delete(response.id);
|
|
2791
|
-
entry.resolve(response);
|
|
2990
|
+
async function validateLicense(apiKey, deviceId) {
|
|
2991
|
+
const did = deviceId ?? loadDeviceId();
|
|
2992
|
+
try {
|
|
2993
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2994
|
+
method: "POST",
|
|
2995
|
+
headers: { "Content-Type": "application/json" },
|
|
2996
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2997
|
+
signal: AbortSignal.timeout(1e4)
|
|
2998
|
+
});
|
|
2999
|
+
if (res.ok) {
|
|
3000
|
+
const data = await res.json();
|
|
3001
|
+
if (data.error === "device_limit_exceeded") {
|
|
3002
|
+
const cached2 = await getCachedLicense();
|
|
3003
|
+
if (cached2) return cached2;
|
|
3004
|
+
const raw2 = getRawCachedPlan();
|
|
3005
|
+
if (raw2) return { ...raw2, valid: false };
|
|
3006
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2792
3007
|
}
|
|
2793
|
-
|
|
3008
|
+
if (data.token) {
|
|
3009
|
+
cacheResponse(data.token);
|
|
3010
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3011
|
+
if (verified) return verified;
|
|
3012
|
+
}
|
|
3013
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3014
|
+
return {
|
|
3015
|
+
valid: data.valid,
|
|
3016
|
+
plan: data.plan,
|
|
3017
|
+
email: data.email,
|
|
3018
|
+
expiresAt: data.expiresAt,
|
|
3019
|
+
deviceLimit: limits.devices,
|
|
3020
|
+
employeeLimit: limits.employees,
|
|
3021
|
+
memoryLimit: limits.memories
|
|
3022
|
+
};
|
|
2794
3023
|
}
|
|
3024
|
+
const cached = await getCachedLicense();
|
|
3025
|
+
if (cached) return cached;
|
|
3026
|
+
const raw = getRawCachedPlan();
|
|
3027
|
+
if (raw) return raw;
|
|
3028
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3029
|
+
} catch {
|
|
3030
|
+
const cached = await getCachedLicense();
|
|
3031
|
+
if (cached) return cached;
|
|
3032
|
+
const rawFallback = getRawCachedPlan();
|
|
3033
|
+
if (rawFallback) return rawFallback;
|
|
3034
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2795
3035
|
}
|
|
2796
3036
|
}
|
|
2797
|
-
function
|
|
2798
|
-
|
|
3037
|
+
function getCacheAgeMs() {
|
|
3038
|
+
try {
|
|
3039
|
+
const { statSync: statSync2 } = __require("fs");
|
|
3040
|
+
const s = statSync2(CACHE_PATH);
|
|
3041
|
+
return Date.now() - s.mtimeMs;
|
|
3042
|
+
} catch {
|
|
3043
|
+
return Infinity;
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
async function checkLicense() {
|
|
3047
|
+
let key = loadLicense();
|
|
3048
|
+
if (!key) {
|
|
2799
3049
|
try {
|
|
2800
|
-
const
|
|
2801
|
-
if (
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
3050
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
3051
|
+
if (existsSync9(configPath)) {
|
|
3052
|
+
const raw = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
3053
|
+
const cloud = raw.cloud;
|
|
3054
|
+
if (cloud?.apiKey) {
|
|
3055
|
+
key = cloud.apiKey;
|
|
3056
|
+
saveLicense(key);
|
|
2806
3057
|
}
|
|
2807
3058
|
}
|
|
2808
3059
|
} catch {
|
|
2809
3060
|
}
|
|
2810
|
-
try {
|
|
2811
|
-
unlinkSync3(PID_PATH);
|
|
2812
|
-
} catch {
|
|
2813
|
-
}
|
|
2814
|
-
try {
|
|
2815
|
-
unlinkSync3(SOCKET_PATH);
|
|
2816
|
-
} catch {
|
|
2817
|
-
}
|
|
2818
3061
|
}
|
|
3062
|
+
if (!key) return FREE_LICENSE;
|
|
3063
|
+
const cached = await getCachedLicense();
|
|
3064
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
3065
|
+
const deviceId = loadDeviceId();
|
|
3066
|
+
return validateLicense(key, deviceId);
|
|
2819
3067
|
}
|
|
2820
|
-
function
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
3068
|
+
function isFeatureAllowed(license, feature) {
|
|
3069
|
+
switch (feature) {
|
|
3070
|
+
case "cloud_sync":
|
|
3071
|
+
case "external_agents":
|
|
3072
|
+
case "wiki":
|
|
3073
|
+
return license.plan !== "free";
|
|
3074
|
+
case "unlimited_employees":
|
|
3075
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
2826
3076
|
}
|
|
2827
|
-
return null;
|
|
2828
3077
|
}
|
|
2829
|
-
function
|
|
2830
|
-
const
|
|
2831
|
-
if (!
|
|
2832
|
-
|
|
2833
|
-
|
|
3078
|
+
function mirrorLicenseKey(apiKey) {
|
|
3079
|
+
const trimmed = apiKey.trim();
|
|
3080
|
+
if (!trimmed) return;
|
|
3081
|
+
saveLicense(trimmed);
|
|
3082
|
+
}
|
|
3083
|
+
async function assertVpsLicense(opts) {
|
|
3084
|
+
const env = opts?.env ?? process.env;
|
|
3085
|
+
const inProduction = env.NODE_ENV === "production";
|
|
3086
|
+
if (!opts?.force && !inProduction) {
|
|
3087
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
2834
3088
|
}
|
|
2835
|
-
const
|
|
2836
|
-
if (
|
|
2837
|
-
|
|
2838
|
-
`);
|
|
2839
|
-
return;
|
|
3089
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
3090
|
+
if (envKey) {
|
|
3091
|
+
saveLicense(envKey);
|
|
2840
3092
|
}
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3093
|
+
const apiKey = envKey ?? loadLicense();
|
|
3094
|
+
if (!apiKey) {
|
|
3095
|
+
throw new Error(
|
|
3096
|
+
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
3099
|
+
const deviceId = loadDeviceId();
|
|
3100
|
+
let backendResponse = null;
|
|
3101
|
+
let explicitRejection = false;
|
|
3102
|
+
let transientFailure = false;
|
|
2846
3103
|
try {
|
|
2847
|
-
|
|
3104
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3105
|
+
method: "POST",
|
|
3106
|
+
headers: { "Content-Type": "application/json" },
|
|
3107
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3108
|
+
signal: AbortSignal.timeout(1e4)
|
|
3109
|
+
});
|
|
3110
|
+
if (res.ok) {
|
|
3111
|
+
backendResponse = await res.json();
|
|
3112
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
3113
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
3114
|
+
explicitRejection = true;
|
|
3115
|
+
} else {
|
|
3116
|
+
transientFailure = true;
|
|
3117
|
+
}
|
|
2848
3118
|
} catch {
|
|
3119
|
+
transientFailure = true;
|
|
2849
3120
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
TMUX: void 0,
|
|
2856
|
-
// Daemon is global — must not inherit session scope
|
|
2857
|
-
TMUX_PANE: void 0,
|
|
2858
|
-
// Prevents resolveExeSession() from scoping to one session
|
|
2859
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2860
|
-
EXE_DAEMON_PID: PID_PATH
|
|
3121
|
+
if (backendResponse?.valid) {
|
|
3122
|
+
if (backendResponse.token) {
|
|
3123
|
+
cacheResponse(backendResponse.token);
|
|
3124
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
3125
|
+
if (verified) return verified;
|
|
2861
3126
|
}
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3127
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
3128
|
+
return {
|
|
3129
|
+
valid: true,
|
|
3130
|
+
plan: backendResponse.plan,
|
|
3131
|
+
email: backendResponse.email,
|
|
3132
|
+
expiresAt: backendResponse.expiresAt,
|
|
3133
|
+
deviceLimit: limits.devices,
|
|
3134
|
+
employeeLimit: limits.employees,
|
|
3135
|
+
memoryLimit: limits.memories
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
if (explicitRejection) {
|
|
3139
|
+
throw new Error(
|
|
3140
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
3141
|
+
);
|
|
3142
|
+
}
|
|
3143
|
+
if (!transientFailure) {
|
|
3144
|
+
throw new Error(
|
|
3145
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
const fresh = await getCachedLicense();
|
|
3149
|
+
if (fresh && fresh.valid) return fresh;
|
|
3150
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
3151
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
3152
|
+
try {
|
|
3153
|
+
const token = readCachedToken();
|
|
3154
|
+
if (token) {
|
|
3155
|
+
const payloadB64 = token.split(".")[1];
|
|
3156
|
+
if (payloadB64) {
|
|
3157
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
3158
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
3159
|
+
if (Date.now() < expMs + graceMs) {
|
|
3160
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
3161
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
3162
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
3163
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
3164
|
+
});
|
|
3165
|
+
const plan = verified.plan ?? "free";
|
|
3166
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3167
|
+
return {
|
|
3168
|
+
valid: true,
|
|
3169
|
+
plan,
|
|
3170
|
+
email: verified.sub ?? "",
|
|
3171
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
3172
|
+
deviceLimit: limits.devices,
|
|
3173
|
+
employeeLimit: limits.employees,
|
|
3174
|
+
memoryLimit: limits.memories
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
2868
3178
|
}
|
|
3179
|
+
} catch {
|
|
2869
3180
|
}
|
|
3181
|
+
throw new Error(
|
|
3182
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
|
|
3183
|
+
);
|
|
2870
3184
|
}
|
|
2871
|
-
function
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
closeSync(fd);
|
|
2875
|
-
return true;
|
|
2876
|
-
} catch {
|
|
3185
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
3186
|
+
if (_revalTimer) return;
|
|
3187
|
+
_revalTimer = setInterval(async () => {
|
|
2877
3188
|
try {
|
|
2878
|
-
const
|
|
2879
|
-
if (
|
|
2880
|
-
|
|
2881
|
-
unlinkSync3(SPAWN_LOCK_PATH);
|
|
2882
|
-
} catch {
|
|
2883
|
-
}
|
|
2884
|
-
try {
|
|
2885
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
2886
|
-
closeSync(fd);
|
|
2887
|
-
return true;
|
|
2888
|
-
} catch {
|
|
2889
|
-
}
|
|
3189
|
+
const license = await checkLicense();
|
|
3190
|
+
if (!license.valid) {
|
|
3191
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
2890
3192
|
}
|
|
2891
3193
|
} catch {
|
|
2892
3194
|
}
|
|
2893
|
-
|
|
3195
|
+
}, intervalMs);
|
|
3196
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
3197
|
+
_revalTimer.unref();
|
|
2894
3198
|
}
|
|
2895
3199
|
}
|
|
2896
|
-
function
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3200
|
+
function stopLicenseRevalidation() {
|
|
3201
|
+
if (_revalTimer) {
|
|
3202
|
+
clearInterval(_revalTimer);
|
|
3203
|
+
_revalTimer = null;
|
|
2900
3204
|
}
|
|
2901
3205
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
clearTimeout(connectTimeout);
|
|
2936
|
-
resolve(false);
|
|
2937
|
-
});
|
|
2938
|
-
});
|
|
2939
|
-
}
|
|
2940
|
-
async function connectEmbedDaemon() {
|
|
2941
|
-
if (_socket && _connected) return true;
|
|
2942
|
-
if (await connectToSocket()) return true;
|
|
2943
|
-
if (acquireSpawnLock()) {
|
|
2944
|
-
try {
|
|
2945
|
-
cleanupStaleFiles();
|
|
2946
|
-
spawnDaemon();
|
|
2947
|
-
} finally {
|
|
2948
|
-
releaseSpawnLock();
|
|
2949
|
-
}
|
|
3206
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
3207
|
+
var init_license = __esm({
|
|
3208
|
+
"src/lib/license.ts"() {
|
|
3209
|
+
"use strict";
|
|
3210
|
+
init_config();
|
|
3211
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3212
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3213
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3214
|
+
API_BASE = "https://askexe.com/cloud";
|
|
3215
|
+
RETRY_DELAY_MS = 500;
|
|
3216
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
3217
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
3218
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
3219
|
+
-----END PUBLIC KEY-----`;
|
|
3220
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3221
|
+
PLAN_LIMITS = {
|
|
3222
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3223
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
3224
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
3225
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3226
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3227
|
+
};
|
|
3228
|
+
FREE_LICENSE = {
|
|
3229
|
+
valid: true,
|
|
3230
|
+
plan: "free",
|
|
3231
|
+
email: "",
|
|
3232
|
+
expiresAt: null,
|
|
3233
|
+
deviceLimit: 1,
|
|
3234
|
+
employeeLimit: 1,
|
|
3235
|
+
memoryLimit: 5e3
|
|
3236
|
+
};
|
|
3237
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
3238
|
+
_revalTimer = null;
|
|
2950
3239
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3240
|
+
});
|
|
3241
|
+
|
|
3242
|
+
// src/lib/plan-limits.ts
|
|
3243
|
+
var plan_limits_exports = {};
|
|
3244
|
+
__export(plan_limits_exports, {
|
|
3245
|
+
PlanLimitError: () => PlanLimitError,
|
|
3246
|
+
assertEmployeeLimit: () => assertEmployeeLimit,
|
|
3247
|
+
assertEmployeeLimitSync: () => assertEmployeeLimitSync,
|
|
3248
|
+
assertFeature: () => assertFeature,
|
|
3249
|
+
assertMemoryLimit: () => assertMemoryLimit,
|
|
3250
|
+
countActiveMemories: () => countActiveMemories,
|
|
3251
|
+
getLicenseSync: () => getLicenseSync
|
|
3252
|
+
});
|
|
3253
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
3254
|
+
import path11 from "path";
|
|
3255
|
+
function getLicenseSync() {
|
|
3256
|
+
try {
|
|
3257
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3258
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
3259
|
+
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3260
|
+
const parts = raw.token.split(".");
|
|
3261
|
+
if (parts.length !== 3) return freeLicense();
|
|
3262
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3263
|
+
const plan = payload.plan ?? "free";
|
|
3264
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3265
|
+
return {
|
|
3266
|
+
valid: true,
|
|
3267
|
+
plan,
|
|
3268
|
+
email: payload.sub ?? "",
|
|
3269
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3270
|
+
deviceLimit: limits.devices,
|
|
3271
|
+
employeeLimit: limits.employees,
|
|
3272
|
+
memoryLimit: limits.memories
|
|
3273
|
+
};
|
|
3274
|
+
} catch {
|
|
3275
|
+
return freeLicense();
|
|
2957
3276
|
}
|
|
2958
|
-
return false;
|
|
2959
3277
|
}
|
|
2960
|
-
function
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
_pending.set(id, { resolve, timer });
|
|
2972
|
-
try {
|
|
2973
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
2974
|
-
} catch {
|
|
2975
|
-
clearTimeout(timer);
|
|
2976
|
-
_pending.delete(id);
|
|
2977
|
-
resolve({ error: "Write failed" });
|
|
2978
|
-
}
|
|
2979
|
-
});
|
|
3278
|
+
function freeLicense() {
|
|
3279
|
+
const limits = PLAN_LIMITS.free;
|
|
3280
|
+
return {
|
|
3281
|
+
valid: true,
|
|
3282
|
+
plan: "free",
|
|
3283
|
+
email: "",
|
|
3284
|
+
expiresAt: null,
|
|
3285
|
+
deviceLimit: limits.devices,
|
|
3286
|
+
employeeLimit: limits.employees,
|
|
3287
|
+
memoryLimit: limits.memories
|
|
3288
|
+
};
|
|
2980
3289
|
}
|
|
2981
|
-
async function
|
|
2982
|
-
if (!
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
_pending.set(id, {
|
|
2990
|
-
resolve: (data) => {
|
|
2991
|
-
if (data.health) {
|
|
2992
|
-
resolve(data.health);
|
|
2993
|
-
} else {
|
|
2994
|
-
resolve(null);
|
|
2995
|
-
}
|
|
2996
|
-
},
|
|
2997
|
-
timer
|
|
2998
|
-
});
|
|
2999
|
-
try {
|
|
3000
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
3001
|
-
} catch {
|
|
3002
|
-
clearTimeout(timer);
|
|
3003
|
-
_pending.delete(id);
|
|
3004
|
-
resolve(null);
|
|
3005
|
-
}
|
|
3006
|
-
});
|
|
3290
|
+
async function countActiveMemories() {
|
|
3291
|
+
if (!isInitialized()) return 0;
|
|
3292
|
+
const client = getClient();
|
|
3293
|
+
const result = await client.execute(
|
|
3294
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
3295
|
+
);
|
|
3296
|
+
const row = result.rows[0];
|
|
3297
|
+
return Number(row?.cnt ?? 0);
|
|
3007
3298
|
}
|
|
3008
|
-
function
|
|
3009
|
-
|
|
3010
|
-
if (
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
} catch {
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
} catch {
|
|
3020
|
-
}
|
|
3299
|
+
async function assertMemoryLimit() {
|
|
3300
|
+
const license = await checkLicense();
|
|
3301
|
+
if (license.memoryLimit < 0) return;
|
|
3302
|
+
const count = await countActiveMemories();
|
|
3303
|
+
if (count >= license.memoryLimit) {
|
|
3304
|
+
throw new PlanLimitError(
|
|
3305
|
+
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
3306
|
+
);
|
|
3021
3307
|
}
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3308
|
+
}
|
|
3309
|
+
async function assertEmployeeLimit(license, rosterPath) {
|
|
3310
|
+
const lic = license ?? await checkLicense();
|
|
3311
|
+
if (lic.employeeLimit < 0) return;
|
|
3312
|
+
const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
|
|
3313
|
+
if (employees.length >= lic.employeeLimit) {
|
|
3314
|
+
throw new PlanLimitError(
|
|
3315
|
+
`Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
3316
|
+
);
|
|
3025
3317
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3318
|
+
}
|
|
3319
|
+
function assertEmployeeLimitSync(rosterPath) {
|
|
3320
|
+
const license = getLicenseSync();
|
|
3321
|
+
if (license.employeeLimit < 0) return;
|
|
3322
|
+
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3323
|
+
let count = 0;
|
|
3028
3324
|
try {
|
|
3029
|
-
|
|
3325
|
+
if (existsSync10(filePath)) {
|
|
3326
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
3327
|
+
const employees = JSON.parse(raw);
|
|
3328
|
+
count = Array.isArray(employees) ? employees.length : 0;
|
|
3329
|
+
}
|
|
3030
3330
|
} catch {
|
|
3331
|
+
throw new PlanLimitError(
|
|
3332
|
+
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
3333
|
+
);
|
|
3031
3334
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3335
|
+
if (count >= license.employeeLimit) {
|
|
3336
|
+
throw new PlanLimitError(
|
|
3337
|
+
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
3338
|
+
);
|
|
3035
3339
|
}
|
|
3036
|
-
spawnDaemon();
|
|
3037
3340
|
}
|
|
3038
|
-
async function
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3341
|
+
async function assertFeature(feature) {
|
|
3342
|
+
const license = await checkLicense();
|
|
3343
|
+
if (!isFeatureAllowed(license, feature)) {
|
|
3344
|
+
throw new PlanLimitError(
|
|
3345
|
+
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
var PlanLimitError, CACHE_PATH2;
|
|
3350
|
+
var init_plan_limits = __esm({
|
|
3351
|
+
"src/lib/plan-limits.ts"() {
|
|
3352
|
+
"use strict";
|
|
3353
|
+
init_database();
|
|
3354
|
+
init_employees();
|
|
3355
|
+
init_license();
|
|
3356
|
+
init_config();
|
|
3357
|
+
PlanLimitError = class extends Error {
|
|
3358
|
+
constructor(message) {
|
|
3359
|
+
super(message);
|
|
3360
|
+
this.name = "PlanLimitError";
|
|
3053
3361
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3362
|
+
};
|
|
3363
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3056
3364
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3365
|
+
});
|
|
3366
|
+
|
|
3367
|
+
// src/lib/tmux-routing.ts
|
|
3368
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
3369
|
+
import path12 from "path";
|
|
3370
|
+
import os7 from "os";
|
|
3371
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3372
|
+
function getMySession() {
|
|
3373
|
+
return getTransport().getMySession();
|
|
3374
|
+
}
|
|
3375
|
+
function extractRootExe(name) {
|
|
3376
|
+
if (!name) return null;
|
|
3377
|
+
if (!name.includes("-")) return name;
|
|
3378
|
+
const parts = name.split("-").filter(Boolean);
|
|
3379
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3380
|
+
}
|
|
3381
|
+
function getParentExe(sessionKey) {
|
|
3382
|
+
try {
|
|
3383
|
+
const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3384
|
+
return data.parentExe || null;
|
|
3385
|
+
} catch {
|
|
3386
|
+
return null;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
function resolveExeSession() {
|
|
3390
|
+
const mySession = getMySession();
|
|
3391
|
+
if (!mySession) return null;
|
|
3392
|
+
try {
|
|
3393
|
+
const key = getSessionKey();
|
|
3394
|
+
const parentExe = getParentExe(key);
|
|
3395
|
+
if (parentExe) {
|
|
3396
|
+
return extractRootExe(parentExe) ?? parentExe;
|
|
3069
3397
|
}
|
|
3070
|
-
|
|
3071
|
-
const retry = await sendRequest([text], priority);
|
|
3072
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
3073
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
3074
|
-
`);
|
|
3398
|
+
} catch {
|
|
3075
3399
|
}
|
|
3076
|
-
return
|
|
3400
|
+
return extractRootExe(mySession) ?? mySession;
|
|
3077
3401
|
}
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3402
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
3403
|
+
var init_tmux_routing = __esm({
|
|
3404
|
+
"src/lib/tmux-routing.ts"() {
|
|
3405
|
+
"use strict";
|
|
3406
|
+
init_session_registry();
|
|
3407
|
+
init_session_key();
|
|
3408
|
+
init_transport();
|
|
3409
|
+
init_cc_agent_support();
|
|
3410
|
+
init_mcp_prefix();
|
|
3411
|
+
init_provider_table();
|
|
3412
|
+
init_agent_config();
|
|
3413
|
+
init_runtime_table();
|
|
3414
|
+
init_intercom_queue();
|
|
3415
|
+
init_plan_limits();
|
|
3416
|
+
init_employees();
|
|
3417
|
+
SPAWN_LOCK_DIR = path12.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3418
|
+
SESSION_CACHE = path12.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3419
|
+
INTERCOM_LOG2 = path12.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3420
|
+
DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3421
|
+
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3082
3422
|
}
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3423
|
+
});
|
|
3424
|
+
|
|
3425
|
+
// src/lib/task-scope.ts
|
|
3426
|
+
function getCurrentSessionScope() {
|
|
3427
|
+
try {
|
|
3428
|
+
return resolveExeSession();
|
|
3429
|
+
} catch {
|
|
3430
|
+
return null;
|
|
3089
3431
|
}
|
|
3090
3432
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3433
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3434
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3435
|
+
if (!scope) return { sql: "", args: [] };
|
|
3436
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3437
|
+
return {
|
|
3438
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3439
|
+
args: [scope]
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
var init_task_scope = __esm({
|
|
3443
|
+
"src/lib/task-scope.ts"() {
|
|
3094
3444
|
"use strict";
|
|
3095
|
-
|
|
3096
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path11.join(EXE_AI_DIR, "exed.sock");
|
|
3097
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path11.join(EXE_AI_DIR, "exed.pid");
|
|
3098
|
-
SPAWN_LOCK_PATH = path11.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3099
|
-
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3100
|
-
CONNECT_TIMEOUT_MS = 15e3;
|
|
3101
|
-
REQUEST_TIMEOUT_MS = 3e4;
|
|
3102
|
-
_socket = null;
|
|
3103
|
-
_connected = false;
|
|
3104
|
-
_buffer = "";
|
|
3105
|
-
_requestCount = 0;
|
|
3106
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
3107
|
-
_pending = /* @__PURE__ */ new Map();
|
|
3108
|
-
MAX_BUFFER = 1e7;
|
|
3445
|
+
init_tmux_routing();
|
|
3109
3446
|
}
|
|
3110
3447
|
});
|
|
3111
3448
|
|
|
@@ -3146,10 +3483,10 @@ async function disposeEmbedder() {
|
|
|
3146
3483
|
async function embedDirect(text) {
|
|
3147
3484
|
const llamaCpp = await import("node-llama-cpp");
|
|
3148
3485
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3149
|
-
const { existsSync:
|
|
3150
|
-
const
|
|
3151
|
-
const modelPath =
|
|
3152
|
-
if (!
|
|
3486
|
+
const { existsSync: existsSync16 } = await import("fs");
|
|
3487
|
+
const path17 = await import("path");
|
|
3488
|
+
const modelPath = path17.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3489
|
+
if (!existsSync16(modelPath)) {
|
|
3153
3490
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3154
3491
|
}
|
|
3155
3492
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3187,14 +3524,14 @@ __export(worker_gate_exports, {
|
|
|
3187
3524
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
3188
3525
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
3189
3526
|
});
|
|
3190
|
-
import { readdirSync as readdirSync3, writeFileSync as
|
|
3191
|
-
import
|
|
3527
|
+
import { readdirSync as readdirSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "fs";
|
|
3528
|
+
import path13 from "path";
|
|
3192
3529
|
function tryAcquireWorkerSlot() {
|
|
3193
3530
|
try {
|
|
3194
|
-
|
|
3531
|
+
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3195
3532
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
3196
|
-
const reservationPath =
|
|
3197
|
-
|
|
3533
|
+
const reservationPath = path13.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
3534
|
+
writeFileSync6(reservationPath, String(process.pid));
|
|
3198
3535
|
const files = readdirSync3(WORKER_PID_DIR);
|
|
3199
3536
|
let alive = 0;
|
|
3200
3537
|
for (const f of files) {
|
|
@@ -3211,7 +3548,7 @@ function tryAcquireWorkerSlot() {
|
|
|
3211
3548
|
alive++;
|
|
3212
3549
|
} catch {
|
|
3213
3550
|
try {
|
|
3214
|
-
unlinkSync4(
|
|
3551
|
+
unlinkSync4(path13.join(WORKER_PID_DIR, f));
|
|
3215
3552
|
} catch {
|
|
3216
3553
|
}
|
|
3217
3554
|
}
|
|
@@ -3234,21 +3571,21 @@ function tryAcquireWorkerSlot() {
|
|
|
3234
3571
|
}
|
|
3235
3572
|
function registerWorkerPid(pid) {
|
|
3236
3573
|
try {
|
|
3237
|
-
|
|
3238
|
-
|
|
3574
|
+
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3575
|
+
writeFileSync6(path13.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
3239
3576
|
} catch {
|
|
3240
3577
|
}
|
|
3241
3578
|
}
|
|
3242
3579
|
function cleanupWorkerPid() {
|
|
3243
3580
|
try {
|
|
3244
|
-
unlinkSync4(
|
|
3581
|
+
unlinkSync4(path13.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
3245
3582
|
} catch {
|
|
3246
3583
|
}
|
|
3247
3584
|
}
|
|
3248
3585
|
function tryAcquireBackfillLock() {
|
|
3249
3586
|
try {
|
|
3250
|
-
|
|
3251
|
-
if (
|
|
3587
|
+
mkdirSync6(WORKER_PID_DIR, { recursive: true });
|
|
3588
|
+
if (existsSync12(BACKFILL_LOCK)) {
|
|
3252
3589
|
try {
|
|
3253
3590
|
const pid = parseInt(
|
|
3254
3591
|
__require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
|
|
@@ -3264,7 +3601,7 @@ function tryAcquireBackfillLock() {
|
|
|
3264
3601
|
} catch {
|
|
3265
3602
|
}
|
|
3266
3603
|
}
|
|
3267
|
-
|
|
3604
|
+
writeFileSync6(BACKFILL_LOCK, String(process.pid));
|
|
3268
3605
|
return true;
|
|
3269
3606
|
} catch {
|
|
3270
3607
|
return true;
|
|
@@ -3281,9 +3618,9 @@ var init_worker_gate = __esm({
|
|
|
3281
3618
|
"src/lib/worker-gate.ts"() {
|
|
3282
3619
|
"use strict";
|
|
3283
3620
|
init_config();
|
|
3284
|
-
WORKER_PID_DIR =
|
|
3621
|
+
WORKER_PID_DIR = path13.join(EXE_AI_DIR, "worker-pids");
|
|
3285
3622
|
MAX_CONCURRENT_WORKERS = 3;
|
|
3286
|
-
BACKFILL_LOCK =
|
|
3623
|
+
BACKFILL_LOCK = path13.join(WORKER_PID_DIR, "backfill.lock");
|
|
3287
3624
|
}
|
|
3288
3625
|
});
|
|
3289
3626
|
|
|
@@ -3366,6 +3703,232 @@ var init_compress = __esm({
|
|
|
3366
3703
|
}
|
|
3367
3704
|
});
|
|
3368
3705
|
|
|
3706
|
+
// src/lib/crdt-sync.ts
|
|
3707
|
+
var crdt_sync_exports = {};
|
|
3708
|
+
__export(crdt_sync_exports, {
|
|
3709
|
+
_setStatePath: () => _setStatePath,
|
|
3710
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
3711
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
3712
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
3713
|
+
getFullState: () => getFullState,
|
|
3714
|
+
getStateVector: () => getStateVector,
|
|
3715
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
3716
|
+
importExistingMemories: () => importExistingMemories,
|
|
3717
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
3718
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
3719
|
+
onUpdate: () => onUpdate,
|
|
3720
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
3721
|
+
readAllMemories: () => readAllMemories,
|
|
3722
|
+
rebuildFromDb: () => rebuildFromDb
|
|
3723
|
+
});
|
|
3724
|
+
import * as Y from "yjs";
|
|
3725
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
3726
|
+
import path14 from "path";
|
|
3727
|
+
import { homedir } from "os";
|
|
3728
|
+
function getStatePath() {
|
|
3729
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
3730
|
+
}
|
|
3731
|
+
function _setStatePath(p) {
|
|
3732
|
+
_statePathOverride = p;
|
|
3733
|
+
}
|
|
3734
|
+
function initCrdtDoc() {
|
|
3735
|
+
if (doc) return doc;
|
|
3736
|
+
doc = new Y.Doc();
|
|
3737
|
+
const sp = getStatePath();
|
|
3738
|
+
if (existsSync13(sp)) {
|
|
3739
|
+
try {
|
|
3740
|
+
const state = readFileSync10(sp);
|
|
3741
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
3742
|
+
} catch {
|
|
3743
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
3744
|
+
try {
|
|
3745
|
+
unlinkSync5(sp);
|
|
3746
|
+
} catch {
|
|
3747
|
+
}
|
|
3748
|
+
rebuildFromDb().catch((err) => {
|
|
3749
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
3750
|
+
});
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
doc.on("update", () => {
|
|
3754
|
+
persistState();
|
|
3755
|
+
});
|
|
3756
|
+
return doc;
|
|
3757
|
+
}
|
|
3758
|
+
function getMemoriesMap() {
|
|
3759
|
+
const d = initCrdtDoc();
|
|
3760
|
+
return d.getMap("memories");
|
|
3761
|
+
}
|
|
3762
|
+
function getBehaviorsMap() {
|
|
3763
|
+
const d = initCrdtDoc();
|
|
3764
|
+
return d.getMap("behaviors");
|
|
3765
|
+
}
|
|
3766
|
+
function applyRemoteUpdate(update) {
|
|
3767
|
+
const d = initCrdtDoc();
|
|
3768
|
+
Y.applyUpdate(d, update);
|
|
3769
|
+
}
|
|
3770
|
+
function getFullState() {
|
|
3771
|
+
const d = initCrdtDoc();
|
|
3772
|
+
return Y.encodeStateAsUpdate(d);
|
|
3773
|
+
}
|
|
3774
|
+
function getDiffUpdate(remoteStateVector) {
|
|
3775
|
+
const d = initCrdtDoc();
|
|
3776
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
3777
|
+
}
|
|
3778
|
+
function getStateVector() {
|
|
3779
|
+
const d = initCrdtDoc();
|
|
3780
|
+
return Y.encodeStateVector(d);
|
|
3781
|
+
}
|
|
3782
|
+
function importExistingMemories(memories) {
|
|
3783
|
+
const map = getMemoriesMap();
|
|
3784
|
+
const d = initCrdtDoc();
|
|
3785
|
+
let imported = 0;
|
|
3786
|
+
d.transact(() => {
|
|
3787
|
+
for (const mem of memories) {
|
|
3788
|
+
if (!mem.id) continue;
|
|
3789
|
+
if (map.has(mem.id)) continue;
|
|
3790
|
+
const entry = new Y.Map();
|
|
3791
|
+
entry.set("id", mem.id);
|
|
3792
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
3793
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
3794
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
3795
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
3796
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
3797
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
3798
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
3799
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
3800
|
+
entry.set("version", mem.version ?? 0);
|
|
3801
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
3802
|
+
entry.set("scope", mem.scope ?? "business");
|
|
3803
|
+
map.set(mem.id, entry);
|
|
3804
|
+
imported++;
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
return imported;
|
|
3808
|
+
}
|
|
3809
|
+
function importExistingBehaviors(behaviors) {
|
|
3810
|
+
const map = getBehaviorsMap();
|
|
3811
|
+
const d = initCrdtDoc();
|
|
3812
|
+
let imported = 0;
|
|
3813
|
+
d.transact(() => {
|
|
3814
|
+
for (const beh of behaviors) {
|
|
3815
|
+
if (!beh.id) continue;
|
|
3816
|
+
if (map.has(beh.id)) continue;
|
|
3817
|
+
const entry = new Y.Map();
|
|
3818
|
+
entry.set("id", beh.id);
|
|
3819
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
3820
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
3821
|
+
entry.set("domain", beh.domain ?? null);
|
|
3822
|
+
entry.set("content", beh.content ?? null);
|
|
3823
|
+
entry.set("active", beh.active ?? 1);
|
|
3824
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
3825
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
3826
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
3827
|
+
map.set(beh.id, entry);
|
|
3828
|
+
imported++;
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
return imported;
|
|
3832
|
+
}
|
|
3833
|
+
function readAllMemories() {
|
|
3834
|
+
const map = getMemoriesMap();
|
|
3835
|
+
const records = [];
|
|
3836
|
+
map.forEach((entry, id) => {
|
|
3837
|
+
records.push({
|
|
3838
|
+
id,
|
|
3839
|
+
agent_id: entry.get("agent_id"),
|
|
3840
|
+
agent_role: entry.get("agent_role"),
|
|
3841
|
+
session_id: entry.get("session_id"),
|
|
3842
|
+
timestamp: entry.get("timestamp"),
|
|
3843
|
+
tool_name: entry.get("tool_name"),
|
|
3844
|
+
project_name: entry.get("project_name"),
|
|
3845
|
+
has_error: entry.get("has_error"),
|
|
3846
|
+
raw_text: entry.get("raw_text"),
|
|
3847
|
+
version: entry.get("version"),
|
|
3848
|
+
author_device_id: entry.get("author_device_id"),
|
|
3849
|
+
scope: entry.get("scope")
|
|
3850
|
+
});
|
|
3851
|
+
});
|
|
3852
|
+
return records;
|
|
3853
|
+
}
|
|
3854
|
+
function readAllBehaviors() {
|
|
3855
|
+
const map = getBehaviorsMap();
|
|
3856
|
+
const records = [];
|
|
3857
|
+
map.forEach((entry, id) => {
|
|
3858
|
+
records.push({
|
|
3859
|
+
id,
|
|
3860
|
+
agent_id: entry.get("agent_id"),
|
|
3861
|
+
project_name: entry.get("project_name"),
|
|
3862
|
+
domain: entry.get("domain"),
|
|
3863
|
+
content: entry.get("content"),
|
|
3864
|
+
active: entry.get("active"),
|
|
3865
|
+
priority: entry.get("priority"),
|
|
3866
|
+
created_at: entry.get("created_at"),
|
|
3867
|
+
updated_at: entry.get("updated_at")
|
|
3868
|
+
});
|
|
3869
|
+
});
|
|
3870
|
+
return records;
|
|
3871
|
+
}
|
|
3872
|
+
function onUpdate(callback) {
|
|
3873
|
+
const d = initCrdtDoc();
|
|
3874
|
+
const handler = (update) => callback(update);
|
|
3875
|
+
d.on("update", handler);
|
|
3876
|
+
return () => d.off("update", handler);
|
|
3877
|
+
}
|
|
3878
|
+
function persistState() {
|
|
3879
|
+
if (!doc) return;
|
|
3880
|
+
try {
|
|
3881
|
+
const sp = getStatePath();
|
|
3882
|
+
const dir = path14.dirname(sp);
|
|
3883
|
+
if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
|
|
3884
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
3885
|
+
writeFileSync7(sp, Buffer.from(state));
|
|
3886
|
+
} catch {
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function isCrdtSyncEnabled() {
|
|
3890
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
3891
|
+
}
|
|
3892
|
+
async function rebuildFromDb() {
|
|
3893
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3894
|
+
const client = getClient2();
|
|
3895
|
+
const result = await client.execute(
|
|
3896
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
3897
|
+
);
|
|
3898
|
+
const memories = result.rows.map((row) => ({
|
|
3899
|
+
id: String(row.id),
|
|
3900
|
+
agent_id: row.agent_id,
|
|
3901
|
+
agent_role: row.agent_role,
|
|
3902
|
+
session_id: row.session_id,
|
|
3903
|
+
timestamp: row.timestamp,
|
|
3904
|
+
tool_name: row.tool_name,
|
|
3905
|
+
project_name: row.project_name,
|
|
3906
|
+
has_error: row.has_error,
|
|
3907
|
+
raw_text: row.raw_text,
|
|
3908
|
+
version: row.version,
|
|
3909
|
+
author_device_id: row.author_device_id,
|
|
3910
|
+
scope: row.scope
|
|
3911
|
+
}));
|
|
3912
|
+
const count = importExistingMemories(memories);
|
|
3913
|
+
persistState();
|
|
3914
|
+
return count;
|
|
3915
|
+
}
|
|
3916
|
+
function destroyCrdtDoc() {
|
|
3917
|
+
if (doc) {
|
|
3918
|
+
doc.destroy();
|
|
3919
|
+
doc = null;
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
3923
|
+
var init_crdt_sync = __esm({
|
|
3924
|
+
"src/lib/crdt-sync.ts"() {
|
|
3925
|
+
"use strict";
|
|
3926
|
+
DEFAULT_STATE_PATH = path14.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
3927
|
+
_statePathOverride = null;
|
|
3928
|
+
doc = null;
|
|
3929
|
+
}
|
|
3930
|
+
});
|
|
3931
|
+
|
|
3369
3932
|
// src/lib/cloud-sync.ts
|
|
3370
3933
|
var cloud_sync_exports = {};
|
|
3371
3934
|
__export(cloud_sync_exports, {
|
|
@@ -3394,16 +3957,16 @@ __export(cloud_sync_exports, {
|
|
|
3394
3957
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
3395
3958
|
recordRosterDeletion: () => recordRosterDeletion
|
|
3396
3959
|
});
|
|
3397
|
-
import { readFileSync as
|
|
3960
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync4, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3398
3961
|
import crypto3 from "crypto";
|
|
3399
|
-
import
|
|
3400
|
-
import { homedir } from "os";
|
|
3962
|
+
import path15 from "path";
|
|
3963
|
+
import { homedir as homedir2 } from "os";
|
|
3401
3964
|
function sqlSafe(v) {
|
|
3402
3965
|
return v === void 0 ? null : v;
|
|
3403
3966
|
}
|
|
3404
3967
|
function logError(msg) {
|
|
3405
3968
|
try {
|
|
3406
|
-
const logPath =
|
|
3969
|
+
const logPath = path15.join(homedir2(), ".exe-os", "workers.log");
|
|
3407
3970
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3408
3971
|
`);
|
|
3409
3972
|
} catch {
|
|
@@ -3413,18 +3976,18 @@ async function withRosterLock(fn) {
|
|
|
3413
3976
|
try {
|
|
3414
3977
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3415
3978
|
closeSync2(fd);
|
|
3416
|
-
|
|
3979
|
+
writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3417
3980
|
} catch (err) {
|
|
3418
3981
|
if (err.code === "EEXIST") {
|
|
3419
3982
|
try {
|
|
3420
|
-
const ts = parseInt(
|
|
3983
|
+
const ts = parseInt(readFileSync11(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3421
3984
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3422
3985
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3423
3986
|
}
|
|
3424
|
-
|
|
3987
|
+
unlinkSync6(ROSTER_LOCK_PATH);
|
|
3425
3988
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3426
3989
|
closeSync2(fd);
|
|
3427
|
-
|
|
3990
|
+
writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3428
3991
|
} catch (retryErr) {
|
|
3429
3992
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3430
3993
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3437,7 +4000,7 @@ async function withRosterLock(fn) {
|
|
|
3437
4000
|
return await fn();
|
|
3438
4001
|
} finally {
|
|
3439
4002
|
try {
|
|
3440
|
-
|
|
4003
|
+
unlinkSync6(ROSTER_LOCK_PATH);
|
|
3441
4004
|
} catch {
|
|
3442
4005
|
}
|
|
3443
4006
|
}
|
|
@@ -3582,29 +4145,75 @@ async function cloudSync(config) {
|
|
|
3582
4145
|
const pullResult = await cloudPull(lastPullVersion, config);
|
|
3583
4146
|
let pulled = 0;
|
|
3584
4147
|
if (pullResult.records.length > 0) {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
4148
|
+
if (isCrdtSyncEnabled()) {
|
|
4149
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
4150
|
+
initCrdtDoc2();
|
|
4151
|
+
importExistingMemories2(
|
|
4152
|
+
pullResult.records.map((rec) => ({
|
|
4153
|
+
id: String(rec.id ?? ""),
|
|
4154
|
+
agent_id: rec.agent_id,
|
|
4155
|
+
agent_role: rec.agent_role,
|
|
4156
|
+
session_id: rec.session_id,
|
|
4157
|
+
timestamp: rec.timestamp,
|
|
4158
|
+
tool_name: rec.tool_name,
|
|
4159
|
+
project_name: rec.project_name,
|
|
4160
|
+
has_error: rec.has_error ?? 0,
|
|
4161
|
+
raw_text: rec.raw_text ?? "",
|
|
4162
|
+
version: rec.version ?? 0,
|
|
4163
|
+
author_device_id: rec.author_device_id,
|
|
4164
|
+
scope: rec.scope ?? "business"
|
|
4165
|
+
}))
|
|
4166
|
+
);
|
|
4167
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
4168
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
4169
|
+
const stmts = merged.map((rec) => ({
|
|
4170
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
4171
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
4172
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
4173
|
+
author_device_id, scope)
|
|
4174
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4175
|
+
args: [
|
|
4176
|
+
sqlSafe(rec.id),
|
|
4177
|
+
sqlSafe(rec.agent_id),
|
|
4178
|
+
sqlSafe(rec.agent_role),
|
|
4179
|
+
sqlSafe(rec.session_id),
|
|
4180
|
+
sqlSafe(rec.timestamp),
|
|
4181
|
+
sqlSafe(rec.tool_name),
|
|
4182
|
+
sqlSafe(rec.project_name),
|
|
4183
|
+
sqlSafe(rec.has_error ?? 0),
|
|
4184
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
4185
|
+
sqlSafe(rec.version ?? 0),
|
|
4186
|
+
sqlSafe(rec.author_device_id),
|
|
4187
|
+
sqlSafe(rec.scope ?? "business")
|
|
4188
|
+
]
|
|
4189
|
+
}));
|
|
4190
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
4191
|
+
pulled = pullResult.records.length;
|
|
4192
|
+
} else {
|
|
4193
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
4194
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
4195
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
4196
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
4197
|
+
author_device_id, scope)
|
|
4198
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4199
|
+
args: [
|
|
4200
|
+
sqlSafe(rec.id),
|
|
4201
|
+
sqlSafe(rec.agent_id),
|
|
4202
|
+
sqlSafe(rec.agent_role),
|
|
4203
|
+
sqlSafe(rec.session_id),
|
|
4204
|
+
sqlSafe(rec.timestamp),
|
|
4205
|
+
sqlSafe(rec.tool_name),
|
|
4206
|
+
sqlSafe(rec.project_name),
|
|
4207
|
+
sqlSafe(rec.has_error ?? 0),
|
|
4208
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
4209
|
+
sqlSafe(rec.version ?? 0),
|
|
4210
|
+
sqlSafe(rec.author_device_id),
|
|
4211
|
+
sqlSafe(rec.scope ?? "business")
|
|
4212
|
+
]
|
|
4213
|
+
}));
|
|
4214
|
+
await client.batch(stmts, "write");
|
|
4215
|
+
pulled = pullResult.records.length;
|
|
4216
|
+
}
|
|
3608
4217
|
}
|
|
3609
4218
|
if (pullResult.maxVersion > lastPullVersion) {
|
|
3610
4219
|
await client.execute({
|
|
@@ -3752,8 +4361,8 @@ async function cloudSync(config) {
|
|
|
3752
4361
|
try {
|
|
3753
4362
|
const employees = await loadEmployees();
|
|
3754
4363
|
rosterResult.employees = employees.length;
|
|
3755
|
-
const idDir =
|
|
3756
|
-
if (
|
|
4364
|
+
const idDir = path15.join(EXE_AI_DIR, "identity");
|
|
4365
|
+
if (existsSync14(idDir)) {
|
|
3757
4366
|
rosterResult.identities = readdirSync4(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3758
4367
|
}
|
|
3759
4368
|
} catch {
|
|
@@ -3774,55 +4383,63 @@ async function cloudSync(config) {
|
|
|
3774
4383
|
function recordRosterDeletion(name) {
|
|
3775
4384
|
let deletions = [];
|
|
3776
4385
|
try {
|
|
3777
|
-
if (
|
|
3778
|
-
deletions = JSON.parse(
|
|
4386
|
+
if (existsSync14(ROSTER_DELETIONS_PATH)) {
|
|
4387
|
+
deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3779
4388
|
}
|
|
3780
4389
|
} catch {
|
|
3781
4390
|
}
|
|
3782
4391
|
if (!deletions.includes(name)) deletions.push(name);
|
|
3783
|
-
|
|
4392
|
+
writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
3784
4393
|
}
|
|
3785
4394
|
function consumeRosterDeletions() {
|
|
3786
4395
|
try {
|
|
3787
|
-
if (!
|
|
3788
|
-
const deletions = JSON.parse(
|
|
3789
|
-
|
|
4396
|
+
if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
|
|
4397
|
+
const deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4398
|
+
writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
|
|
3790
4399
|
return deletions;
|
|
3791
4400
|
} catch {
|
|
3792
4401
|
return [];
|
|
3793
4402
|
}
|
|
3794
4403
|
}
|
|
3795
4404
|
function buildRosterBlob(paths) {
|
|
3796
|
-
const rosterPath = paths?.rosterPath ??
|
|
3797
|
-
const identityDir = paths?.identityDir ??
|
|
3798
|
-
const configPath = paths?.configPath ??
|
|
4405
|
+
const rosterPath = paths?.rosterPath ?? path15.join(EXE_AI_DIR, "exe-employees.json");
|
|
4406
|
+
const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
|
|
4407
|
+
const configPath = paths?.configPath ?? path15.join(EXE_AI_DIR, "config.json");
|
|
3799
4408
|
let roster = [];
|
|
3800
|
-
if (
|
|
4409
|
+
if (existsSync14(rosterPath)) {
|
|
3801
4410
|
try {
|
|
3802
|
-
roster = JSON.parse(
|
|
4411
|
+
roster = JSON.parse(readFileSync11(rosterPath, "utf-8"));
|
|
3803
4412
|
} catch {
|
|
3804
4413
|
}
|
|
3805
4414
|
}
|
|
3806
4415
|
const identities = {};
|
|
3807
|
-
if (
|
|
4416
|
+
if (existsSync14(identityDir)) {
|
|
3808
4417
|
for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3809
4418
|
try {
|
|
3810
|
-
identities[file] =
|
|
4419
|
+
identities[file] = readFileSync11(path15.join(identityDir, file), "utf-8");
|
|
3811
4420
|
} catch {
|
|
3812
4421
|
}
|
|
3813
4422
|
}
|
|
3814
4423
|
}
|
|
3815
4424
|
let config;
|
|
3816
|
-
if (
|
|
4425
|
+
if (existsSync14(configPath)) {
|
|
3817
4426
|
try {
|
|
3818
|
-
config = JSON.parse(
|
|
4427
|
+
config = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
let agentConfig;
|
|
4432
|
+
const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
|
|
4433
|
+
if (existsSync14(agentConfigPath)) {
|
|
4434
|
+
try {
|
|
4435
|
+
agentConfig = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
|
|
3819
4436
|
} catch {
|
|
3820
4437
|
}
|
|
3821
4438
|
}
|
|
3822
4439
|
const deletedNames = consumeRosterDeletions();
|
|
3823
|
-
const content = JSON.stringify({ roster, identities, config, deletedNames });
|
|
4440
|
+
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
3824
4441
|
const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3825
|
-
return { roster, identities, config, deletedNames, version: hash };
|
|
4442
|
+
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
3826
4443
|
}
|
|
3827
4444
|
async function cloudPushRoster(config) {
|
|
3828
4445
|
assertSecureEndpoint(config.endpoint);
|
|
@@ -3891,23 +4508,23 @@ async function cloudPullRoster(config) {
|
|
|
3891
4508
|
}
|
|
3892
4509
|
}
|
|
3893
4510
|
function mergeConfig(remoteConfig, configPath) {
|
|
3894
|
-
const cfgPath = configPath ??
|
|
4511
|
+
const cfgPath = configPath ?? path15.join(EXE_AI_DIR, "config.json");
|
|
3895
4512
|
let local = {};
|
|
3896
|
-
if (
|
|
4513
|
+
if (existsSync14(cfgPath)) {
|
|
3897
4514
|
try {
|
|
3898
|
-
local = JSON.parse(
|
|
4515
|
+
local = JSON.parse(readFileSync11(cfgPath, "utf-8"));
|
|
3899
4516
|
} catch {
|
|
3900
4517
|
}
|
|
3901
4518
|
}
|
|
3902
4519
|
const merged = { ...remoteConfig, ...local };
|
|
3903
|
-
const dir =
|
|
3904
|
-
if (!
|
|
3905
|
-
|
|
4520
|
+
const dir = path15.dirname(cfgPath);
|
|
4521
|
+
if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
|
|
4522
|
+
writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3906
4523
|
}
|
|
3907
4524
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3908
4525
|
return withRosterLock(async () => {
|
|
3909
4526
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3910
|
-
const identityDir = paths?.identityDir ??
|
|
4527
|
+
const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
|
|
3911
4528
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3912
4529
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3913
4530
|
let added = 0;
|
|
@@ -3928,15 +4545,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3928
4545
|
) ?? lookupKey;
|
|
3929
4546
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3930
4547
|
if (remoteIdentity) {
|
|
3931
|
-
if (!
|
|
3932
|
-
const idPath =
|
|
4548
|
+
if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
|
|
4549
|
+
const idPath = path15.join(identityDir, `${remoteEmp.name}.md`);
|
|
3933
4550
|
let localIdentity = null;
|
|
3934
4551
|
try {
|
|
3935
|
-
localIdentity =
|
|
4552
|
+
localIdentity = existsSync14(idPath) ? readFileSync11(idPath, "utf-8") : null;
|
|
3936
4553
|
} catch {
|
|
3937
4554
|
}
|
|
3938
4555
|
if (localIdentity !== remoteIdentity) {
|
|
3939
|
-
|
|
4556
|
+
writeFileSync8(idPath, remoteIdentity, "utf-8");
|
|
3940
4557
|
identitiesUpdated++;
|
|
3941
4558
|
}
|
|
3942
4559
|
}
|
|
@@ -3960,6 +4577,21 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3960
4577
|
} catch {
|
|
3961
4578
|
}
|
|
3962
4579
|
}
|
|
4580
|
+
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
4581
|
+
try {
|
|
4582
|
+
const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
|
|
4583
|
+
let local = {};
|
|
4584
|
+
if (existsSync14(agentConfigPath)) {
|
|
4585
|
+
try {
|
|
4586
|
+
local = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
|
|
4587
|
+
} catch {
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
const merged = { ...remote.agentConfig, ...local };
|
|
4591
|
+
writeFileSync8(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4592
|
+
} catch {
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
3963
4595
|
return { added, identitiesUpdated };
|
|
3964
4596
|
});
|
|
3965
4597
|
}
|
|
@@ -4389,13 +5021,14 @@ var init_cloud_sync = __esm({
|
|
|
4389
5021
|
init_compress();
|
|
4390
5022
|
init_license();
|
|
4391
5023
|
init_config();
|
|
5024
|
+
init_crdt_sync();
|
|
4392
5025
|
init_employees();
|
|
4393
5026
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4394
5027
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4395
5028
|
PUSH_BATCH_SIZE = 5e3;
|
|
4396
|
-
ROSTER_LOCK_PATH =
|
|
5029
|
+
ROSTER_LOCK_PATH = path15.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4397
5030
|
LOCK_STALE_MS = 3e4;
|
|
4398
|
-
ROSTER_DELETIONS_PATH =
|
|
5031
|
+
ROSTER_DELETIONS_PATH = path15.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4399
5032
|
}
|
|
4400
5033
|
});
|
|
4401
5034
|
|
|
@@ -4405,6 +5038,7 @@ init_database();
|
|
|
4405
5038
|
init_keychain();
|
|
4406
5039
|
init_config();
|
|
4407
5040
|
init_state_bus();
|
|
5041
|
+
import { createHash } from "crypto";
|
|
4408
5042
|
var INIT_MAX_RETRIES = 3;
|
|
4409
5043
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
4410
5044
|
function isBusyError2(err) {
|
|
@@ -4486,12 +5120,52 @@ function classifyTier(record) {
|
|
|
4486
5120
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4487
5121
|
return 3;
|
|
4488
5122
|
}
|
|
5123
|
+
function inferFilePaths(record) {
|
|
5124
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5125
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5126
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5127
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5128
|
+
}
|
|
5129
|
+
function inferCommitHash(record) {
|
|
5130
|
+
if (record.tool_name !== "Bash") return null;
|
|
5131
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5132
|
+
return match ? match[1] : null;
|
|
5133
|
+
}
|
|
5134
|
+
function inferLanguageType(record) {
|
|
5135
|
+
const text = record.raw_text;
|
|
5136
|
+
if (!text || text.length < 10) return null;
|
|
5137
|
+
const trimmed = text.trimStart();
|
|
5138
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5139
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5140
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5141
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5142
|
+
return "mixed";
|
|
5143
|
+
}
|
|
5144
|
+
function inferDomain(record) {
|
|
5145
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5146
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5147
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5148
|
+
return null;
|
|
5149
|
+
}
|
|
4489
5150
|
async function writeMemory(record) {
|
|
4490
5151
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4491
5152
|
throw new Error(
|
|
4492
5153
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4493
5154
|
);
|
|
4494
5155
|
}
|
|
5156
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5157
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5158
|
+
return;
|
|
5159
|
+
}
|
|
5160
|
+
try {
|
|
5161
|
+
const client = getClient();
|
|
5162
|
+
const existing = await client.execute({
|
|
5163
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5164
|
+
args: [contentHash, record.agent_id]
|
|
5165
|
+
});
|
|
5166
|
+
if (existing.rows.length > 0) return;
|
|
5167
|
+
} catch {
|
|
5168
|
+
}
|
|
4495
5169
|
const dbRow = {
|
|
4496
5170
|
id: record.id,
|
|
4497
5171
|
agent_id: record.agent_id,
|
|
@@ -4521,7 +5195,23 @@ async function writeMemory(record) {
|
|
|
4521
5195
|
supersedes_id: record.supersedes_id ?? null,
|
|
4522
5196
|
draft: record.draft ? 1 : 0,
|
|
4523
5197
|
memory_type: record.memory_type ?? "raw",
|
|
4524
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5198
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5199
|
+
content_hash: contentHash,
|
|
5200
|
+
intent: record.intent ?? null,
|
|
5201
|
+
outcome: record.outcome ?? null,
|
|
5202
|
+
domain: record.domain ?? inferDomain(record),
|
|
5203
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5204
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5205
|
+
chain_position: record.chain_position ?? null,
|
|
5206
|
+
review_status: record.review_status ?? null,
|
|
5207
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5208
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5209
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5210
|
+
duration_ms: record.duration_ms ?? null,
|
|
5211
|
+
token_cost: record.token_cost ?? null,
|
|
5212
|
+
audience: record.audience ?? null,
|
|
5213
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5214
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4525
5215
|
};
|
|
4526
5216
|
_pendingRecords.push(dbRow);
|
|
4527
5217
|
orgBus.emit({
|
|
@@ -4579,80 +5269,85 @@ async function flushBatch() {
|
|
|
4579
5269
|
const draft = row.draft ? 1 : 0;
|
|
4580
5270
|
const memoryType = row.memory_type ?? "raw";
|
|
4581
5271
|
const trajectory = row.trajectory ?? null;
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
5272
|
+
const contentHash = row.content_hash ?? null;
|
|
5273
|
+
const intent = row.intent ?? null;
|
|
5274
|
+
const outcome = row.outcome ?? null;
|
|
5275
|
+
const domain = row.domain ?? null;
|
|
5276
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5277
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5278
|
+
const chainPosition = row.chain_position ?? null;
|
|
5279
|
+
const reviewStatus = row.review_status ?? null;
|
|
5280
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5281
|
+
const filePaths = row.file_paths ?? null;
|
|
5282
|
+
const commitHash = row.commit_hash ?? null;
|
|
5283
|
+
const durationMs = row.duration_ms ?? null;
|
|
5284
|
+
const tokenCost = row.token_cost ?? null;
|
|
5285
|
+
const audience = row.audience ?? null;
|
|
5286
|
+
const languageType = row.language_type ?? null;
|
|
5287
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5288
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4585
5289
|
tool_name, project_name,
|
|
4586
5290
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4587
5291
|
confidence, last_accessed,
|
|
4588
5292
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4589
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
pageNumber,
|
|
4648
|
-
sourcePath,
|
|
4649
|
-
sourceType,
|
|
4650
|
-
tier,
|
|
4651
|
-
supersedesId,
|
|
4652
|
-
draft,
|
|
4653
|
-
memoryType,
|
|
4654
|
-
trajectory
|
|
4655
|
-
]
|
|
5293
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5294
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5295
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5296
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5297
|
+
const metaArgs = [
|
|
5298
|
+
intent,
|
|
5299
|
+
outcome,
|
|
5300
|
+
domain,
|
|
5301
|
+
referencedEntities,
|
|
5302
|
+
retrievalCount,
|
|
5303
|
+
chainPosition,
|
|
5304
|
+
reviewStatus,
|
|
5305
|
+
contextWindowPct,
|
|
5306
|
+
filePaths,
|
|
5307
|
+
commitHash,
|
|
5308
|
+
durationMs,
|
|
5309
|
+
tokenCost,
|
|
5310
|
+
audience,
|
|
5311
|
+
languageType,
|
|
5312
|
+
parentMemoryId
|
|
5313
|
+
];
|
|
5314
|
+
const baseArgs = [
|
|
5315
|
+
row.id,
|
|
5316
|
+
row.agent_id,
|
|
5317
|
+
row.agent_role,
|
|
5318
|
+
row.session_id,
|
|
5319
|
+
row.timestamp,
|
|
5320
|
+
row.tool_name,
|
|
5321
|
+
row.project_name,
|
|
5322
|
+
row.has_error,
|
|
5323
|
+
row.raw_text
|
|
5324
|
+
];
|
|
5325
|
+
const sharedArgs = [
|
|
5326
|
+
row.version,
|
|
5327
|
+
taskId,
|
|
5328
|
+
importance,
|
|
5329
|
+
status,
|
|
5330
|
+
confidence,
|
|
5331
|
+
lastAccessed,
|
|
5332
|
+
workspaceId,
|
|
5333
|
+
documentId,
|
|
5334
|
+
userId,
|
|
5335
|
+
charOffset,
|
|
5336
|
+
pageNumber,
|
|
5337
|
+
sourcePath,
|
|
5338
|
+
sourceType,
|
|
5339
|
+
tier,
|
|
5340
|
+
supersedesId,
|
|
5341
|
+
draft,
|
|
5342
|
+
memoryType,
|
|
5343
|
+
trajectory,
|
|
5344
|
+
contentHash
|
|
5345
|
+
];
|
|
5346
|
+
return {
|
|
5347
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5348
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5349
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5350
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4656
5351
|
};
|
|
4657
5352
|
};
|
|
4658
5353
|
const globalClient = getClient();
|
|
@@ -4700,8 +5395,8 @@ init_task_scope();
|
|
|
4700
5395
|
init_employees();
|
|
4701
5396
|
import crypto4 from "crypto";
|
|
4702
5397
|
import { execSync as execSync4 } from "child_process";
|
|
4703
|
-
import { existsSync as
|
|
4704
|
-
import
|
|
5398
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
|
|
5399
|
+
import path16 from "path";
|
|
4705
5400
|
async function main() {
|
|
4706
5401
|
const agentId = process.env.AGENT_ID ?? "default";
|
|
4707
5402
|
const agentRole = process.env.AGENT_ROLE ?? "employee";
|
|
@@ -4836,8 +5531,8 @@ async function main() {
|
|
|
4836
5531
|
}
|
|
4837
5532
|
try {
|
|
4838
5533
|
const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4839
|
-
const flagPath =
|
|
4840
|
-
if (
|
|
5534
|
+
const flagPath = path16.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
|
|
5535
|
+
if (existsSync15(flagPath)) {
|
|
4841
5536
|
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
4842
5537
|
if (!tryAcquireWorkerSlot2()) {
|
|
4843
5538
|
process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
|
|
@@ -4845,11 +5540,11 @@ async function main() {
|
|
|
4845
5540
|
const { spawn: spawn2 } = await import("child_process");
|
|
4846
5541
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
4847
5542
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
4848
|
-
const backfillPath =
|
|
4849
|
-
if (
|
|
5543
|
+
const backfillPath = path16.resolve(path16.dirname(thisFile), "backfill-vectors.js");
|
|
5544
|
+
if (existsSync15(backfillPath)) {
|
|
4850
5545
|
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4851
|
-
const bLogPath =
|
|
4852
|
-
|
|
5546
|
+
const bLogPath = path16.join(exeDir2, "workers.log");
|
|
5547
|
+
mkdirSync9(path16.dirname(bLogPath), { recursive: true });
|
|
4853
5548
|
const bLogFd = openSync3(bLogPath, "a");
|
|
4854
5549
|
const child = spawn2(process.execPath, [backfillPath], {
|
|
4855
5550
|
detached: true,
|