@askexenow/exe-os 0.8.83 → 0.8.85
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 +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- 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 +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- 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 +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- 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 +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- 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 +120 -22
- 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 +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
|
@@ -428,387 +428,940 @@ var init_employees = __esm({
|
|
|
428
428
|
}
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
-
// src/lib/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
import { createClient } from "@libsql/client";
|
|
444
|
-
async function initDatabase(config) {
|
|
445
|
-
if (_client) {
|
|
446
|
-
_client.close();
|
|
447
|
-
_client = null;
|
|
448
|
-
_resilientClient = null;
|
|
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;
|
|
449
443
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
+
}
|
|
455
461
|
}
|
|
456
|
-
_client = createClient(opts);
|
|
457
|
-
_resilientClient = wrapWithRetry(_client);
|
|
458
|
-
}
|
|
459
|
-
function isInitialized() {
|
|
460
|
-
return _client !== null;
|
|
461
462
|
}
|
|
462
|
-
function
|
|
463
|
-
if (
|
|
464
|
-
|
|
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
|
+
}
|
|
465
484
|
}
|
|
466
|
-
return _resilientClient;
|
|
467
485
|
}
|
|
468
|
-
function
|
|
469
|
-
|
|
470
|
-
|
|
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);
|
|
471
492
|
}
|
|
472
|
-
return
|
|
493
|
+
return null;
|
|
473
494
|
}
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
try {
|
|
480
|
-
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
481
|
-
} catch {
|
|
495
|
+
function spawnDaemon() {
|
|
496
|
+
const pkgRoot = findPackageRoot();
|
|
497
|
+
if (!pkgRoot) {
|
|
498
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
499
|
+
return;
|
|
482
500
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
session_id TEXT NOT NULL,
|
|
489
|
-
timestamp TEXT NOT NULL,
|
|
490
|
-
tool_name TEXT NOT NULL,
|
|
491
|
-
project_name TEXT NOT NULL,
|
|
492
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
493
|
-
raw_text TEXT NOT NULL,
|
|
494
|
-
vector F32_BLOB(1024),
|
|
495
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
499
|
-
ON memories(agent_id);
|
|
500
|
-
|
|
501
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
502
|
-
ON memories(timestamp);
|
|
503
|
-
|
|
504
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
505
|
-
ON memories(session_id);
|
|
506
|
-
|
|
507
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
508
|
-
ON memories(project_name);
|
|
509
|
-
|
|
510
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
511
|
-
ON memories(tool_name);
|
|
512
|
-
|
|
513
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
514
|
-
ON memories(version);
|
|
515
|
-
|
|
516
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
517
|
-
ON memories(agent_id, project_name);
|
|
518
|
-
`);
|
|
519
|
-
await client.executeMultiple(`
|
|
520
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
521
|
-
raw_text,
|
|
522
|
-
content='memories',
|
|
523
|
-
content_rowid='rowid'
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
527
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
528
|
-
END;
|
|
529
|
-
|
|
530
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
531
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
532
|
-
END;
|
|
533
|
-
|
|
534
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
535
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
536
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
537
|
-
END;
|
|
538
|
-
`);
|
|
539
|
-
await client.executeMultiple(`
|
|
540
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
541
|
-
key TEXT PRIMARY KEY,
|
|
542
|
-
value TEXT NOT NULL
|
|
543
|
-
);
|
|
544
|
-
`);
|
|
545
|
-
await client.executeMultiple(`
|
|
546
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
547
|
-
id TEXT PRIMARY KEY,
|
|
548
|
-
title TEXT NOT NULL,
|
|
549
|
-
assigned_to TEXT NOT NULL,
|
|
550
|
-
assigned_by TEXT NOT NULL,
|
|
551
|
-
project_name TEXT NOT NULL,
|
|
552
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
553
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
554
|
-
task_file TEXT,
|
|
555
|
-
created_at TEXT NOT NULL,
|
|
556
|
-
updated_at TEXT NOT NULL
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
560
|
-
ON tasks(assigned_to, status);
|
|
561
|
-
`);
|
|
562
|
-
await client.executeMultiple(`
|
|
563
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
564
|
-
id TEXT PRIMARY KEY,
|
|
565
|
-
agent_id TEXT NOT NULL,
|
|
566
|
-
project_name TEXT,
|
|
567
|
-
domain TEXT,
|
|
568
|
-
content TEXT NOT NULL,
|
|
569
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
570
|
-
created_at TEXT NOT NULL,
|
|
571
|
-
updated_at TEXT NOT NULL
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
575
|
-
ON behaviors(agent_id, active);
|
|
576
|
-
`);
|
|
577
|
-
try {
|
|
578
|
-
const coordinatorName = getCoordinatorName();
|
|
579
|
-
const existing = await client.execute({
|
|
580
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
581
|
-
args: [coordinatorName]
|
|
582
|
-
});
|
|
583
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
584
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
585
|
-
for (const [domain, content] of [
|
|
586
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
587
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
588
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
589
|
-
]) {
|
|
590
|
-
await client.execute({
|
|
591
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
592
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
593
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
} catch {
|
|
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;
|
|
598
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";
|
|
599
512
|
try {
|
|
600
|
-
|
|
601
|
-
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
602
|
-
args: []
|
|
603
|
-
});
|
|
513
|
+
stderrFd = openSync(logPath, "a");
|
|
604
514
|
} catch {
|
|
605
515
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
+
}
|
|
612
535
|
}
|
|
536
|
+
}
|
|
537
|
+
function acquireSpawnLock() {
|
|
613
538
|
try {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
});
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
await client.execute({
|
|
622
|
-
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
623
|
-
ON tasks(parent_task_id)
|
|
624
|
-
WHERE parent_task_id IS NOT NULL`,
|
|
625
|
-
args: []
|
|
626
|
-
});
|
|
539
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
540
|
+
closeSync(fd);
|
|
541
|
+
return true;
|
|
627
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;
|
|
628
560
|
}
|
|
561
|
+
}
|
|
562
|
+
function releaseSpawnLock() {
|
|
629
563
|
try {
|
|
630
|
-
|
|
631
|
-
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
632
|
-
args: []
|
|
633
|
-
});
|
|
564
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
634
565
|
} catch {
|
|
635
566
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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);
|
|
640
599
|
});
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
await client.execute({
|
|
645
|
-
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
646
|
-
args: []
|
|
600
|
+
socket.on("error", () => {
|
|
601
|
+
clearTimeout(connectTimeout);
|
|
602
|
+
resolve(false);
|
|
647
603
|
});
|
|
648
|
-
}
|
|
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
|
+
}
|
|
649
616
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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);
|
|
656
623
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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;
|
|
663
655
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
}
|
|
670
671
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
args: []
|
|
675
|
-
});
|
|
676
|
-
} catch {
|
|
672
|
+
if (_socket) {
|
|
673
|
+
_socket.destroy();
|
|
674
|
+
_socket = null;
|
|
677
675
|
}
|
|
676
|
+
_connected = false;
|
|
677
|
+
_buffer = "";
|
|
678
678
|
try {
|
|
679
|
-
|
|
680
|
-
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
681
|
-
args: []
|
|
682
|
-
});
|
|
679
|
+
unlinkSync2(PID_PATH);
|
|
683
680
|
} catch {
|
|
684
681
|
}
|
|
685
682
|
try {
|
|
686
|
-
|
|
687
|
-
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
688
|
-
args: []
|
|
689
|
-
});
|
|
683
|
+
unlinkSync2(SOCKET_PATH);
|
|
690
684
|
} catch {
|
|
691
685
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
706
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
+
`);
|
|
705
725
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
function disconnectClient() {
|
|
729
|
+
if (_socket) {
|
|
730
|
+
_socket.destroy();
|
|
731
|
+
_socket = null;
|
|
712
732
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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" });
|
|
719
739
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
+
|
|
964
|
+
// src/lib/database.ts
|
|
965
|
+
var database_exports = {};
|
|
966
|
+
__export(database_exports, {
|
|
967
|
+
disposeDatabase: () => disposeDatabase,
|
|
968
|
+
disposeTurso: () => disposeTurso,
|
|
969
|
+
ensureSchema: () => ensureSchema,
|
|
970
|
+
getClient: () => getClient,
|
|
971
|
+
getRawClient: () => getRawClient,
|
|
972
|
+
initDaemonClient: () => initDaemonClient,
|
|
973
|
+
initDatabase: () => initDatabase,
|
|
974
|
+
initTurso: () => initTurso,
|
|
975
|
+
isInitialized: () => isInitialized
|
|
976
|
+
});
|
|
977
|
+
import { createClient } from "@libsql/client";
|
|
978
|
+
async function initDatabase(config) {
|
|
979
|
+
if (_client) {
|
|
980
|
+
_client.close();
|
|
981
|
+
_client = null;
|
|
982
|
+
_resilientClient = null;
|
|
983
|
+
}
|
|
984
|
+
const opts = {
|
|
985
|
+
url: `file:${config.dbPath}`
|
|
986
|
+
};
|
|
987
|
+
if (config.encryptionKey) {
|
|
988
|
+
opts.encryptionKey = config.encryptionKey;
|
|
989
|
+
}
|
|
990
|
+
_client = createClient(opts);
|
|
991
|
+
_resilientClient = wrapWithRetry(_client);
|
|
992
|
+
}
|
|
993
|
+
function isInitialized() {
|
|
994
|
+
return _client !== null;
|
|
995
|
+
}
|
|
996
|
+
function getClient() {
|
|
997
|
+
if (!_resilientClient) {
|
|
998
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
999
|
+
}
|
|
1000
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1001
|
+
return _resilientClient;
|
|
1002
|
+
}
|
|
1003
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1004
|
+
return _daemonClient;
|
|
1005
|
+
}
|
|
1006
|
+
return _resilientClient;
|
|
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
|
+
}
|
|
1021
|
+
function getRawClient() {
|
|
1022
|
+
if (!_client) {
|
|
1023
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1024
|
+
}
|
|
1025
|
+
return _client;
|
|
1026
|
+
}
|
|
1027
|
+
async function ensureSchema() {
|
|
1028
|
+
const client = getRawClient();
|
|
1029
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1030
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1031
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1032
|
+
try {
|
|
1033
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
await client.executeMultiple(`
|
|
1037
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
736
1038
|
id TEXT PRIMARY KEY,
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1039
|
+
agent_id TEXT NOT NULL,
|
|
1040
|
+
agent_role TEXT NOT NULL,
|
|
1041
|
+
session_id TEXT NOT NULL,
|
|
1042
|
+
timestamp TEXT NOT NULL,
|
|
1043
|
+
tool_name TEXT NOT NULL,
|
|
1044
|
+
project_name TEXT NOT NULL,
|
|
1045
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1046
|
+
raw_text TEXT NOT NULL,
|
|
1047
|
+
vector F32_BLOB(1024),
|
|
1048
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
741
1049
|
);
|
|
1050
|
+
|
|
1051
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1052
|
+
ON memories(agent_id);
|
|
1053
|
+
|
|
1054
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1055
|
+
ON memories(timestamp);
|
|
1056
|
+
|
|
1057
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1058
|
+
ON memories(session_id);
|
|
1059
|
+
|
|
1060
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1061
|
+
ON memories(project_name);
|
|
1062
|
+
|
|
1063
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1064
|
+
ON memories(tool_name);
|
|
1065
|
+
|
|
1066
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1067
|
+
ON memories(version);
|
|
1068
|
+
|
|
1069
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1070
|
+
ON memories(agent_id, project_name);
|
|
742
1071
|
`);
|
|
743
1072
|
await client.executeMultiple(`
|
|
744
|
-
CREATE TABLE IF NOT EXISTS
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
event TEXT NOT NULL,
|
|
749
|
-
project TEXT NOT NULL,
|
|
750
|
-
summary TEXT NOT NULL,
|
|
751
|
-
task_file TEXT,
|
|
752
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
753
|
-
created_at TEXT NOT NULL
|
|
1073
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1074
|
+
raw_text,
|
|
1075
|
+
content='memories',
|
|
1076
|
+
content_rowid='rowid'
|
|
754
1077
|
);
|
|
755
1078
|
|
|
756
|
-
CREATE
|
|
757
|
-
|
|
1079
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1080
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1081
|
+
END;
|
|
758
1082
|
|
|
759
|
-
CREATE
|
|
760
|
-
|
|
1083
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1084
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1085
|
+
END;
|
|
761
1086
|
|
|
762
|
-
CREATE
|
|
763
|
-
|
|
1087
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1088
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1089
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1090
|
+
END;
|
|
764
1091
|
`);
|
|
765
1092
|
await client.executeMultiple(`
|
|
766
|
-
CREATE TABLE IF NOT EXISTS
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
description TEXT NOT NULL,
|
|
770
|
-
job_type TEXT NOT NULL DEFAULT 'report',
|
|
771
|
-
prompt TEXT,
|
|
772
|
-
assigned_to TEXT,
|
|
773
|
-
project_name TEXT,
|
|
774
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
775
|
-
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
776
|
-
created_at TEXT NOT NULL
|
|
1093
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1094
|
+
key TEXT PRIMARY KEY,
|
|
1095
|
+
value TEXT NOT NULL
|
|
777
1096
|
);
|
|
778
1097
|
`);
|
|
779
1098
|
await client.executeMultiple(`
|
|
780
|
-
CREATE TABLE IF NOT EXISTS
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1099
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1100
|
+
id TEXT PRIMARY KEY,
|
|
1101
|
+
title TEXT NOT NULL,
|
|
1102
|
+
assigned_to TEXT NOT NULL,
|
|
1103
|
+
assigned_by TEXT NOT NULL,
|
|
1104
|
+
project_name TEXT NOT NULL,
|
|
1105
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1106
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1107
|
+
task_file TEXT,
|
|
1108
|
+
created_at TEXT NOT NULL,
|
|
1109
|
+
updated_at TEXT NOT NULL
|
|
788
1110
|
);
|
|
1111
|
+
|
|
1112
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1113
|
+
ON tasks(assigned_to, status);
|
|
789
1114
|
`);
|
|
790
1115
|
await client.executeMultiple(`
|
|
791
|
-
CREATE TABLE IF NOT EXISTS
|
|
792
|
-
id
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
status TEXT DEFAULT 'pending',
|
|
801
|
-
server_seq INTEGER,
|
|
802
|
-
retry_count INTEGER DEFAULT 0,
|
|
803
|
-
created_at TEXT NOT NULL,
|
|
804
|
-
delivered_at TEXT,
|
|
805
|
-
processed_at TEXT,
|
|
806
|
-
failed_at TEXT,
|
|
807
|
-
failure_reason TEXT
|
|
1116
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1117
|
+
id TEXT PRIMARY KEY,
|
|
1118
|
+
agent_id TEXT NOT NULL,
|
|
1119
|
+
project_name TEXT,
|
|
1120
|
+
domain TEXT,
|
|
1121
|
+
content TEXT NOT NULL,
|
|
1122
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1123
|
+
created_at TEXT NOT NULL,
|
|
1124
|
+
updated_at TEXT NOT NULL
|
|
808
1125
|
);
|
|
809
1126
|
|
|
810
|
-
CREATE INDEX IF NOT EXISTS
|
|
811
|
-
ON
|
|
1127
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1128
|
+
ON behaviors(agent_id, active);
|
|
1129
|
+
`);
|
|
1130
|
+
try {
|
|
1131
|
+
const coordinatorName = getCoordinatorName();
|
|
1132
|
+
const existing = await client.execute({
|
|
1133
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1134
|
+
args: [coordinatorName]
|
|
1135
|
+
});
|
|
1136
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1137
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1138
|
+
for (const [domain, content] of [
|
|
1139
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1140
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1141
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1142
|
+
]) {
|
|
1143
|
+
await client.execute({
|
|
1144
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1145
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1146
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
try {
|
|
1153
|
+
await client.execute({
|
|
1154
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1155
|
+
args: []
|
|
1156
|
+
});
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
try {
|
|
1160
|
+
await client.execute({
|
|
1161
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1162
|
+
args: []
|
|
1163
|
+
});
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
try {
|
|
1167
|
+
await client.execute({
|
|
1168
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1169
|
+
args: []
|
|
1170
|
+
});
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
await client.execute({
|
|
1175
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1176
|
+
ON tasks(parent_task_id)
|
|
1177
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1178
|
+
args: []
|
|
1179
|
+
});
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
await client.execute({
|
|
1184
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1185
|
+
args: []
|
|
1186
|
+
});
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
await client.execute({
|
|
1191
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1192
|
+
args: []
|
|
1193
|
+
});
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
try {
|
|
1197
|
+
await client.execute({
|
|
1198
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1199
|
+
args: []
|
|
1200
|
+
});
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
try {
|
|
1204
|
+
await client.execute({
|
|
1205
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1206
|
+
args: []
|
|
1207
|
+
});
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1210
|
+
try {
|
|
1211
|
+
await client.execute({
|
|
1212
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1213
|
+
args: []
|
|
1214
|
+
});
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
try {
|
|
1218
|
+
await client.execute({
|
|
1219
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1220
|
+
args: []
|
|
1221
|
+
});
|
|
1222
|
+
} catch {
|
|
1223
|
+
}
|
|
1224
|
+
try {
|
|
1225
|
+
await client.execute({
|
|
1226
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1227
|
+
args: []
|
|
1228
|
+
});
|
|
1229
|
+
} catch {
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
await client.execute({
|
|
1233
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1234
|
+
args: []
|
|
1235
|
+
});
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
await client.execute({
|
|
1240
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1241
|
+
args: []
|
|
1242
|
+
});
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
await client.execute({
|
|
1247
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1248
|
+
args: []
|
|
1249
|
+
});
|
|
1250
|
+
} catch {
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
await client.execute({
|
|
1254
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1255
|
+
args: []
|
|
1256
|
+
});
|
|
1257
|
+
} catch {
|
|
1258
|
+
}
|
|
1259
|
+
try {
|
|
1260
|
+
await client.execute({
|
|
1261
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1262
|
+
args: []
|
|
1263
|
+
});
|
|
1264
|
+
} catch {
|
|
1265
|
+
}
|
|
1266
|
+
try {
|
|
1267
|
+
await client.execute({
|
|
1268
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1269
|
+
args: []
|
|
1270
|
+
});
|
|
1271
|
+
} catch {
|
|
1272
|
+
}
|
|
1273
|
+
await client.executeMultiple(`
|
|
1274
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1275
|
+
id TEXT PRIMARY KEY,
|
|
1276
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1277
|
+
source_memory_id TEXT NOT NULL,
|
|
1278
|
+
created_at TEXT NOT NULL
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1282
|
+
ON consolidations(source_memory_id);
|
|
1283
|
+
|
|
1284
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1285
|
+
ON consolidations(consolidated_memory_id);
|
|
1286
|
+
`);
|
|
1287
|
+
await client.executeMultiple(`
|
|
1288
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1289
|
+
id TEXT PRIMARY KEY,
|
|
1290
|
+
text TEXT NOT NULL,
|
|
1291
|
+
created_at TEXT NOT NULL,
|
|
1292
|
+
due_date TEXT,
|
|
1293
|
+
completed_at TEXT
|
|
1294
|
+
);
|
|
1295
|
+
`);
|
|
1296
|
+
await client.executeMultiple(`
|
|
1297
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1298
|
+
id TEXT PRIMARY KEY,
|
|
1299
|
+
agent_id TEXT NOT NULL,
|
|
1300
|
+
agent_role TEXT NOT NULL,
|
|
1301
|
+
event TEXT NOT NULL,
|
|
1302
|
+
project TEXT NOT NULL,
|
|
1303
|
+
summary TEXT NOT NULL,
|
|
1304
|
+
task_file TEXT,
|
|
1305
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1306
|
+
created_at TEXT NOT NULL
|
|
1307
|
+
);
|
|
1308
|
+
|
|
1309
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1310
|
+
ON notifications(read);
|
|
1311
|
+
|
|
1312
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1313
|
+
ON notifications(agent_id);
|
|
1314
|
+
|
|
1315
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1316
|
+
ON notifications(task_file);
|
|
1317
|
+
`);
|
|
1318
|
+
await client.executeMultiple(`
|
|
1319
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1320
|
+
id TEXT PRIMARY KEY,
|
|
1321
|
+
cron TEXT NOT NULL,
|
|
1322
|
+
description TEXT NOT NULL,
|
|
1323
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1324
|
+
prompt TEXT,
|
|
1325
|
+
assigned_to TEXT,
|
|
1326
|
+
project_name TEXT,
|
|
1327
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1328
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1329
|
+
created_at TEXT NOT NULL
|
|
1330
|
+
);
|
|
1331
|
+
`);
|
|
1332
|
+
await client.executeMultiple(`
|
|
1333
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1334
|
+
device_id TEXT PRIMARY KEY,
|
|
1335
|
+
friendly_name TEXT NOT NULL,
|
|
1336
|
+
hostname TEXT NOT NULL,
|
|
1337
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1338
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1339
|
+
connected INTEGER DEFAULT 0,
|
|
1340
|
+
last_seen TEXT NOT NULL
|
|
1341
|
+
);
|
|
1342
|
+
`);
|
|
1343
|
+
await client.executeMultiple(`
|
|
1344
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1345
|
+
id TEXT PRIMARY KEY,
|
|
1346
|
+
from_agent TEXT NOT NULL,
|
|
1347
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
1348
|
+
target_agent TEXT NOT NULL,
|
|
1349
|
+
target_project TEXT,
|
|
1350
|
+
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1351
|
+
content TEXT NOT NULL,
|
|
1352
|
+
priority TEXT DEFAULT 'normal',
|
|
1353
|
+
status TEXT DEFAULT 'pending',
|
|
1354
|
+
server_seq INTEGER,
|
|
1355
|
+
retry_count INTEGER DEFAULT 0,
|
|
1356
|
+
created_at TEXT NOT NULL,
|
|
1357
|
+
delivered_at TEXT,
|
|
1358
|
+
processed_at TEXT,
|
|
1359
|
+
failed_at TEXT,
|
|
1360
|
+
failure_reason TEXT
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1364
|
+
ON messages(target_agent, status);
|
|
812
1365
|
|
|
813
1366
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
814
1367
|
ON messages(target_agent, from_agent, server_seq);
|
|
@@ -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,8 +1766,35 @@ async function ensureSchema() {
|
|
|
1184
1766
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1185
1767
|
ON conversations(channel_id);
|
|
1186
1768
|
`);
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
+
}
|
|
1796
|
+
try {
|
|
1797
|
+
await client.execute({
|
|
1189
1798
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1190
1799
|
args: []
|
|
1191
1800
|
});
|
|
@@ -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
|
|
|
@@ -2159,16 +2803,16 @@ var init_provider_table = __esm({
|
|
|
2159
2803
|
});
|
|
2160
2804
|
|
|
2161
2805
|
// src/lib/intercom-queue.ts
|
|
2162
|
-
import { readFileSync as
|
|
2163
|
-
import
|
|
2806
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
2807
|
+
import path8 from "path";
|
|
2164
2808
|
import os6 from "os";
|
|
2165
2809
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
2166
2810
|
var init_intercom_queue = __esm({
|
|
2167
2811
|
"src/lib/intercom-queue.ts"() {
|
|
2168
2812
|
"use strict";
|
|
2169
|
-
QUEUE_PATH =
|
|
2813
|
+
QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
2170
2814
|
TTL_MS = 60 * 60 * 1e3;
|
|
2171
|
-
INTERCOM_LOG =
|
|
2815
|
+
INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
2172
2816
|
}
|
|
2173
2817
|
});
|
|
2174
2818
|
|
|
@@ -2185,927 +2829,582 @@ __export(license_exports, {
|
|
|
2185
2829
|
loadLicense: () => loadLicense,
|
|
2186
2830
|
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
2187
2831
|
saveLicense: () => saveLicense,
|
|
2188
|
-
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
2189
|
-
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2190
|
-
validateLicense: () => validateLicense
|
|
2191
|
-
});
|
|
2192
|
-
import { readFileSync as
|
|
2193
|
-
import { randomUUID as
|
|
2194
|
-
import
|
|
2195
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
2196
|
-
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
|
-
);
|
|
2832
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
2833
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2834
|
+
validateLicense: () => validateLicense
|
|
2835
|
+
});
|
|
2836
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
2837
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2838
|
+
import path9 from "path";
|
|
2839
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
2840
|
+
async function fetchRetry(url, init) {
|
|
2841
|
+
try {
|
|
2842
|
+
return await fetch(url, init);
|
|
2843
|
+
} catch {
|
|
2844
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
2845
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
2467
2846
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const
|
|
2471
|
-
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
2847
|
+
}
|
|
2848
|
+
function loadDeviceId() {
|
|
2849
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2472
2850
|
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
|
-
}
|
|
2851
|
+
if (existsSync8(deviceJsonPath)) {
|
|
2852
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
2853
|
+
if (data.deviceId) return data.deviceId;
|
|
2498
2854
|
}
|
|
2499
2855
|
} catch {
|
|
2500
2856
|
}
|
|
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 {
|
|
2857
|
+
try {
|
|
2858
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
2859
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
2860
|
+
if (id2) return id2;
|
|
2514
2861
|
}
|
|
2515
|
-
}
|
|
2516
|
-
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
2517
|
-
_revalTimer.unref();
|
|
2862
|
+
} catch {
|
|
2518
2863
|
}
|
|
2864
|
+
const id = randomUUID3();
|
|
2865
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2866
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2867
|
+
return id;
|
|
2519
2868
|
}
|
|
2520
|
-
function
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2869
|
+
function loadLicense() {
|
|
2870
|
+
try {
|
|
2871
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
2872
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
2873
|
+
} catch {
|
|
2874
|
+
return null;
|
|
2524
2875
|
}
|
|
2525
2876
|
}
|
|
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() {
|
|
2877
|
+
function saveLicense(apiKey) {
|
|
2878
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
2879
|
+
writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2880
|
+
}
|
|
2881
|
+
async function verifyLicenseJwt(token) {
|
|
2576
2882
|
try {
|
|
2577
|
-
|
|
2578
|
-
const
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
if (parts.length !== 3) return freeLicense();
|
|
2582
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2883
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2884
|
+
const { payload } = await jwtVerify(token, key, {
|
|
2885
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
2886
|
+
});
|
|
2583
2887
|
const plan = payload.plan ?? "free";
|
|
2888
|
+
const email = payload.sub ?? "";
|
|
2584
2889
|
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2585
2890
|
return {
|
|
2586
2891
|
valid: true,
|
|
2587
2892
|
plan,
|
|
2588
|
-
email
|
|
2893
|
+
email,
|
|
2589
2894
|
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2590
2895
|
deviceLimit: limits.devices,
|
|
2591
2896
|
employeeLimit: limits.employees,
|
|
2592
2897
|
memoryLimit: limits.memories
|
|
2593
2898
|
};
|
|
2594
2899
|
} 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
|
-
);
|
|
2900
|
+
return null;
|
|
2637
2901
|
}
|
|
2638
2902
|
}
|
|
2639
|
-
function
|
|
2640
|
-
const license = getLicenseSync();
|
|
2641
|
-
if (license.employeeLimit < 0) return;
|
|
2642
|
-
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2643
|
-
let count = 0;
|
|
2903
|
+
async function getCachedLicense() {
|
|
2644
2904
|
try {
|
|
2645
|
-
if (existsSync8(
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
}
|
|
2905
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
2906
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2907
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2908
|
+
return await verifyLicenseJwt(raw.token);
|
|
2650
2909
|
} 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
|
-
);
|
|
2910
|
+
return null;
|
|
2667
2911
|
}
|
|
2668
2912
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
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");
|
|
2913
|
+
function readCachedToken() {
|
|
2914
|
+
try {
|
|
2915
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
2916
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2917
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
2918
|
+
} catch {
|
|
2919
|
+
return null;
|
|
2684
2920
|
}
|
|
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
2921
|
}
|
|
2701
|
-
function
|
|
2922
|
+
function getRawCachedPlan() {
|
|
2702
2923
|
try {
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2924
|
+
const token = readCachedToken();
|
|
2925
|
+
if (!token) return null;
|
|
2926
|
+
const parts = token.split(".");
|
|
2927
|
+
if (parts.length !== 3) return null;
|
|
2928
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2929
|
+
const plan = payload.plan ?? "free";
|
|
2930
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2931
|
+
process.stderr.write(
|
|
2932
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2933
|
+
`
|
|
2934
|
+
);
|
|
2935
|
+
return {
|
|
2936
|
+
valid: true,
|
|
2937
|
+
plan,
|
|
2938
|
+
email: payload.sub ?? "",
|
|
2939
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2940
|
+
deviceLimit: limits.devices,
|
|
2941
|
+
employeeLimit: limits.employees,
|
|
2942
|
+
memoryLimit: limits.memories
|
|
2943
|
+
};
|
|
2705
2944
|
} catch {
|
|
2706
2945
|
return null;
|
|
2707
2946
|
}
|
|
2708
2947
|
}
|
|
2709
|
-
function
|
|
2710
|
-
const mySession = getMySession();
|
|
2711
|
-
if (!mySession) return null;
|
|
2948
|
+
function cacheResponse(token) {
|
|
2712
2949
|
try {
|
|
2713
|
-
|
|
2714
|
-
const parentExe = getParentExe(key);
|
|
2715
|
-
if (parentExe) {
|
|
2716
|
-
return extractRootExe(parentExe) ?? parentExe;
|
|
2717
|
-
}
|
|
2950
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2718
2951
|
} catch {
|
|
2719
2952
|
}
|
|
2720
|
-
return extractRootExe(mySession) ?? mySession;
|
|
2721
2953
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
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() {
|
|
2954
|
+
async function validateLicense(apiKey, deviceId) {
|
|
2955
|
+
const did = deviceId ?? loadDeviceId();
|
|
2745
2956
|
try {
|
|
2746
|
-
|
|
2957
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2958
|
+
method: "POST",
|
|
2959
|
+
headers: { "Content-Type": "application/json" },
|
|
2960
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2961
|
+
signal: AbortSignal.timeout(1e4)
|
|
2962
|
+
});
|
|
2963
|
+
if (res.ok) {
|
|
2964
|
+
const data = await res.json();
|
|
2965
|
+
if (data.error === "device_limit_exceeded") {
|
|
2966
|
+
const cached2 = await getCachedLicense();
|
|
2967
|
+
if (cached2) return cached2;
|
|
2968
|
+
const raw2 = getRawCachedPlan();
|
|
2969
|
+
if (raw2) return { ...raw2, valid: false };
|
|
2970
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2971
|
+
}
|
|
2972
|
+
if (data.token) {
|
|
2973
|
+
cacheResponse(data.token);
|
|
2974
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
2975
|
+
if (verified) return verified;
|
|
2976
|
+
}
|
|
2977
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2978
|
+
return {
|
|
2979
|
+
valid: data.valid,
|
|
2980
|
+
plan: data.plan,
|
|
2981
|
+
email: data.email,
|
|
2982
|
+
expiresAt: data.expiresAt,
|
|
2983
|
+
deviceLimit: limits.devices,
|
|
2984
|
+
employeeLimit: limits.employees,
|
|
2985
|
+
memoryLimit: limits.memories
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
const cached = await getCachedLicense();
|
|
2989
|
+
if (cached) return cached;
|
|
2990
|
+
const raw = getRawCachedPlan();
|
|
2991
|
+
if (raw) return raw;
|
|
2992
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2747
2993
|
} catch {
|
|
2748
|
-
|
|
2994
|
+
const cached = await getCachedLicense();
|
|
2995
|
+
if (cached) return cached;
|
|
2996
|
+
const rawFallback = getRawCachedPlan();
|
|
2997
|
+
if (rawFallback) return rawFallback;
|
|
2998
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2749
2999
|
}
|
|
2750
3000
|
}
|
|
2751
|
-
function
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
};
|
|
2759
|
-
}
|
|
2760
|
-
var init_task_scope = __esm({
|
|
2761
|
-
"src/lib/task-scope.ts"() {
|
|
2762
|
-
"use strict";
|
|
2763
|
-
init_tmux_routing();
|
|
2764
|
-
}
|
|
2765
|
-
});
|
|
2766
|
-
|
|
2767
|
-
// src/lib/exe-daemon-client.ts
|
|
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);
|
|
2792
|
-
}
|
|
2793
|
-
} catch {
|
|
2794
|
-
}
|
|
3001
|
+
function getCacheAgeMs() {
|
|
3002
|
+
try {
|
|
3003
|
+
const { statSync: statSync2 } = __require("fs");
|
|
3004
|
+
const s = statSync2(CACHE_PATH);
|
|
3005
|
+
return Date.now() - s.mtimeMs;
|
|
3006
|
+
} catch {
|
|
3007
|
+
return Infinity;
|
|
2795
3008
|
}
|
|
2796
3009
|
}
|
|
2797
|
-
function
|
|
2798
|
-
|
|
3010
|
+
async function checkLicense() {
|
|
3011
|
+
let key = loadLicense();
|
|
3012
|
+
if (!key) {
|
|
2799
3013
|
try {
|
|
2800
|
-
const
|
|
2801
|
-
if (
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
3014
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
3015
|
+
if (existsSync8(configPath)) {
|
|
3016
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
3017
|
+
const cloud = raw.cloud;
|
|
3018
|
+
if (cloud?.apiKey) {
|
|
3019
|
+
key = cloud.apiKey;
|
|
3020
|
+
saveLicense(key);
|
|
2806
3021
|
}
|
|
2807
3022
|
}
|
|
2808
3023
|
} catch {
|
|
2809
3024
|
}
|
|
2810
|
-
try {
|
|
2811
|
-
unlinkSync3(PID_PATH);
|
|
2812
|
-
} catch {
|
|
2813
|
-
}
|
|
2814
|
-
try {
|
|
2815
|
-
unlinkSync3(SOCKET_PATH);
|
|
2816
|
-
} catch {
|
|
2817
|
-
}
|
|
2818
3025
|
}
|
|
3026
|
+
if (!key) return FREE_LICENSE;
|
|
3027
|
+
const cached = await getCachedLicense();
|
|
3028
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
3029
|
+
const deviceId = loadDeviceId();
|
|
3030
|
+
return validateLicense(key, deviceId);
|
|
2819
3031
|
}
|
|
2820
|
-
function
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
3032
|
+
function isFeatureAllowed(license, feature) {
|
|
3033
|
+
switch (feature) {
|
|
3034
|
+
case "cloud_sync":
|
|
3035
|
+
case "external_agents":
|
|
3036
|
+
case "wiki":
|
|
3037
|
+
return license.plan !== "free";
|
|
3038
|
+
case "unlimited_employees":
|
|
3039
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
2826
3040
|
}
|
|
2827
|
-
return null;
|
|
2828
3041
|
}
|
|
2829
|
-
function
|
|
2830
|
-
const
|
|
2831
|
-
if (!
|
|
2832
|
-
|
|
2833
|
-
|
|
3042
|
+
function mirrorLicenseKey(apiKey) {
|
|
3043
|
+
const trimmed = apiKey.trim();
|
|
3044
|
+
if (!trimmed) return;
|
|
3045
|
+
saveLicense(trimmed);
|
|
3046
|
+
}
|
|
3047
|
+
async function assertVpsLicense(opts) {
|
|
3048
|
+
const env = opts?.env ?? process.env;
|
|
3049
|
+
const inProduction = env.NODE_ENV === "production";
|
|
3050
|
+
if (!opts?.force && !inProduction) {
|
|
3051
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
2834
3052
|
}
|
|
2835
|
-
const
|
|
2836
|
-
if (
|
|
2837
|
-
|
|
2838
|
-
`);
|
|
2839
|
-
return;
|
|
3053
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
3054
|
+
if (envKey) {
|
|
3055
|
+
saveLicense(envKey);
|
|
2840
3056
|
}
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3057
|
+
const apiKey = envKey ?? loadLicense();
|
|
3058
|
+
if (!apiKey) {
|
|
3059
|
+
throw new Error(
|
|
3060
|
+
"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."
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
const deviceId = loadDeviceId();
|
|
3064
|
+
let backendResponse = null;
|
|
3065
|
+
let explicitRejection = false;
|
|
3066
|
+
let transientFailure = false;
|
|
2846
3067
|
try {
|
|
2847
|
-
|
|
3068
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3069
|
+
method: "POST",
|
|
3070
|
+
headers: { "Content-Type": "application/json" },
|
|
3071
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3072
|
+
signal: AbortSignal.timeout(1e4)
|
|
3073
|
+
});
|
|
3074
|
+
if (res.ok) {
|
|
3075
|
+
backendResponse = await res.json();
|
|
3076
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
3077
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
3078
|
+
explicitRejection = true;
|
|
3079
|
+
} else {
|
|
3080
|
+
transientFailure = true;
|
|
3081
|
+
}
|
|
2848
3082
|
} catch {
|
|
3083
|
+
transientFailure = true;
|
|
2849
3084
|
}
|
|
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
|
|
2861
|
-
}
|
|
2862
|
-
});
|
|
2863
|
-
child.unref();
|
|
2864
|
-
if (typeof stderrFd === "number") {
|
|
2865
|
-
try {
|
|
2866
|
-
closeSync(stderrFd);
|
|
2867
|
-
} catch {
|
|
3085
|
+
if (backendResponse?.valid) {
|
|
3086
|
+
if (backendResponse.token) {
|
|
3087
|
+
cacheResponse(backendResponse.token);
|
|
3088
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
3089
|
+
if (verified) return verified;
|
|
2868
3090
|
}
|
|
3091
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
3092
|
+
return {
|
|
3093
|
+
valid: true,
|
|
3094
|
+
plan: backendResponse.plan,
|
|
3095
|
+
email: backendResponse.email,
|
|
3096
|
+
expiresAt: backendResponse.expiresAt,
|
|
3097
|
+
deviceLimit: limits.devices,
|
|
3098
|
+
employeeLimit: limits.employees,
|
|
3099
|
+
memoryLimit: limits.memories
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
if (explicitRejection) {
|
|
3103
|
+
throw new Error(
|
|
3104
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
3105
|
+
);
|
|
3106
|
+
}
|
|
3107
|
+
if (!transientFailure) {
|
|
3108
|
+
throw new Error(
|
|
3109
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
3110
|
+
);
|
|
2869
3111
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
3112
|
+
const fresh = await getCachedLicense();
|
|
3113
|
+
if (fresh && fresh.valid) return fresh;
|
|
3114
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
3115
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
2872
3116
|
try {
|
|
2873
|
-
const
|
|
2874
|
-
|
|
2875
|
-
|
|
3117
|
+
const token = readCachedToken();
|
|
3118
|
+
if (token) {
|
|
3119
|
+
const payloadB64 = token.split(".")[1];
|
|
3120
|
+
if (payloadB64) {
|
|
3121
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
3122
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
3123
|
+
if (Date.now() < expMs + graceMs) {
|
|
3124
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
3125
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
3126
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
3127
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
3128
|
+
});
|
|
3129
|
+
const plan = verified.plan ?? "free";
|
|
3130
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3131
|
+
return {
|
|
3132
|
+
valid: true,
|
|
3133
|
+
plan,
|
|
3134
|
+
email: verified.sub ?? "",
|
|
3135
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
3136
|
+
deviceLimit: limits.devices,
|
|
3137
|
+
employeeLimit: limits.employees,
|
|
3138
|
+
memoryLimit: limits.memories
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
2876
3143
|
} catch {
|
|
3144
|
+
}
|
|
3145
|
+
throw new Error(
|
|
3146
|
+
`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.`
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
3150
|
+
if (_revalTimer) return;
|
|
3151
|
+
_revalTimer = setInterval(async () => {
|
|
2877
3152
|
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
|
-
}
|
|
3153
|
+
const license = await checkLicense();
|
|
3154
|
+
if (!license.valid) {
|
|
3155
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
2890
3156
|
}
|
|
2891
3157
|
} catch {
|
|
2892
3158
|
}
|
|
2893
|
-
|
|
3159
|
+
}, intervalMs);
|
|
3160
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
3161
|
+
_revalTimer.unref();
|
|
2894
3162
|
}
|
|
2895
3163
|
}
|
|
2896
|
-
function
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3164
|
+
function stopLicenseRevalidation() {
|
|
3165
|
+
if (_revalTimer) {
|
|
3166
|
+
clearInterval(_revalTimer);
|
|
3167
|
+
_revalTimer = null;
|
|
2900
3168
|
}
|
|
2901
3169
|
}
|
|
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
|
-
}
|
|
3170
|
+
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;
|
|
3171
|
+
var init_license = __esm({
|
|
3172
|
+
"src/lib/license.ts"() {
|
|
3173
|
+
"use strict";
|
|
3174
|
+
init_config();
|
|
3175
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3176
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3177
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
3178
|
+
API_BASE = "https://askexe.com/cloud";
|
|
3179
|
+
RETRY_DELAY_MS = 500;
|
|
3180
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
3181
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
3182
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
3183
|
+
-----END PUBLIC KEY-----`;
|
|
3184
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3185
|
+
PLAN_LIMITS = {
|
|
3186
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3187
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
3188
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
3189
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3190
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3191
|
+
};
|
|
3192
|
+
FREE_LICENSE = {
|
|
3193
|
+
valid: true,
|
|
3194
|
+
plan: "free",
|
|
3195
|
+
email: "",
|
|
3196
|
+
expiresAt: null,
|
|
3197
|
+
deviceLimit: 1,
|
|
3198
|
+
employeeLimit: 1,
|
|
3199
|
+
memoryLimit: 5e3
|
|
3200
|
+
};
|
|
3201
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
3202
|
+
_revalTimer = null;
|
|
2950
3203
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3204
|
+
});
|
|
3205
|
+
|
|
3206
|
+
// src/lib/plan-limits.ts
|
|
3207
|
+
var plan_limits_exports = {};
|
|
3208
|
+
__export(plan_limits_exports, {
|
|
3209
|
+
PlanLimitError: () => PlanLimitError,
|
|
3210
|
+
assertEmployeeLimit: () => assertEmployeeLimit,
|
|
3211
|
+
assertEmployeeLimitSync: () => assertEmployeeLimitSync,
|
|
3212
|
+
assertFeature: () => assertFeature,
|
|
3213
|
+
assertMemoryLimit: () => assertMemoryLimit,
|
|
3214
|
+
countActiveMemories: () => countActiveMemories,
|
|
3215
|
+
getLicenseSync: () => getLicenseSync
|
|
3216
|
+
});
|
|
3217
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
3218
|
+
import path10 from "path";
|
|
3219
|
+
function getLicenseSync() {
|
|
3220
|
+
try {
|
|
3221
|
+
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
3222
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
3223
|
+
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3224
|
+
const parts = raw.token.split(".");
|
|
3225
|
+
if (parts.length !== 3) return freeLicense();
|
|
3226
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3227
|
+
const plan = payload.plan ?? "free";
|
|
3228
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3229
|
+
return {
|
|
3230
|
+
valid: true,
|
|
3231
|
+
plan,
|
|
3232
|
+
email: payload.sub ?? "",
|
|
3233
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3234
|
+
deviceLimit: limits.devices,
|
|
3235
|
+
employeeLimit: limits.employees,
|
|
3236
|
+
memoryLimit: limits.memories
|
|
3237
|
+
};
|
|
3238
|
+
} catch {
|
|
3239
|
+
return freeLicense();
|
|
2957
3240
|
}
|
|
2958
|
-
return false;
|
|
2959
3241
|
}
|
|
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
|
-
});
|
|
3242
|
+
function freeLicense() {
|
|
3243
|
+
const limits = PLAN_LIMITS.free;
|
|
3244
|
+
return {
|
|
3245
|
+
valid: true,
|
|
3246
|
+
plan: "free",
|
|
3247
|
+
email: "",
|
|
3248
|
+
expiresAt: null,
|
|
3249
|
+
deviceLimit: limits.devices,
|
|
3250
|
+
employeeLimit: limits.employees,
|
|
3251
|
+
memoryLimit: limits.memories
|
|
3252
|
+
};
|
|
2980
3253
|
}
|
|
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
|
-
});
|
|
3254
|
+
async function countActiveMemories() {
|
|
3255
|
+
if (!isInitialized()) return 0;
|
|
3256
|
+
const client = getClient();
|
|
3257
|
+
const result = await client.execute(
|
|
3258
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
3259
|
+
);
|
|
3260
|
+
const row = result.rows[0];
|
|
3261
|
+
return Number(row?.cnt ?? 0);
|
|
3007
3262
|
}
|
|
3008
|
-
function
|
|
3009
|
-
|
|
3010
|
-
if (
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
} catch {
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
} catch {
|
|
3020
|
-
}
|
|
3263
|
+
async function assertMemoryLimit() {
|
|
3264
|
+
const license = await checkLicense();
|
|
3265
|
+
if (license.memoryLimit < 0) return;
|
|
3266
|
+
const count = await countActiveMemories();
|
|
3267
|
+
if (count >= license.memoryLimit) {
|
|
3268
|
+
throw new PlanLimitError(
|
|
3269
|
+
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
3270
|
+
);
|
|
3021
3271
|
}
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3272
|
+
}
|
|
3273
|
+
async function assertEmployeeLimit(license, rosterPath) {
|
|
3274
|
+
const lic = license ?? await checkLicense();
|
|
3275
|
+
if (lic.employeeLimit < 0) return;
|
|
3276
|
+
const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
|
|
3277
|
+
if (employees.length >= lic.employeeLimit) {
|
|
3278
|
+
throw new PlanLimitError(
|
|
3279
|
+
`Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
3280
|
+
);
|
|
3025
3281
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3282
|
+
}
|
|
3283
|
+
function assertEmployeeLimitSync(rosterPath) {
|
|
3284
|
+
const license = getLicenseSync();
|
|
3285
|
+
if (license.employeeLimit < 0) return;
|
|
3286
|
+
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3287
|
+
let count = 0;
|
|
3028
3288
|
try {
|
|
3029
|
-
|
|
3289
|
+
if (existsSync9(filePath)) {
|
|
3290
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
3291
|
+
const employees = JSON.parse(raw);
|
|
3292
|
+
count = Array.isArray(employees) ? employees.length : 0;
|
|
3293
|
+
}
|
|
3030
3294
|
} catch {
|
|
3295
|
+
throw new PlanLimitError(
|
|
3296
|
+
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
3297
|
+
);
|
|
3031
3298
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3299
|
+
if (count >= license.employeeLimit) {
|
|
3300
|
+
throw new PlanLimitError(
|
|
3301
|
+
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
3302
|
+
);
|
|
3035
3303
|
}
|
|
3036
|
-
spawnDaemon();
|
|
3037
3304
|
}
|
|
3038
|
-
async function
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3305
|
+
async function assertFeature(feature) {
|
|
3306
|
+
const license = await checkLicense();
|
|
3307
|
+
if (!isFeatureAllowed(license, feature)) {
|
|
3308
|
+
throw new PlanLimitError(
|
|
3309
|
+
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
var PlanLimitError, CACHE_PATH2;
|
|
3314
|
+
var init_plan_limits = __esm({
|
|
3315
|
+
"src/lib/plan-limits.ts"() {
|
|
3316
|
+
"use strict";
|
|
3317
|
+
init_database();
|
|
3318
|
+
init_employees();
|
|
3319
|
+
init_license();
|
|
3320
|
+
init_config();
|
|
3321
|
+
PlanLimitError = class extends Error {
|
|
3322
|
+
constructor(message) {
|
|
3323
|
+
super(message);
|
|
3324
|
+
this.name = "PlanLimitError";
|
|
3053
3325
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3326
|
+
};
|
|
3327
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3056
3328
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3329
|
+
});
|
|
3330
|
+
|
|
3331
|
+
// src/lib/tmux-routing.ts
|
|
3332
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10, appendFileSync } from "fs";
|
|
3333
|
+
import path11 from "path";
|
|
3334
|
+
import os7 from "os";
|
|
3335
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3336
|
+
function getMySession() {
|
|
3337
|
+
return getTransport().getMySession();
|
|
3338
|
+
}
|
|
3339
|
+
function extractRootExe(name) {
|
|
3340
|
+
if (!name) return null;
|
|
3341
|
+
if (!name.includes("-")) return name;
|
|
3342
|
+
const parts = name.split("-").filter(Boolean);
|
|
3343
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3344
|
+
}
|
|
3345
|
+
function getParentExe(sessionKey) {
|
|
3346
|
+
try {
|
|
3347
|
+
const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3348
|
+
return data.parentExe || null;
|
|
3349
|
+
} catch {
|
|
3350
|
+
return null;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
function resolveExeSession() {
|
|
3354
|
+
const mySession = getMySession();
|
|
3355
|
+
if (!mySession) return null;
|
|
3356
|
+
try {
|
|
3357
|
+
const key = getSessionKey();
|
|
3358
|
+
const parentExe = getParentExe(key);
|
|
3359
|
+
if (parentExe) {
|
|
3360
|
+
return extractRootExe(parentExe) ?? parentExe;
|
|
3069
3361
|
}
|
|
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
|
-
`);
|
|
3362
|
+
} catch {
|
|
3075
3363
|
}
|
|
3076
|
-
return
|
|
3364
|
+
return extractRootExe(mySession) ?? mySession;
|
|
3077
3365
|
}
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3366
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
3367
|
+
var init_tmux_routing = __esm({
|
|
3368
|
+
"src/lib/tmux-routing.ts"() {
|
|
3369
|
+
"use strict";
|
|
3370
|
+
init_session_registry();
|
|
3371
|
+
init_session_key();
|
|
3372
|
+
init_transport();
|
|
3373
|
+
init_cc_agent_support();
|
|
3374
|
+
init_mcp_prefix();
|
|
3375
|
+
init_provider_table();
|
|
3376
|
+
init_intercom_queue();
|
|
3377
|
+
init_plan_limits();
|
|
3378
|
+
init_employees();
|
|
3379
|
+
SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3380
|
+
SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3381
|
+
INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3382
|
+
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3383
|
+
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3082
3384
|
}
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3385
|
+
});
|
|
3386
|
+
|
|
3387
|
+
// src/lib/task-scope.ts
|
|
3388
|
+
function getCurrentSessionScope() {
|
|
3389
|
+
try {
|
|
3390
|
+
return resolveExeSession();
|
|
3391
|
+
} catch {
|
|
3392
|
+
return null;
|
|
3089
3393
|
}
|
|
3090
3394
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3395
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3396
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3397
|
+
if (!scope) return { sql: "", args: [] };
|
|
3398
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3399
|
+
return {
|
|
3400
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3401
|
+
args: [scope]
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
var init_task_scope = __esm({
|
|
3405
|
+
"src/lib/task-scope.ts"() {
|
|
3094
3406
|
"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;
|
|
3407
|
+
init_tmux_routing();
|
|
3109
3408
|
}
|
|
3110
3409
|
});
|
|
3111
3410
|
|
|
@@ -3146,10 +3445,10 @@ async function disposeEmbedder() {
|
|
|
3146
3445
|
async function embedDirect(text) {
|
|
3147
3446
|
const llamaCpp = await import("node-llama-cpp");
|
|
3148
3447
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3149
|
-
const { existsSync:
|
|
3150
|
-
const
|
|
3151
|
-
const modelPath =
|
|
3152
|
-
if (!
|
|
3448
|
+
const { existsSync: existsSync15 } = await import("fs");
|
|
3449
|
+
const path16 = await import("path");
|
|
3450
|
+
const modelPath = path16.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3451
|
+
if (!existsSync15(modelPath)) {
|
|
3153
3452
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3154
3453
|
}
|
|
3155
3454
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3366,6 +3665,232 @@ var init_compress = __esm({
|
|
|
3366
3665
|
}
|
|
3367
3666
|
});
|
|
3368
3667
|
|
|
3668
|
+
// src/lib/crdt-sync.ts
|
|
3669
|
+
var crdt_sync_exports = {};
|
|
3670
|
+
__export(crdt_sync_exports, {
|
|
3671
|
+
_setStatePath: () => _setStatePath,
|
|
3672
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
3673
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
3674
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
3675
|
+
getFullState: () => getFullState,
|
|
3676
|
+
getStateVector: () => getStateVector,
|
|
3677
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
3678
|
+
importExistingMemories: () => importExistingMemories,
|
|
3679
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
3680
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
3681
|
+
onUpdate: () => onUpdate,
|
|
3682
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
3683
|
+
readAllMemories: () => readAllMemories,
|
|
3684
|
+
rebuildFromDb: () => rebuildFromDb
|
|
3685
|
+
});
|
|
3686
|
+
import * as Y from "yjs";
|
|
3687
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
3688
|
+
import path13 from "path";
|
|
3689
|
+
import { homedir } from "os";
|
|
3690
|
+
function getStatePath() {
|
|
3691
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
3692
|
+
}
|
|
3693
|
+
function _setStatePath(p) {
|
|
3694
|
+
_statePathOverride = p;
|
|
3695
|
+
}
|
|
3696
|
+
function initCrdtDoc() {
|
|
3697
|
+
if (doc) return doc;
|
|
3698
|
+
doc = new Y.Doc();
|
|
3699
|
+
const sp = getStatePath();
|
|
3700
|
+
if (existsSync12(sp)) {
|
|
3701
|
+
try {
|
|
3702
|
+
const state = readFileSync9(sp);
|
|
3703
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
3704
|
+
} catch {
|
|
3705
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
3706
|
+
try {
|
|
3707
|
+
unlinkSync5(sp);
|
|
3708
|
+
} catch {
|
|
3709
|
+
}
|
|
3710
|
+
rebuildFromDb().catch((err) => {
|
|
3711
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
doc.on("update", () => {
|
|
3716
|
+
persistState();
|
|
3717
|
+
});
|
|
3718
|
+
return doc;
|
|
3719
|
+
}
|
|
3720
|
+
function getMemoriesMap() {
|
|
3721
|
+
const d = initCrdtDoc();
|
|
3722
|
+
return d.getMap("memories");
|
|
3723
|
+
}
|
|
3724
|
+
function getBehaviorsMap() {
|
|
3725
|
+
const d = initCrdtDoc();
|
|
3726
|
+
return d.getMap("behaviors");
|
|
3727
|
+
}
|
|
3728
|
+
function applyRemoteUpdate(update) {
|
|
3729
|
+
const d = initCrdtDoc();
|
|
3730
|
+
Y.applyUpdate(d, update);
|
|
3731
|
+
}
|
|
3732
|
+
function getFullState() {
|
|
3733
|
+
const d = initCrdtDoc();
|
|
3734
|
+
return Y.encodeStateAsUpdate(d);
|
|
3735
|
+
}
|
|
3736
|
+
function getDiffUpdate(remoteStateVector) {
|
|
3737
|
+
const d = initCrdtDoc();
|
|
3738
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
3739
|
+
}
|
|
3740
|
+
function getStateVector() {
|
|
3741
|
+
const d = initCrdtDoc();
|
|
3742
|
+
return Y.encodeStateVector(d);
|
|
3743
|
+
}
|
|
3744
|
+
function importExistingMemories(memories) {
|
|
3745
|
+
const map = getMemoriesMap();
|
|
3746
|
+
const d = initCrdtDoc();
|
|
3747
|
+
let imported = 0;
|
|
3748
|
+
d.transact(() => {
|
|
3749
|
+
for (const mem of memories) {
|
|
3750
|
+
if (!mem.id) continue;
|
|
3751
|
+
if (map.has(mem.id)) continue;
|
|
3752
|
+
const entry = new Y.Map();
|
|
3753
|
+
entry.set("id", mem.id);
|
|
3754
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
3755
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
3756
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
3757
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
3758
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
3759
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
3760
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
3761
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
3762
|
+
entry.set("version", mem.version ?? 0);
|
|
3763
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
3764
|
+
entry.set("scope", mem.scope ?? "business");
|
|
3765
|
+
map.set(mem.id, entry);
|
|
3766
|
+
imported++;
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
return imported;
|
|
3770
|
+
}
|
|
3771
|
+
function importExistingBehaviors(behaviors) {
|
|
3772
|
+
const map = getBehaviorsMap();
|
|
3773
|
+
const d = initCrdtDoc();
|
|
3774
|
+
let imported = 0;
|
|
3775
|
+
d.transact(() => {
|
|
3776
|
+
for (const beh of behaviors) {
|
|
3777
|
+
if (!beh.id) continue;
|
|
3778
|
+
if (map.has(beh.id)) continue;
|
|
3779
|
+
const entry = new Y.Map();
|
|
3780
|
+
entry.set("id", beh.id);
|
|
3781
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
3782
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
3783
|
+
entry.set("domain", beh.domain ?? null);
|
|
3784
|
+
entry.set("content", beh.content ?? null);
|
|
3785
|
+
entry.set("active", beh.active ?? 1);
|
|
3786
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
3787
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
3788
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
3789
|
+
map.set(beh.id, entry);
|
|
3790
|
+
imported++;
|
|
3791
|
+
}
|
|
3792
|
+
});
|
|
3793
|
+
return imported;
|
|
3794
|
+
}
|
|
3795
|
+
function readAllMemories() {
|
|
3796
|
+
const map = getMemoriesMap();
|
|
3797
|
+
const records = [];
|
|
3798
|
+
map.forEach((entry, id) => {
|
|
3799
|
+
records.push({
|
|
3800
|
+
id,
|
|
3801
|
+
agent_id: entry.get("agent_id"),
|
|
3802
|
+
agent_role: entry.get("agent_role"),
|
|
3803
|
+
session_id: entry.get("session_id"),
|
|
3804
|
+
timestamp: entry.get("timestamp"),
|
|
3805
|
+
tool_name: entry.get("tool_name"),
|
|
3806
|
+
project_name: entry.get("project_name"),
|
|
3807
|
+
has_error: entry.get("has_error"),
|
|
3808
|
+
raw_text: entry.get("raw_text"),
|
|
3809
|
+
version: entry.get("version"),
|
|
3810
|
+
author_device_id: entry.get("author_device_id"),
|
|
3811
|
+
scope: entry.get("scope")
|
|
3812
|
+
});
|
|
3813
|
+
});
|
|
3814
|
+
return records;
|
|
3815
|
+
}
|
|
3816
|
+
function readAllBehaviors() {
|
|
3817
|
+
const map = getBehaviorsMap();
|
|
3818
|
+
const records = [];
|
|
3819
|
+
map.forEach((entry, id) => {
|
|
3820
|
+
records.push({
|
|
3821
|
+
id,
|
|
3822
|
+
agent_id: entry.get("agent_id"),
|
|
3823
|
+
project_name: entry.get("project_name"),
|
|
3824
|
+
domain: entry.get("domain"),
|
|
3825
|
+
content: entry.get("content"),
|
|
3826
|
+
active: entry.get("active"),
|
|
3827
|
+
priority: entry.get("priority"),
|
|
3828
|
+
created_at: entry.get("created_at"),
|
|
3829
|
+
updated_at: entry.get("updated_at")
|
|
3830
|
+
});
|
|
3831
|
+
});
|
|
3832
|
+
return records;
|
|
3833
|
+
}
|
|
3834
|
+
function onUpdate(callback) {
|
|
3835
|
+
const d = initCrdtDoc();
|
|
3836
|
+
const handler = (update) => callback(update);
|
|
3837
|
+
d.on("update", handler);
|
|
3838
|
+
return () => d.off("update", handler);
|
|
3839
|
+
}
|
|
3840
|
+
function persistState() {
|
|
3841
|
+
if (!doc) return;
|
|
3842
|
+
try {
|
|
3843
|
+
const sp = getStatePath();
|
|
3844
|
+
const dir = path13.dirname(sp);
|
|
3845
|
+
if (!existsSync12(dir)) mkdirSync6(dir, { recursive: true });
|
|
3846
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
3847
|
+
writeFileSync6(sp, Buffer.from(state));
|
|
3848
|
+
} catch {
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
function isCrdtSyncEnabled() {
|
|
3852
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
3853
|
+
}
|
|
3854
|
+
async function rebuildFromDb() {
|
|
3855
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3856
|
+
const client = getClient2();
|
|
3857
|
+
const result = await client.execute(
|
|
3858
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
3859
|
+
);
|
|
3860
|
+
const memories = result.rows.map((row) => ({
|
|
3861
|
+
id: String(row.id),
|
|
3862
|
+
agent_id: row.agent_id,
|
|
3863
|
+
agent_role: row.agent_role,
|
|
3864
|
+
session_id: row.session_id,
|
|
3865
|
+
timestamp: row.timestamp,
|
|
3866
|
+
tool_name: row.tool_name,
|
|
3867
|
+
project_name: row.project_name,
|
|
3868
|
+
has_error: row.has_error,
|
|
3869
|
+
raw_text: row.raw_text,
|
|
3870
|
+
version: row.version,
|
|
3871
|
+
author_device_id: row.author_device_id,
|
|
3872
|
+
scope: row.scope
|
|
3873
|
+
}));
|
|
3874
|
+
const count = importExistingMemories(memories);
|
|
3875
|
+
persistState();
|
|
3876
|
+
return count;
|
|
3877
|
+
}
|
|
3878
|
+
function destroyCrdtDoc() {
|
|
3879
|
+
if (doc) {
|
|
3880
|
+
doc.destroy();
|
|
3881
|
+
doc = null;
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
3885
|
+
var init_crdt_sync = __esm({
|
|
3886
|
+
"src/lib/crdt-sync.ts"() {
|
|
3887
|
+
"use strict";
|
|
3888
|
+
DEFAULT_STATE_PATH = path13.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
3889
|
+
_statePathOverride = null;
|
|
3890
|
+
doc = null;
|
|
3891
|
+
}
|
|
3892
|
+
});
|
|
3893
|
+
|
|
3369
3894
|
// src/lib/cloud-sync.ts
|
|
3370
3895
|
var cloud_sync_exports = {};
|
|
3371
3896
|
__export(cloud_sync_exports, {
|
|
@@ -3394,16 +3919,16 @@ __export(cloud_sync_exports, {
|
|
|
3394
3919
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
3395
3920
|
recordRosterDeletion: () => recordRosterDeletion
|
|
3396
3921
|
});
|
|
3397
|
-
import { readFileSync as
|
|
3922
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, readdirSync as readdirSync4, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3398
3923
|
import crypto3 from "crypto";
|
|
3399
|
-
import
|
|
3400
|
-
import { homedir } from "os";
|
|
3924
|
+
import path14 from "path";
|
|
3925
|
+
import { homedir as homedir2 } from "os";
|
|
3401
3926
|
function sqlSafe(v) {
|
|
3402
3927
|
return v === void 0 ? null : v;
|
|
3403
3928
|
}
|
|
3404
3929
|
function logError(msg) {
|
|
3405
3930
|
try {
|
|
3406
|
-
const logPath =
|
|
3931
|
+
const logPath = path14.join(homedir2(), ".exe-os", "workers.log");
|
|
3407
3932
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3408
3933
|
`);
|
|
3409
3934
|
} catch {
|
|
@@ -3413,18 +3938,18 @@ async function withRosterLock(fn) {
|
|
|
3413
3938
|
try {
|
|
3414
3939
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3415
3940
|
closeSync2(fd);
|
|
3416
|
-
|
|
3941
|
+
writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3417
3942
|
} catch (err) {
|
|
3418
3943
|
if (err.code === "EEXIST") {
|
|
3419
3944
|
try {
|
|
3420
|
-
const ts = parseInt(
|
|
3945
|
+
const ts = parseInt(readFileSync10(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3421
3946
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3422
3947
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3423
3948
|
}
|
|
3424
|
-
|
|
3949
|
+
unlinkSync6(ROSTER_LOCK_PATH);
|
|
3425
3950
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3426
3951
|
closeSync2(fd);
|
|
3427
|
-
|
|
3952
|
+
writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3428
3953
|
} catch (retryErr) {
|
|
3429
3954
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3430
3955
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3437,7 +3962,7 @@ async function withRosterLock(fn) {
|
|
|
3437
3962
|
return await fn();
|
|
3438
3963
|
} finally {
|
|
3439
3964
|
try {
|
|
3440
|
-
|
|
3965
|
+
unlinkSync6(ROSTER_LOCK_PATH);
|
|
3441
3966
|
} catch {
|
|
3442
3967
|
}
|
|
3443
3968
|
}
|
|
@@ -3582,29 +4107,75 @@ async function cloudSync(config) {
|
|
|
3582
4107
|
const pullResult = await cloudPull(lastPullVersion, config);
|
|
3583
4108
|
let pulled = 0;
|
|
3584
4109
|
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
|
-
|
|
4110
|
+
if (isCrdtSyncEnabled()) {
|
|
4111
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
4112
|
+
initCrdtDoc2();
|
|
4113
|
+
importExistingMemories2(
|
|
4114
|
+
pullResult.records.map((rec) => ({
|
|
4115
|
+
id: String(rec.id ?? ""),
|
|
4116
|
+
agent_id: rec.agent_id,
|
|
4117
|
+
agent_role: rec.agent_role,
|
|
4118
|
+
session_id: rec.session_id,
|
|
4119
|
+
timestamp: rec.timestamp,
|
|
4120
|
+
tool_name: rec.tool_name,
|
|
4121
|
+
project_name: rec.project_name,
|
|
4122
|
+
has_error: rec.has_error ?? 0,
|
|
4123
|
+
raw_text: rec.raw_text ?? "",
|
|
4124
|
+
version: rec.version ?? 0,
|
|
4125
|
+
author_device_id: rec.author_device_id,
|
|
4126
|
+
scope: rec.scope ?? "business"
|
|
4127
|
+
}))
|
|
4128
|
+
);
|
|
4129
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
4130
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
4131
|
+
const stmts = merged.map((rec) => ({
|
|
4132
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
4133
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
4134
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
4135
|
+
author_device_id, scope)
|
|
4136
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4137
|
+
args: [
|
|
4138
|
+
sqlSafe(rec.id),
|
|
4139
|
+
sqlSafe(rec.agent_id),
|
|
4140
|
+
sqlSafe(rec.agent_role),
|
|
4141
|
+
sqlSafe(rec.session_id),
|
|
4142
|
+
sqlSafe(rec.timestamp),
|
|
4143
|
+
sqlSafe(rec.tool_name),
|
|
4144
|
+
sqlSafe(rec.project_name),
|
|
4145
|
+
sqlSafe(rec.has_error ?? 0),
|
|
4146
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
4147
|
+
sqlSafe(rec.version ?? 0),
|
|
4148
|
+
sqlSafe(rec.author_device_id),
|
|
4149
|
+
sqlSafe(rec.scope ?? "business")
|
|
4150
|
+
]
|
|
4151
|
+
}));
|
|
4152
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
4153
|
+
pulled = pullResult.records.length;
|
|
4154
|
+
} else {
|
|
4155
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
4156
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
4157
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
4158
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
4159
|
+
author_device_id, scope)
|
|
4160
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4161
|
+
args: [
|
|
4162
|
+
sqlSafe(rec.id),
|
|
4163
|
+
sqlSafe(rec.agent_id),
|
|
4164
|
+
sqlSafe(rec.agent_role),
|
|
4165
|
+
sqlSafe(rec.session_id),
|
|
4166
|
+
sqlSafe(rec.timestamp),
|
|
4167
|
+
sqlSafe(rec.tool_name),
|
|
4168
|
+
sqlSafe(rec.project_name),
|
|
4169
|
+
sqlSafe(rec.has_error ?? 0),
|
|
4170
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
4171
|
+
sqlSafe(rec.version ?? 0),
|
|
4172
|
+
sqlSafe(rec.author_device_id),
|
|
4173
|
+
sqlSafe(rec.scope ?? "business")
|
|
4174
|
+
]
|
|
4175
|
+
}));
|
|
4176
|
+
await client.batch(stmts, "write");
|
|
4177
|
+
pulled = pullResult.records.length;
|
|
4178
|
+
}
|
|
3608
4179
|
}
|
|
3609
4180
|
if (pullResult.maxVersion > lastPullVersion) {
|
|
3610
4181
|
await client.execute({
|
|
@@ -3752,8 +4323,8 @@ async function cloudSync(config) {
|
|
|
3752
4323
|
try {
|
|
3753
4324
|
const employees = await loadEmployees();
|
|
3754
4325
|
rosterResult.employees = employees.length;
|
|
3755
|
-
const idDir =
|
|
3756
|
-
if (
|
|
4326
|
+
const idDir = path14.join(EXE_AI_DIR, "identity");
|
|
4327
|
+
if (existsSync13(idDir)) {
|
|
3757
4328
|
rosterResult.identities = readdirSync4(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3758
4329
|
}
|
|
3759
4330
|
} catch {
|
|
@@ -3774,48 +4345,48 @@ async function cloudSync(config) {
|
|
|
3774
4345
|
function recordRosterDeletion(name) {
|
|
3775
4346
|
let deletions = [];
|
|
3776
4347
|
try {
|
|
3777
|
-
if (
|
|
3778
|
-
deletions = JSON.parse(
|
|
4348
|
+
if (existsSync13(ROSTER_DELETIONS_PATH)) {
|
|
4349
|
+
deletions = JSON.parse(readFileSync10(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3779
4350
|
}
|
|
3780
4351
|
} catch {
|
|
3781
4352
|
}
|
|
3782
4353
|
if (!deletions.includes(name)) deletions.push(name);
|
|
3783
|
-
|
|
4354
|
+
writeFileSync7(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
3784
4355
|
}
|
|
3785
4356
|
function consumeRosterDeletions() {
|
|
3786
4357
|
try {
|
|
3787
|
-
if (!
|
|
3788
|
-
const deletions = JSON.parse(
|
|
3789
|
-
|
|
4358
|
+
if (!existsSync13(ROSTER_DELETIONS_PATH)) return [];
|
|
4359
|
+
const deletions = JSON.parse(readFileSync10(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4360
|
+
writeFileSync7(ROSTER_DELETIONS_PATH, "[]");
|
|
3790
4361
|
return deletions;
|
|
3791
4362
|
} catch {
|
|
3792
4363
|
return [];
|
|
3793
4364
|
}
|
|
3794
4365
|
}
|
|
3795
4366
|
function buildRosterBlob(paths) {
|
|
3796
|
-
const rosterPath = paths?.rosterPath ??
|
|
3797
|
-
const identityDir = paths?.identityDir ??
|
|
3798
|
-
const configPath = paths?.configPath ??
|
|
4367
|
+
const rosterPath = paths?.rosterPath ?? path14.join(EXE_AI_DIR, "exe-employees.json");
|
|
4368
|
+
const identityDir = paths?.identityDir ?? path14.join(EXE_AI_DIR, "identity");
|
|
4369
|
+
const configPath = paths?.configPath ?? path14.join(EXE_AI_DIR, "config.json");
|
|
3799
4370
|
let roster = [];
|
|
3800
|
-
if (
|
|
4371
|
+
if (existsSync13(rosterPath)) {
|
|
3801
4372
|
try {
|
|
3802
|
-
roster = JSON.parse(
|
|
4373
|
+
roster = JSON.parse(readFileSync10(rosterPath, "utf-8"));
|
|
3803
4374
|
} catch {
|
|
3804
4375
|
}
|
|
3805
4376
|
}
|
|
3806
4377
|
const identities = {};
|
|
3807
|
-
if (
|
|
4378
|
+
if (existsSync13(identityDir)) {
|
|
3808
4379
|
for (const file of readdirSync4(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3809
4380
|
try {
|
|
3810
|
-
identities[file] =
|
|
4381
|
+
identities[file] = readFileSync10(path14.join(identityDir, file), "utf-8");
|
|
3811
4382
|
} catch {
|
|
3812
4383
|
}
|
|
3813
4384
|
}
|
|
3814
4385
|
}
|
|
3815
4386
|
let config;
|
|
3816
|
-
if (
|
|
4387
|
+
if (existsSync13(configPath)) {
|
|
3817
4388
|
try {
|
|
3818
|
-
config = JSON.parse(
|
|
4389
|
+
config = JSON.parse(readFileSync10(configPath, "utf-8"));
|
|
3819
4390
|
} catch {
|
|
3820
4391
|
}
|
|
3821
4392
|
}
|
|
@@ -3891,23 +4462,23 @@ async function cloudPullRoster(config) {
|
|
|
3891
4462
|
}
|
|
3892
4463
|
}
|
|
3893
4464
|
function mergeConfig(remoteConfig, configPath) {
|
|
3894
|
-
const cfgPath = configPath ??
|
|
4465
|
+
const cfgPath = configPath ?? path14.join(EXE_AI_DIR, "config.json");
|
|
3895
4466
|
let local = {};
|
|
3896
|
-
if (
|
|
4467
|
+
if (existsSync13(cfgPath)) {
|
|
3897
4468
|
try {
|
|
3898
|
-
local = JSON.parse(
|
|
4469
|
+
local = JSON.parse(readFileSync10(cfgPath, "utf-8"));
|
|
3899
4470
|
} catch {
|
|
3900
4471
|
}
|
|
3901
4472
|
}
|
|
3902
4473
|
const merged = { ...remoteConfig, ...local };
|
|
3903
|
-
const dir =
|
|
3904
|
-
if (!
|
|
3905
|
-
|
|
4474
|
+
const dir = path14.dirname(cfgPath);
|
|
4475
|
+
if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
|
|
4476
|
+
writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3906
4477
|
}
|
|
3907
4478
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3908
4479
|
return withRosterLock(async () => {
|
|
3909
4480
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3910
|
-
const identityDir = paths?.identityDir ??
|
|
4481
|
+
const identityDir = paths?.identityDir ?? path14.join(EXE_AI_DIR, "identity");
|
|
3911
4482
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3912
4483
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3913
4484
|
let added = 0;
|
|
@@ -3928,15 +4499,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3928
4499
|
) ?? lookupKey;
|
|
3929
4500
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3930
4501
|
if (remoteIdentity) {
|
|
3931
|
-
if (!
|
|
3932
|
-
const idPath =
|
|
4502
|
+
if (!existsSync13(identityDir)) mkdirSync7(identityDir, { recursive: true });
|
|
4503
|
+
const idPath = path14.join(identityDir, `${remoteEmp.name}.md`);
|
|
3933
4504
|
let localIdentity = null;
|
|
3934
4505
|
try {
|
|
3935
|
-
localIdentity =
|
|
4506
|
+
localIdentity = existsSync13(idPath) ? readFileSync10(idPath, "utf-8") : null;
|
|
3936
4507
|
} catch {
|
|
3937
4508
|
}
|
|
3938
4509
|
if (localIdentity !== remoteIdentity) {
|
|
3939
|
-
|
|
4510
|
+
writeFileSync7(idPath, remoteIdentity, "utf-8");
|
|
3940
4511
|
identitiesUpdated++;
|
|
3941
4512
|
}
|
|
3942
4513
|
}
|
|
@@ -4389,13 +4960,14 @@ var init_cloud_sync = __esm({
|
|
|
4389
4960
|
init_compress();
|
|
4390
4961
|
init_license();
|
|
4391
4962
|
init_config();
|
|
4963
|
+
init_crdt_sync();
|
|
4392
4964
|
init_employees();
|
|
4393
4965
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4394
4966
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4395
4967
|
PUSH_BATCH_SIZE = 5e3;
|
|
4396
|
-
ROSTER_LOCK_PATH =
|
|
4968
|
+
ROSTER_LOCK_PATH = path14.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4397
4969
|
LOCK_STALE_MS = 3e4;
|
|
4398
|
-
ROSTER_DELETIONS_PATH =
|
|
4970
|
+
ROSTER_DELETIONS_PATH = path14.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4399
4971
|
}
|
|
4400
4972
|
});
|
|
4401
4973
|
|
|
@@ -4405,6 +4977,7 @@ init_database();
|
|
|
4405
4977
|
init_keychain();
|
|
4406
4978
|
init_config();
|
|
4407
4979
|
init_state_bus();
|
|
4980
|
+
import { createHash } from "crypto";
|
|
4408
4981
|
var INIT_MAX_RETRIES = 3;
|
|
4409
4982
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
4410
4983
|
function isBusyError2(err) {
|
|
@@ -4486,12 +5059,52 @@ function classifyTier(record) {
|
|
|
4486
5059
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4487
5060
|
return 3;
|
|
4488
5061
|
}
|
|
5062
|
+
function inferFilePaths(record) {
|
|
5063
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5064
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5065
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5066
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5067
|
+
}
|
|
5068
|
+
function inferCommitHash(record) {
|
|
5069
|
+
if (record.tool_name !== "Bash") return null;
|
|
5070
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5071
|
+
return match ? match[1] : null;
|
|
5072
|
+
}
|
|
5073
|
+
function inferLanguageType(record) {
|
|
5074
|
+
const text = record.raw_text;
|
|
5075
|
+
if (!text || text.length < 10) return null;
|
|
5076
|
+
const trimmed = text.trimStart();
|
|
5077
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5078
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5079
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5080
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5081
|
+
return "mixed";
|
|
5082
|
+
}
|
|
5083
|
+
function inferDomain(record) {
|
|
5084
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5085
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5086
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5087
|
+
return null;
|
|
5088
|
+
}
|
|
4489
5089
|
async function writeMemory(record) {
|
|
4490
5090
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4491
5091
|
throw new Error(
|
|
4492
5092
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4493
5093
|
);
|
|
4494
5094
|
}
|
|
5095
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5096
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
try {
|
|
5100
|
+
const client = getClient();
|
|
5101
|
+
const existing = await client.execute({
|
|
5102
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5103
|
+
args: [contentHash, record.agent_id]
|
|
5104
|
+
});
|
|
5105
|
+
if (existing.rows.length > 0) return;
|
|
5106
|
+
} catch {
|
|
5107
|
+
}
|
|
4495
5108
|
const dbRow = {
|
|
4496
5109
|
id: record.id,
|
|
4497
5110
|
agent_id: record.agent_id,
|
|
@@ -4521,7 +5134,23 @@ async function writeMemory(record) {
|
|
|
4521
5134
|
supersedes_id: record.supersedes_id ?? null,
|
|
4522
5135
|
draft: record.draft ? 1 : 0,
|
|
4523
5136
|
memory_type: record.memory_type ?? "raw",
|
|
4524
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5137
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5138
|
+
content_hash: contentHash,
|
|
5139
|
+
intent: record.intent ?? null,
|
|
5140
|
+
outcome: record.outcome ?? null,
|
|
5141
|
+
domain: record.domain ?? inferDomain(record),
|
|
5142
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5143
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5144
|
+
chain_position: record.chain_position ?? null,
|
|
5145
|
+
review_status: record.review_status ?? null,
|
|
5146
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5147
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5148
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5149
|
+
duration_ms: record.duration_ms ?? null,
|
|
5150
|
+
token_cost: record.token_cost ?? null,
|
|
5151
|
+
audience: record.audience ?? null,
|
|
5152
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5153
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4525
5154
|
};
|
|
4526
5155
|
_pendingRecords.push(dbRow);
|
|
4527
5156
|
orgBus.emit({
|
|
@@ -4579,80 +5208,85 @@ async function flushBatch() {
|
|
|
4579
5208
|
const draft = row.draft ? 1 : 0;
|
|
4580
5209
|
const memoryType = row.memory_type ?? "raw";
|
|
4581
5210
|
const trajectory = row.trajectory ?? null;
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
5211
|
+
const contentHash = row.content_hash ?? null;
|
|
5212
|
+
const intent = row.intent ?? null;
|
|
5213
|
+
const outcome = row.outcome ?? null;
|
|
5214
|
+
const domain = row.domain ?? null;
|
|
5215
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5216
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5217
|
+
const chainPosition = row.chain_position ?? null;
|
|
5218
|
+
const reviewStatus = row.review_status ?? null;
|
|
5219
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5220
|
+
const filePaths = row.file_paths ?? null;
|
|
5221
|
+
const commitHash = row.commit_hash ?? null;
|
|
5222
|
+
const durationMs = row.duration_ms ?? null;
|
|
5223
|
+
const tokenCost = row.token_cost ?? null;
|
|
5224
|
+
const audience = row.audience ?? null;
|
|
5225
|
+
const languageType = row.language_type ?? null;
|
|
5226
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5227
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4585
5228
|
tool_name, project_name,
|
|
4586
5229
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4587
5230
|
confidence, last_accessed,
|
|
4588
5231
|
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
|
-
]
|
|
5232
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5233
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5234
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5235
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5236
|
+
const metaArgs = [
|
|
5237
|
+
intent,
|
|
5238
|
+
outcome,
|
|
5239
|
+
domain,
|
|
5240
|
+
referencedEntities,
|
|
5241
|
+
retrievalCount,
|
|
5242
|
+
chainPosition,
|
|
5243
|
+
reviewStatus,
|
|
5244
|
+
contextWindowPct,
|
|
5245
|
+
filePaths,
|
|
5246
|
+
commitHash,
|
|
5247
|
+
durationMs,
|
|
5248
|
+
tokenCost,
|
|
5249
|
+
audience,
|
|
5250
|
+
languageType,
|
|
5251
|
+
parentMemoryId
|
|
5252
|
+
];
|
|
5253
|
+
const baseArgs = [
|
|
5254
|
+
row.id,
|
|
5255
|
+
row.agent_id,
|
|
5256
|
+
row.agent_role,
|
|
5257
|
+
row.session_id,
|
|
5258
|
+
row.timestamp,
|
|
5259
|
+
row.tool_name,
|
|
5260
|
+
row.project_name,
|
|
5261
|
+
row.has_error,
|
|
5262
|
+
row.raw_text
|
|
5263
|
+
];
|
|
5264
|
+
const sharedArgs = [
|
|
5265
|
+
row.version,
|
|
5266
|
+
taskId,
|
|
5267
|
+
importance,
|
|
5268
|
+
status,
|
|
5269
|
+
confidence,
|
|
5270
|
+
lastAccessed,
|
|
5271
|
+
workspaceId,
|
|
5272
|
+
documentId,
|
|
5273
|
+
userId,
|
|
5274
|
+
charOffset,
|
|
5275
|
+
pageNumber,
|
|
5276
|
+
sourcePath,
|
|
5277
|
+
sourceType,
|
|
5278
|
+
tier,
|
|
5279
|
+
supersedesId,
|
|
5280
|
+
draft,
|
|
5281
|
+
memoryType,
|
|
5282
|
+
trajectory,
|
|
5283
|
+
contentHash
|
|
5284
|
+
];
|
|
5285
|
+
return {
|
|
5286
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5287
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5288
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5289
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4656
5290
|
};
|
|
4657
5291
|
};
|
|
4658
5292
|
const globalClient = getClient();
|
|
@@ -4700,8 +5334,8 @@ init_task_scope();
|
|
|
4700
5334
|
init_employees();
|
|
4701
5335
|
import crypto4 from "crypto";
|
|
4702
5336
|
import { execSync as execSync4 } from "child_process";
|
|
4703
|
-
import { existsSync as
|
|
4704
|
-
import
|
|
5337
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, openSync as openSync3, closeSync as closeSync3 } from "fs";
|
|
5338
|
+
import path15 from "path";
|
|
4705
5339
|
async function main() {
|
|
4706
5340
|
const agentId = process.env.AGENT_ID ?? "default";
|
|
4707
5341
|
const agentRole = process.env.AGENT_ROLE ?? "employee";
|
|
@@ -4836,8 +5470,8 @@ async function main() {
|
|
|
4836
5470
|
}
|
|
4837
5471
|
try {
|
|
4838
5472
|
const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4839
|
-
const flagPath =
|
|
4840
|
-
if (
|
|
5473
|
+
const flagPath = path15.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
|
|
5474
|
+
if (existsSync14(flagPath)) {
|
|
4841
5475
|
const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
4842
5476
|
if (!tryAcquireWorkerSlot2()) {
|
|
4843
5477
|
process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
|
|
@@ -4845,11 +5479,11 @@ async function main() {
|
|
|
4845
5479
|
const { spawn: spawn2 } = await import("child_process");
|
|
4846
5480
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
4847
5481
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
4848
|
-
const backfillPath =
|
|
4849
|
-
if (
|
|
5482
|
+
const backfillPath = path15.resolve(path15.dirname(thisFile), "backfill-vectors.js");
|
|
5483
|
+
if (existsSync14(backfillPath)) {
|
|
4850
5484
|
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4851
|
-
const bLogPath =
|
|
4852
|
-
|
|
5485
|
+
const bLogPath = path15.join(exeDir2, "workers.log");
|
|
5486
|
+
mkdirSync8(path15.dirname(bLogPath), { recursive: true });
|
|
4853
5487
|
const bLogFd = openSync3(bLogPath, "a");
|
|
4854
5488
|
const child = spawn2(process.execPath, [backfillPath], {
|
|
4855
5489
|
detached: true,
|