@askexenow/exe-os 0.8.82 → 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 +14360 -12525
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1260 -323
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +32 -9
- package/dist/bin/exe-dispatch.js +212 -36
- 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 +553 -174
- 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 +41 -11
- 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 +577 -33
- 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 +901 -209
- 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 +906 -213
- package/dist/bin/setup.js +870 -271
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +4 -3
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +550 -168
- package/dist/hooks/bug-report-worker.js +210 -25
- package/dist/hooks/commit-complete.js +899 -207
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1639 -1195
- 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 +899 -207
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +245 -104
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +245 -104
- 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 +1970 -1336
- package/dist/index.js +1653 -1055
- 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 +1957 -924
- 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/license.js +3 -3
- 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 +113 -24
- package/dist/lib/tmux-routing.js +122 -33
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +10874 -5546
- 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 +810 -27
- 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 +913 -221
- package/dist/tui/App.js +1000 -298
- package/package.json +6 -1
- package/src/commands/exe/build-adv.md +2 -2
|
@@ -183,12 +183,12 @@ __export(config_exports, {
|
|
|
183
183
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
184
184
|
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
185
185
|
import path3 from "path";
|
|
186
|
-
import
|
|
186
|
+
import os2 from "os";
|
|
187
187
|
function resolveDataDir() {
|
|
188
188
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
189
189
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
190
|
-
const newDir = path3.join(
|
|
191
|
-
const legacyDir = path3.join(
|
|
190
|
+
const newDir = path3.join(os2.homedir(), ".exe-os");
|
|
191
|
+
const legacyDir = path3.join(os2.homedir(), ".exe-mem");
|
|
192
192
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
193
193
|
try {
|
|
194
194
|
renameSync(legacyDir, newDir);
|
|
@@ -275,7 +275,7 @@ async function loadConfig() {
|
|
|
275
275
|
normalizeAutoUpdate(migratedCfg);
|
|
276
276
|
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
277
277
|
if (config.dbPath.startsWith("~")) {
|
|
278
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
278
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
279
279
|
}
|
|
280
280
|
return config;
|
|
281
281
|
} catch {
|
|
@@ -409,7 +409,7 @@ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from
|
|
|
409
409
|
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
410
410
|
import { execSync as execSync3 } from "child_process";
|
|
411
411
|
import path4 from "path";
|
|
412
|
-
import
|
|
412
|
+
import os3 from "os";
|
|
413
413
|
function normalizeRole(role) {
|
|
414
414
|
return (role ?? "").trim().toLowerCase();
|
|
415
415
|
}
|
|
@@ -458,389 +458,942 @@ var init_employees = __esm({
|
|
|
458
458
|
}
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
-
// src/lib/
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
import { createClient } from "@libsql/client";
|
|
474
|
-
async function initDatabase(config) {
|
|
475
|
-
if (_client) {
|
|
476
|
-
_client.close();
|
|
477
|
-
_client = null;
|
|
478
|
-
_resilientClient = null;
|
|
461
|
+
// src/lib/exe-daemon-client.ts
|
|
462
|
+
import net from "net";
|
|
463
|
+
import { spawn } from "child_process";
|
|
464
|
+
import { randomUUID } from "crypto";
|
|
465
|
+
import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync as statSync2 } from "fs";
|
|
466
|
+
import path5 from "path";
|
|
467
|
+
import { fileURLToPath } from "url";
|
|
468
|
+
function handleData(chunk) {
|
|
469
|
+
_buffer += chunk.toString();
|
|
470
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
471
|
+
_buffer = "";
|
|
472
|
+
return;
|
|
479
473
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
474
|
+
let newlineIdx;
|
|
475
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
476
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
477
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
478
|
+
if (!line) continue;
|
|
479
|
+
try {
|
|
480
|
+
const response = JSON.parse(line);
|
|
481
|
+
const id = response.id;
|
|
482
|
+
if (!id) continue;
|
|
483
|
+
const entry = _pending.get(id);
|
|
484
|
+
if (entry) {
|
|
485
|
+
clearTimeout(entry.timer);
|
|
486
|
+
_pending.delete(id);
|
|
487
|
+
entry.resolve(response);
|
|
488
|
+
}
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
485
491
|
}
|
|
486
|
-
_client = createClient(opts);
|
|
487
|
-
_resilientClient = wrapWithRetry(_client);
|
|
488
|
-
}
|
|
489
|
-
function isInitialized() {
|
|
490
|
-
return _client !== null;
|
|
491
492
|
}
|
|
492
|
-
function
|
|
493
|
-
if (
|
|
494
|
-
|
|
493
|
+
function cleanupStaleFiles() {
|
|
494
|
+
if (existsSync4(PID_PATH)) {
|
|
495
|
+
try {
|
|
496
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
497
|
+
if (pid > 0) {
|
|
498
|
+
try {
|
|
499
|
+
process.kill(pid, 0);
|
|
500
|
+
return;
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
unlinkSync2(PID_PATH);
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
unlinkSync2(SOCKET_PATH);
|
|
512
|
+
} catch {
|
|
513
|
+
}
|
|
495
514
|
}
|
|
496
|
-
return _resilientClient;
|
|
497
515
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
|
|
516
|
+
function findPackageRoot() {
|
|
517
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
518
|
+
const { root } = path5.parse(dir);
|
|
519
|
+
while (dir !== root) {
|
|
520
|
+
if (existsSync4(path5.join(dir, "package.json"))) return dir;
|
|
521
|
+
dir = path5.dirname(dir);
|
|
501
522
|
}
|
|
502
|
-
return
|
|
523
|
+
return null;
|
|
503
524
|
}
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
511
|
-
} catch {
|
|
525
|
+
function spawnDaemon() {
|
|
526
|
+
const pkgRoot = findPackageRoot();
|
|
527
|
+
if (!pkgRoot) {
|
|
528
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
529
|
+
return;
|
|
512
530
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
session_id TEXT NOT NULL,
|
|
519
|
-
timestamp TEXT NOT NULL,
|
|
520
|
-
tool_name TEXT NOT NULL,
|
|
521
|
-
project_name TEXT NOT NULL,
|
|
522
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
523
|
-
raw_text TEXT NOT NULL,
|
|
524
|
-
vector F32_BLOB(1024),
|
|
525
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
529
|
-
ON memories(agent_id);
|
|
530
|
-
|
|
531
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
532
|
-
ON memories(timestamp);
|
|
533
|
-
|
|
534
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
535
|
-
ON memories(session_id);
|
|
536
|
-
|
|
537
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
538
|
-
ON memories(project_name);
|
|
539
|
-
|
|
540
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
541
|
-
ON memories(tool_name);
|
|
542
|
-
|
|
543
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
544
|
-
ON memories(version);
|
|
545
|
-
|
|
546
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
547
|
-
ON memories(agent_id, project_name);
|
|
548
|
-
`);
|
|
549
|
-
await client.executeMultiple(`
|
|
550
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
551
|
-
raw_text,
|
|
552
|
-
content='memories',
|
|
553
|
-
content_rowid='rowid'
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
557
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
558
|
-
END;
|
|
559
|
-
|
|
560
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
561
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
562
|
-
END;
|
|
563
|
-
|
|
564
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
565
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
566
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
567
|
-
END;
|
|
568
|
-
`);
|
|
569
|
-
await client.executeMultiple(`
|
|
570
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
571
|
-
key TEXT PRIMARY KEY,
|
|
572
|
-
value TEXT NOT NULL
|
|
573
|
-
);
|
|
574
|
-
`);
|
|
575
|
-
await client.executeMultiple(`
|
|
576
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
577
|
-
id TEXT PRIMARY KEY,
|
|
578
|
-
title TEXT NOT NULL,
|
|
579
|
-
assigned_to TEXT NOT NULL,
|
|
580
|
-
assigned_by TEXT NOT NULL,
|
|
581
|
-
project_name TEXT NOT NULL,
|
|
582
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
583
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
584
|
-
task_file TEXT,
|
|
585
|
-
created_at TEXT NOT NULL,
|
|
586
|
-
updated_at TEXT NOT NULL
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
590
|
-
ON tasks(assigned_to, status);
|
|
591
|
-
`);
|
|
592
|
-
await client.executeMultiple(`
|
|
593
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
594
|
-
id TEXT PRIMARY KEY,
|
|
595
|
-
agent_id TEXT NOT NULL,
|
|
596
|
-
project_name TEXT,
|
|
597
|
-
domain TEXT,
|
|
598
|
-
content TEXT NOT NULL,
|
|
599
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
600
|
-
created_at TEXT NOT NULL,
|
|
601
|
-
updated_at TEXT NOT NULL
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
605
|
-
ON behaviors(agent_id, active);
|
|
606
|
-
`);
|
|
607
|
-
try {
|
|
608
|
-
const coordinatorName = getCoordinatorName();
|
|
609
|
-
const existing = await client.execute({
|
|
610
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
611
|
-
args: [coordinatorName]
|
|
612
|
-
});
|
|
613
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
614
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
615
|
-
for (const [domain, content] of [
|
|
616
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
617
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
618
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
619
|
-
]) {
|
|
620
|
-
await client.execute({
|
|
621
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
622
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
623
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
} catch {
|
|
531
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
532
|
+
if (!existsSync4(daemonPath)) {
|
|
533
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
534
|
+
`);
|
|
535
|
+
return;
|
|
628
536
|
}
|
|
537
|
+
const resolvedPath = daemonPath;
|
|
538
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
539
|
+
`);
|
|
540
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
541
|
+
let stderrFd = "ignore";
|
|
629
542
|
try {
|
|
630
|
-
|
|
631
|
-
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
632
|
-
args: []
|
|
633
|
-
});
|
|
543
|
+
stderrFd = openSync(logPath, "a");
|
|
634
544
|
} catch {
|
|
635
545
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
546
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
547
|
+
detached: true,
|
|
548
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
549
|
+
env: {
|
|
550
|
+
...process.env,
|
|
551
|
+
TMUX: void 0,
|
|
552
|
+
// Daemon is global — must not inherit session scope
|
|
553
|
+
TMUX_PANE: void 0,
|
|
554
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
555
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
556
|
+
EXE_DAEMON_PID: PID_PATH
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
child.unref();
|
|
560
|
+
if (typeof stderrFd === "number") {
|
|
561
|
+
try {
|
|
562
|
+
closeSync(stderrFd);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
642
565
|
}
|
|
566
|
+
}
|
|
567
|
+
function acquireSpawnLock() {
|
|
643
568
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
648
|
-
} catch {
|
|
649
|
-
}
|
|
650
|
-
try {
|
|
651
|
-
await client.execute({
|
|
652
|
-
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
653
|
-
ON tasks(parent_task_id)
|
|
654
|
-
WHERE parent_task_id IS NOT NULL`,
|
|
655
|
-
args: []
|
|
656
|
-
});
|
|
569
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
570
|
+
closeSync(fd);
|
|
571
|
+
return true;
|
|
657
572
|
} catch {
|
|
573
|
+
try {
|
|
574
|
+
const stat = statSync2(SPAWN_LOCK_PATH);
|
|
575
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
576
|
+
try {
|
|
577
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
582
|
+
closeSync(fd);
|
|
583
|
+
return true;
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
658
590
|
}
|
|
591
|
+
}
|
|
592
|
+
function releaseSpawnLock() {
|
|
659
593
|
try {
|
|
660
|
-
|
|
661
|
-
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
662
|
-
args: []
|
|
663
|
-
});
|
|
594
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
664
595
|
} catch {
|
|
665
596
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
597
|
+
}
|
|
598
|
+
function connectToSocket() {
|
|
599
|
+
return new Promise((resolve) => {
|
|
600
|
+
if (_socket && _connected) {
|
|
601
|
+
resolve(true);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
605
|
+
const connectTimeout = setTimeout(() => {
|
|
606
|
+
socket.destroy();
|
|
607
|
+
resolve(false);
|
|
608
|
+
}, 2e3);
|
|
609
|
+
socket.on("connect", () => {
|
|
610
|
+
clearTimeout(connectTimeout);
|
|
611
|
+
_socket = socket;
|
|
612
|
+
_connected = true;
|
|
613
|
+
_buffer = "";
|
|
614
|
+
socket.on("data", handleData);
|
|
615
|
+
socket.on("close", () => {
|
|
616
|
+
_connected = false;
|
|
617
|
+
_socket = null;
|
|
618
|
+
for (const [id, entry] of _pending) {
|
|
619
|
+
clearTimeout(entry.timer);
|
|
620
|
+
_pending.delete(id);
|
|
621
|
+
entry.resolve({ error: "Connection closed" });
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
socket.on("error", () => {
|
|
625
|
+
_connected = false;
|
|
626
|
+
_socket = null;
|
|
627
|
+
});
|
|
628
|
+
resolve(true);
|
|
670
629
|
});
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
await client.execute({
|
|
675
|
-
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
676
|
-
args: []
|
|
630
|
+
socket.on("error", () => {
|
|
631
|
+
clearTimeout(connectTimeout);
|
|
632
|
+
resolve(false);
|
|
677
633
|
});
|
|
678
|
-
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async function connectEmbedDaemon() {
|
|
637
|
+
if (_socket && _connected) return true;
|
|
638
|
+
if (await connectToSocket()) return true;
|
|
639
|
+
if (acquireSpawnLock()) {
|
|
640
|
+
try {
|
|
641
|
+
cleanupStaleFiles();
|
|
642
|
+
spawnDaemon();
|
|
643
|
+
} finally {
|
|
644
|
+
releaseSpawnLock();
|
|
645
|
+
}
|
|
679
646
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
647
|
+
const start = Date.now();
|
|
648
|
+
let delay2 = 100;
|
|
649
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
650
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
651
|
+
if (await connectToSocket()) return true;
|
|
652
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
686
653
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
function sendRequest(texts, priority) {
|
|
657
|
+
return sendDaemonRequest({ texts, priority });
|
|
658
|
+
}
|
|
659
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
660
|
+
return new Promise((resolve) => {
|
|
661
|
+
if (!_socket || !_connected) {
|
|
662
|
+
resolve({ error: "Not connected" });
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const id = randomUUID();
|
|
666
|
+
const timer = setTimeout(() => {
|
|
667
|
+
_pending.delete(id);
|
|
668
|
+
resolve({ error: "Request timeout" });
|
|
669
|
+
}, timeoutMs);
|
|
670
|
+
_pending.set(id, { resolve, timer });
|
|
671
|
+
try {
|
|
672
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
673
|
+
} catch {
|
|
674
|
+
clearTimeout(timer);
|
|
675
|
+
_pending.delete(id);
|
|
676
|
+
resolve({ error: "Write failed" });
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async function pingDaemon() {
|
|
681
|
+
if (!_socket || !_connected) return null;
|
|
682
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
683
|
+
if (response.health) {
|
|
684
|
+
return response.health;
|
|
693
685
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
function killAndRespawnDaemon() {
|
|
689
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
690
|
+
if (existsSync4(PID_PATH)) {
|
|
691
|
+
try {
|
|
692
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
693
|
+
if (pid > 0) {
|
|
694
|
+
try {
|
|
695
|
+
process.kill(pid, "SIGKILL");
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch {
|
|
700
|
+
}
|
|
700
701
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
args: []
|
|
705
|
-
});
|
|
706
|
-
} catch {
|
|
702
|
+
if (_socket) {
|
|
703
|
+
_socket.destroy();
|
|
704
|
+
_socket = null;
|
|
707
705
|
}
|
|
706
|
+
_connected = false;
|
|
707
|
+
_buffer = "";
|
|
708
708
|
try {
|
|
709
|
-
|
|
710
|
-
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
711
|
-
args: []
|
|
712
|
-
});
|
|
709
|
+
unlinkSync2(PID_PATH);
|
|
713
710
|
} catch {
|
|
714
711
|
}
|
|
715
712
|
try {
|
|
716
|
-
|
|
717
|
-
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
718
|
-
args: []
|
|
719
|
-
});
|
|
713
|
+
unlinkSync2(SOCKET_PATH);
|
|
720
714
|
} catch {
|
|
721
715
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
716
|
+
spawnDaemon();
|
|
717
|
+
}
|
|
718
|
+
async function embedViaClient(text, priority = "high") {
|
|
719
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
720
|
+
_requestCount++;
|
|
721
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
722
|
+
const health = await pingDaemon();
|
|
723
|
+
if (!health) {
|
|
724
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
725
|
+
`);
|
|
726
|
+
killAndRespawnDaemon();
|
|
727
|
+
const start = Date.now();
|
|
728
|
+
let delay2 = 200;
|
|
729
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
730
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
731
|
+
if (await connectToSocket()) break;
|
|
732
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
733
|
+
}
|
|
734
|
+
if (!_connected) return null;
|
|
735
|
+
}
|
|
728
736
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
737
|
+
const result = await sendRequest([text], priority);
|
|
738
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
739
|
+
if (result.error) {
|
|
740
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
741
|
+
`);
|
|
742
|
+
killAndRespawnDaemon();
|
|
743
|
+
const start = Date.now();
|
|
744
|
+
let delay2 = 200;
|
|
745
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
746
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
747
|
+
if (await connectToSocket()) break;
|
|
748
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
749
|
+
}
|
|
750
|
+
if (!_connected) return null;
|
|
751
|
+
const retry = await sendRequest([text], priority);
|
|
752
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
753
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
754
|
+
`);
|
|
735
755
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function disconnectClient() {
|
|
759
|
+
if (_socket) {
|
|
760
|
+
_socket.destroy();
|
|
761
|
+
_socket = null;
|
|
742
762
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
763
|
+
_connected = false;
|
|
764
|
+
_buffer = "";
|
|
765
|
+
for (const [id, entry] of _pending) {
|
|
766
|
+
clearTimeout(entry.timer);
|
|
767
|
+
_pending.delete(id);
|
|
768
|
+
entry.resolve({ error: "Client disconnected" });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function isClientConnected() {
|
|
772
|
+
return _connected;
|
|
773
|
+
}
|
|
774
|
+
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;
|
|
775
|
+
var init_exe_daemon_client = __esm({
|
|
776
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
777
|
+
"use strict";
|
|
778
|
+
init_config();
|
|
779
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
780
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
781
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
782
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
783
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
784
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
785
|
+
_socket = null;
|
|
786
|
+
_connected = false;
|
|
787
|
+
_buffer = "";
|
|
788
|
+
_requestCount = 0;
|
|
789
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
790
|
+
_pending = /* @__PURE__ */ new Map();
|
|
791
|
+
MAX_BUFFER = 1e7;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// src/lib/daemon-protocol.ts
|
|
796
|
+
function serializeValue(v) {
|
|
797
|
+
if (v === null || v === void 0) return null;
|
|
798
|
+
if (typeof v === "bigint") return Number(v);
|
|
799
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
800
|
+
if (v instanceof Uint8Array) {
|
|
801
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
802
|
+
}
|
|
803
|
+
if (ArrayBuffer.isView(v)) {
|
|
804
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
805
|
+
}
|
|
806
|
+
if (v instanceof ArrayBuffer) {
|
|
807
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
808
|
+
}
|
|
809
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
810
|
+
return String(v);
|
|
811
|
+
}
|
|
812
|
+
function deserializeValue(v) {
|
|
813
|
+
if (v === null) return null;
|
|
814
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
815
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
816
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
817
|
+
}
|
|
818
|
+
return v;
|
|
819
|
+
}
|
|
820
|
+
function deserializeResultSet(srs) {
|
|
821
|
+
const rows = srs.rows.map((obj) => {
|
|
822
|
+
const values = srs.columns.map(
|
|
823
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
824
|
+
);
|
|
825
|
+
const row = values;
|
|
826
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
827
|
+
const col = srs.columns[i];
|
|
828
|
+
if (col !== void 0) {
|
|
829
|
+
row[col] = values[i] ?? null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
Object.defineProperty(row, "length", {
|
|
833
|
+
value: values.length,
|
|
834
|
+
enumerable: false
|
|
835
|
+
});
|
|
836
|
+
return row;
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
columns: srs.columns,
|
|
840
|
+
columnTypes: srs.columnTypes ?? [],
|
|
841
|
+
rows,
|
|
842
|
+
rowsAffected: srs.rowsAffected,
|
|
843
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
844
|
+
toJSON: () => ({
|
|
845
|
+
columns: srs.columns,
|
|
846
|
+
columnTypes: srs.columnTypes ?? [],
|
|
847
|
+
rows: srs.rows,
|
|
848
|
+
rowsAffected: srs.rowsAffected,
|
|
849
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
850
|
+
})
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
var init_daemon_protocol = __esm({
|
|
854
|
+
"src/lib/daemon-protocol.ts"() {
|
|
855
|
+
"use strict";
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// src/lib/db-daemon-client.ts
|
|
860
|
+
var db_daemon_client_exports = {};
|
|
861
|
+
__export(db_daemon_client_exports, {
|
|
862
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
863
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
864
|
+
});
|
|
865
|
+
function normalizeStatement(stmt) {
|
|
866
|
+
if (typeof stmt === "string") {
|
|
867
|
+
return { sql: stmt, args: [] };
|
|
868
|
+
}
|
|
869
|
+
const sql = stmt.sql;
|
|
870
|
+
let args = [];
|
|
871
|
+
if (Array.isArray(stmt.args)) {
|
|
872
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
873
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
874
|
+
const named = {};
|
|
875
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
876
|
+
named[key] = serializeValue(val);
|
|
877
|
+
}
|
|
878
|
+
return { sql, args: named };
|
|
879
|
+
}
|
|
880
|
+
return { sql, args };
|
|
881
|
+
}
|
|
882
|
+
function createDaemonDbClient(fallbackClient) {
|
|
883
|
+
let _useDaemon = false;
|
|
884
|
+
const client = {
|
|
885
|
+
async execute(stmt) {
|
|
886
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
887
|
+
return fallbackClient.execute(stmt);
|
|
888
|
+
}
|
|
889
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
890
|
+
const response = await sendDaemonRequest({
|
|
891
|
+
type: "db-execute",
|
|
892
|
+
sql,
|
|
893
|
+
args
|
|
894
|
+
});
|
|
895
|
+
if (response.error) {
|
|
896
|
+
const errMsg = String(response.error);
|
|
897
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
898
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
899
|
+
`);
|
|
900
|
+
return fallbackClient.execute(stmt);
|
|
901
|
+
}
|
|
902
|
+
throw new Error(errMsg);
|
|
903
|
+
}
|
|
904
|
+
if (response.db) {
|
|
905
|
+
return deserializeResultSet(response.db);
|
|
906
|
+
}
|
|
907
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
908
|
+
return fallbackClient.execute(stmt);
|
|
909
|
+
},
|
|
910
|
+
async batch(stmts, mode) {
|
|
911
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
912
|
+
return fallbackClient.batch(stmts, mode);
|
|
913
|
+
}
|
|
914
|
+
const statements = stmts.map(normalizeStatement);
|
|
915
|
+
const response = await sendDaemonRequest({
|
|
916
|
+
type: "db-batch",
|
|
917
|
+
statements,
|
|
918
|
+
mode: mode ?? "deferred"
|
|
919
|
+
});
|
|
920
|
+
if (response.error) {
|
|
921
|
+
const errMsg = String(response.error);
|
|
922
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
923
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
924
|
+
`);
|
|
925
|
+
return fallbackClient.batch(stmts, mode);
|
|
926
|
+
}
|
|
927
|
+
throw new Error(errMsg);
|
|
928
|
+
}
|
|
929
|
+
const batchResults = response["db-batch"];
|
|
930
|
+
if (batchResults) {
|
|
931
|
+
return batchResults.map(deserializeResultSet);
|
|
932
|
+
}
|
|
933
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
934
|
+
return fallbackClient.batch(stmts, mode);
|
|
935
|
+
},
|
|
936
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
937
|
+
async transaction(mode) {
|
|
938
|
+
return fallbackClient.transaction(mode);
|
|
939
|
+
},
|
|
940
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
941
|
+
async executeMultiple(sql) {
|
|
942
|
+
return fallbackClient.executeMultiple(sql);
|
|
943
|
+
},
|
|
944
|
+
// migrate — delegate to fallback
|
|
945
|
+
async migrate(stmts) {
|
|
946
|
+
return fallbackClient.migrate(stmts);
|
|
947
|
+
},
|
|
948
|
+
// Sync mode — delegate to fallback
|
|
949
|
+
sync() {
|
|
950
|
+
return fallbackClient.sync();
|
|
951
|
+
},
|
|
952
|
+
close() {
|
|
953
|
+
_useDaemon = false;
|
|
954
|
+
},
|
|
955
|
+
get closed() {
|
|
956
|
+
return fallbackClient.closed;
|
|
957
|
+
},
|
|
958
|
+
get protocol() {
|
|
959
|
+
return fallbackClient.protocol;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
return {
|
|
963
|
+
...client,
|
|
964
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
965
|
+
_enableDaemon() {
|
|
966
|
+
_useDaemon = true;
|
|
967
|
+
},
|
|
968
|
+
/** Check if daemon routing is active */
|
|
969
|
+
_isDaemonActive() {
|
|
970
|
+
return _useDaemon && isClientConnected();
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
975
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
976
|
+
const connected = await connectEmbedDaemon();
|
|
977
|
+
if (!connected) {
|
|
978
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
982
|
+
client._enableDaemon();
|
|
983
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
984
|
+
return client;
|
|
985
|
+
}
|
|
986
|
+
var init_db_daemon_client = __esm({
|
|
987
|
+
"src/lib/db-daemon-client.ts"() {
|
|
988
|
+
"use strict";
|
|
989
|
+
init_exe_daemon_client();
|
|
990
|
+
init_daemon_protocol();
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// src/lib/database.ts
|
|
995
|
+
var database_exports = {};
|
|
996
|
+
__export(database_exports, {
|
|
997
|
+
disposeDatabase: () => disposeDatabase,
|
|
998
|
+
disposeTurso: () => disposeTurso,
|
|
999
|
+
ensureSchema: () => ensureSchema,
|
|
1000
|
+
getClient: () => getClient,
|
|
1001
|
+
getRawClient: () => getRawClient,
|
|
1002
|
+
initDaemonClient: () => initDaemonClient,
|
|
1003
|
+
initDatabase: () => initDatabase,
|
|
1004
|
+
initTurso: () => initTurso,
|
|
1005
|
+
isInitialized: () => isInitialized
|
|
1006
|
+
});
|
|
1007
|
+
import { createClient } from "@libsql/client";
|
|
1008
|
+
async function initDatabase(config) {
|
|
1009
|
+
if (_client) {
|
|
1010
|
+
_client.close();
|
|
1011
|
+
_client = null;
|
|
1012
|
+
_resilientClient = null;
|
|
1013
|
+
}
|
|
1014
|
+
const opts = {
|
|
1015
|
+
url: `file:${config.dbPath}`
|
|
1016
|
+
};
|
|
1017
|
+
if (config.encryptionKey) {
|
|
1018
|
+
opts.encryptionKey = config.encryptionKey;
|
|
1019
|
+
}
|
|
1020
|
+
_client = createClient(opts);
|
|
1021
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1022
|
+
}
|
|
1023
|
+
function isInitialized() {
|
|
1024
|
+
return _client !== null;
|
|
1025
|
+
}
|
|
1026
|
+
function getClient() {
|
|
1027
|
+
if (!_resilientClient) {
|
|
1028
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1029
|
+
}
|
|
1030
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1031
|
+
return _resilientClient;
|
|
1032
|
+
}
|
|
1033
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1034
|
+
return _daemonClient;
|
|
1035
|
+
}
|
|
1036
|
+
return _resilientClient;
|
|
1037
|
+
}
|
|
1038
|
+
async function initDaemonClient() {
|
|
1039
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1040
|
+
if (!_resilientClient) return;
|
|
1041
|
+
try {
|
|
1042
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1043
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
process.stderr.write(
|
|
1046
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1047
|
+
`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function getRawClient() {
|
|
1052
|
+
if (!_client) {
|
|
1053
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1054
|
+
}
|
|
1055
|
+
return _client;
|
|
1056
|
+
}
|
|
1057
|
+
async function ensureSchema() {
|
|
1058
|
+
const client = getRawClient();
|
|
1059
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1060
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1061
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1062
|
+
try {
|
|
1063
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1064
|
+
} catch {
|
|
749
1065
|
}
|
|
750
1066
|
await client.executeMultiple(`
|
|
751
|
-
CREATE TABLE IF NOT EXISTS
|
|
752
|
-
id TEXT PRIMARY KEY,
|
|
753
|
-
consolidated_memory_id TEXT NOT NULL,
|
|
754
|
-
source_memory_id TEXT NOT NULL,
|
|
755
|
-
created_at TEXT NOT NULL
|
|
756
|
-
);
|
|
757
|
-
|
|
758
|
-
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
759
|
-
ON consolidations(source_memory_id);
|
|
760
|
-
|
|
761
|
-
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
762
|
-
ON consolidations(consolidated_memory_id);
|
|
763
|
-
`);
|
|
764
|
-
await client.executeMultiple(`
|
|
765
|
-
CREATE TABLE IF NOT EXISTS reminders (
|
|
1067
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
766
1068
|
id TEXT PRIMARY KEY,
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1069
|
+
agent_id TEXT NOT NULL,
|
|
1070
|
+
agent_role TEXT NOT NULL,
|
|
1071
|
+
session_id TEXT NOT NULL,
|
|
1072
|
+
timestamp TEXT NOT NULL,
|
|
1073
|
+
tool_name TEXT NOT NULL,
|
|
1074
|
+
project_name TEXT NOT NULL,
|
|
1075
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1076
|
+
raw_text TEXT NOT NULL,
|
|
1077
|
+
vector F32_BLOB(1024),
|
|
1078
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
771
1079
|
);
|
|
1080
|
+
|
|
1081
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1082
|
+
ON memories(agent_id);
|
|
1083
|
+
|
|
1084
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1085
|
+
ON memories(timestamp);
|
|
1086
|
+
|
|
1087
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1088
|
+
ON memories(session_id);
|
|
1089
|
+
|
|
1090
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1091
|
+
ON memories(project_name);
|
|
1092
|
+
|
|
1093
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1094
|
+
ON memories(tool_name);
|
|
1095
|
+
|
|
1096
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1097
|
+
ON memories(version);
|
|
1098
|
+
|
|
1099
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1100
|
+
ON memories(agent_id, project_name);
|
|
772
1101
|
`);
|
|
773
1102
|
await client.executeMultiple(`
|
|
774
|
-
CREATE TABLE IF NOT EXISTS
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
event TEXT NOT NULL,
|
|
779
|
-
project TEXT NOT NULL,
|
|
780
|
-
summary TEXT NOT NULL,
|
|
781
|
-
task_file TEXT,
|
|
782
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
783
|
-
created_at TEXT NOT NULL
|
|
1103
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1104
|
+
raw_text,
|
|
1105
|
+
content='memories',
|
|
1106
|
+
content_rowid='rowid'
|
|
784
1107
|
);
|
|
785
1108
|
|
|
786
|
-
CREATE
|
|
787
|
-
|
|
1109
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1110
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1111
|
+
END;
|
|
788
1112
|
|
|
789
|
-
CREATE
|
|
790
|
-
|
|
1113
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1114
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1115
|
+
END;
|
|
791
1116
|
|
|
792
|
-
CREATE
|
|
793
|
-
|
|
1117
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1118
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1119
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1120
|
+
END;
|
|
794
1121
|
`);
|
|
795
1122
|
await client.executeMultiple(`
|
|
796
|
-
CREATE TABLE IF NOT EXISTS
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
description TEXT NOT NULL,
|
|
800
|
-
job_type TEXT NOT NULL DEFAULT 'report',
|
|
801
|
-
prompt TEXT,
|
|
802
|
-
assigned_to TEXT,
|
|
803
|
-
project_name TEXT,
|
|
804
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
805
|
-
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
806
|
-
created_at TEXT NOT NULL
|
|
1123
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1124
|
+
key TEXT PRIMARY KEY,
|
|
1125
|
+
value TEXT NOT NULL
|
|
807
1126
|
);
|
|
808
1127
|
`);
|
|
809
1128
|
await client.executeMultiple(`
|
|
810
|
-
CREATE TABLE IF NOT EXISTS
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1129
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1130
|
+
id TEXT PRIMARY KEY,
|
|
1131
|
+
title TEXT NOT NULL,
|
|
1132
|
+
assigned_to TEXT NOT NULL,
|
|
1133
|
+
assigned_by TEXT NOT NULL,
|
|
1134
|
+
project_name TEXT NOT NULL,
|
|
1135
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1136
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1137
|
+
task_file TEXT,
|
|
1138
|
+
created_at TEXT NOT NULL,
|
|
1139
|
+
updated_at TEXT NOT NULL
|
|
818
1140
|
);
|
|
1141
|
+
|
|
1142
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1143
|
+
ON tasks(assigned_to, status);
|
|
819
1144
|
`);
|
|
820
1145
|
await client.executeMultiple(`
|
|
821
|
-
CREATE TABLE IF NOT EXISTS
|
|
822
|
-
id
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
status TEXT DEFAULT 'pending',
|
|
831
|
-
server_seq INTEGER,
|
|
832
|
-
retry_count INTEGER DEFAULT 0,
|
|
833
|
-
created_at TEXT NOT NULL,
|
|
834
|
-
delivered_at TEXT,
|
|
835
|
-
processed_at TEXT,
|
|
836
|
-
failed_at TEXT,
|
|
837
|
-
failure_reason TEXT
|
|
1146
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1147
|
+
id TEXT PRIMARY KEY,
|
|
1148
|
+
agent_id TEXT NOT NULL,
|
|
1149
|
+
project_name TEXT,
|
|
1150
|
+
domain TEXT,
|
|
1151
|
+
content TEXT NOT NULL,
|
|
1152
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1153
|
+
created_at TEXT NOT NULL,
|
|
1154
|
+
updated_at TEXT NOT NULL
|
|
838
1155
|
);
|
|
839
1156
|
|
|
840
|
-
CREATE INDEX IF NOT EXISTS
|
|
841
|
-
ON
|
|
842
|
-
|
|
843
|
-
|
|
1157
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1158
|
+
ON behaviors(agent_id, active);
|
|
1159
|
+
`);
|
|
1160
|
+
try {
|
|
1161
|
+
const coordinatorName = getCoordinatorName();
|
|
1162
|
+
const existing = await client.execute({
|
|
1163
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1164
|
+
args: [coordinatorName]
|
|
1165
|
+
});
|
|
1166
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1167
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1168
|
+
for (const [domain, content] of [
|
|
1169
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1170
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1171
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1172
|
+
]) {
|
|
1173
|
+
await client.execute({
|
|
1174
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1175
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1176
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
await client.execute({
|
|
1184
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1185
|
+
args: []
|
|
1186
|
+
});
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
await client.execute({
|
|
1191
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1192
|
+
args: []
|
|
1193
|
+
});
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
try {
|
|
1197
|
+
await client.execute({
|
|
1198
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1199
|
+
args: []
|
|
1200
|
+
});
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
try {
|
|
1204
|
+
await client.execute({
|
|
1205
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1206
|
+
ON tasks(parent_task_id)
|
|
1207
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1208
|
+
args: []
|
|
1209
|
+
});
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
await client.execute({
|
|
1214
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1215
|
+
args: []
|
|
1216
|
+
});
|
|
1217
|
+
} catch {
|
|
1218
|
+
}
|
|
1219
|
+
try {
|
|
1220
|
+
await client.execute({
|
|
1221
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1222
|
+
args: []
|
|
1223
|
+
});
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
await client.execute({
|
|
1228
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1229
|
+
args: []
|
|
1230
|
+
});
|
|
1231
|
+
} catch {
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
await client.execute({
|
|
1235
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1236
|
+
args: []
|
|
1237
|
+
});
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
try {
|
|
1241
|
+
await client.execute({
|
|
1242
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1243
|
+
args: []
|
|
1244
|
+
});
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
await client.execute({
|
|
1249
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1250
|
+
args: []
|
|
1251
|
+
});
|
|
1252
|
+
} catch {
|
|
1253
|
+
}
|
|
1254
|
+
try {
|
|
1255
|
+
await client.execute({
|
|
1256
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1257
|
+
args: []
|
|
1258
|
+
});
|
|
1259
|
+
} catch {
|
|
1260
|
+
}
|
|
1261
|
+
try {
|
|
1262
|
+
await client.execute({
|
|
1263
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1264
|
+
args: []
|
|
1265
|
+
});
|
|
1266
|
+
} catch {
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
await client.execute({
|
|
1270
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1271
|
+
args: []
|
|
1272
|
+
});
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
await client.execute({
|
|
1277
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1278
|
+
args: []
|
|
1279
|
+
});
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
try {
|
|
1283
|
+
await client.execute({
|
|
1284
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1285
|
+
args: []
|
|
1286
|
+
});
|
|
1287
|
+
} catch {
|
|
1288
|
+
}
|
|
1289
|
+
try {
|
|
1290
|
+
await client.execute({
|
|
1291
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1292
|
+
args: []
|
|
1293
|
+
});
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
await client.execute({
|
|
1298
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1299
|
+
args: []
|
|
1300
|
+
});
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
await client.executeMultiple(`
|
|
1304
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1305
|
+
id TEXT PRIMARY KEY,
|
|
1306
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1307
|
+
source_memory_id TEXT NOT NULL,
|
|
1308
|
+
created_at TEXT NOT NULL
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1312
|
+
ON consolidations(source_memory_id);
|
|
1313
|
+
|
|
1314
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1315
|
+
ON consolidations(consolidated_memory_id);
|
|
1316
|
+
`);
|
|
1317
|
+
await client.executeMultiple(`
|
|
1318
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1319
|
+
id TEXT PRIMARY KEY,
|
|
1320
|
+
text TEXT NOT NULL,
|
|
1321
|
+
created_at TEXT NOT NULL,
|
|
1322
|
+
due_date TEXT,
|
|
1323
|
+
completed_at TEXT
|
|
1324
|
+
);
|
|
1325
|
+
`);
|
|
1326
|
+
await client.executeMultiple(`
|
|
1327
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1328
|
+
id TEXT PRIMARY KEY,
|
|
1329
|
+
agent_id TEXT NOT NULL,
|
|
1330
|
+
agent_role TEXT NOT NULL,
|
|
1331
|
+
event TEXT NOT NULL,
|
|
1332
|
+
project TEXT NOT NULL,
|
|
1333
|
+
summary TEXT NOT NULL,
|
|
1334
|
+
task_file TEXT,
|
|
1335
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1336
|
+
created_at TEXT NOT NULL
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1340
|
+
ON notifications(read);
|
|
1341
|
+
|
|
1342
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1343
|
+
ON notifications(agent_id);
|
|
1344
|
+
|
|
1345
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1346
|
+
ON notifications(task_file);
|
|
1347
|
+
`);
|
|
1348
|
+
await client.executeMultiple(`
|
|
1349
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1350
|
+
id TEXT PRIMARY KEY,
|
|
1351
|
+
cron TEXT NOT NULL,
|
|
1352
|
+
description TEXT NOT NULL,
|
|
1353
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1354
|
+
prompt TEXT,
|
|
1355
|
+
assigned_to TEXT,
|
|
1356
|
+
project_name TEXT,
|
|
1357
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1358
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1359
|
+
created_at TEXT NOT NULL
|
|
1360
|
+
);
|
|
1361
|
+
`);
|
|
1362
|
+
await client.executeMultiple(`
|
|
1363
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1364
|
+
device_id TEXT PRIMARY KEY,
|
|
1365
|
+
friendly_name TEXT NOT NULL,
|
|
1366
|
+
hostname TEXT NOT NULL,
|
|
1367
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1368
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1369
|
+
connected INTEGER DEFAULT 0,
|
|
1370
|
+
last_seen TEXT NOT NULL
|
|
1371
|
+
);
|
|
1372
|
+
`);
|
|
1373
|
+
await client.executeMultiple(`
|
|
1374
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1375
|
+
id TEXT PRIMARY KEY,
|
|
1376
|
+
from_agent TEXT NOT NULL,
|
|
1377
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
1378
|
+
target_agent TEXT NOT NULL,
|
|
1379
|
+
target_project TEXT,
|
|
1380
|
+
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1381
|
+
content TEXT NOT NULL,
|
|
1382
|
+
priority TEXT DEFAULT 'normal',
|
|
1383
|
+
status TEXT DEFAULT 'pending',
|
|
1384
|
+
server_seq INTEGER,
|
|
1385
|
+
retry_count INTEGER DEFAULT 0,
|
|
1386
|
+
created_at TEXT NOT NULL,
|
|
1387
|
+
delivered_at TEXT,
|
|
1388
|
+
processed_at TEXT,
|
|
1389
|
+
failed_at TEXT,
|
|
1390
|
+
failure_reason TEXT
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1394
|
+
ON messages(target_agent, status);
|
|
1395
|
+
|
|
1396
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
844
1397
|
ON messages(target_agent, from_agent, server_seq);
|
|
845
1398
|
`);
|
|
846
1399
|
try {
|
|
@@ -981,6 +1534,12 @@ async function ensureSchema() {
|
|
|
981
1534
|
} catch {
|
|
982
1535
|
}
|
|
983
1536
|
}
|
|
1537
|
+
try {
|
|
1538
|
+
await client.execute(
|
|
1539
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1540
|
+
);
|
|
1541
|
+
} catch {
|
|
1542
|
+
}
|
|
984
1543
|
await client.executeMultiple(`
|
|
985
1544
|
CREATE TABLE IF NOT EXISTS entities (
|
|
986
1545
|
id TEXT PRIMARY KEY,
|
|
@@ -1033,7 +1592,30 @@ async function ensureSchema() {
|
|
|
1033
1592
|
entity_id TEXT NOT NULL,
|
|
1034
1593
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1035
1594
|
);
|
|
1595
|
+
|
|
1596
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1597
|
+
name,
|
|
1598
|
+
content=entities,
|
|
1599
|
+
content_rowid=rowid
|
|
1600
|
+
);
|
|
1601
|
+
|
|
1602
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1603
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1604
|
+
END;
|
|
1605
|
+
|
|
1606
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1607
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1608
|
+
END;
|
|
1609
|
+
|
|
1610
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1611
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1612
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1613
|
+
END;
|
|
1036
1614
|
`);
|
|
1615
|
+
try {
|
|
1616
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1037
1619
|
await client.executeMultiple(`
|
|
1038
1620
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1039
1621
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1214,6 +1796,33 @@ async function ensureSchema() {
|
|
|
1214
1796
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1215
1797
|
ON conversations(channel_id);
|
|
1216
1798
|
`);
|
|
1799
|
+
await client.executeMultiple(`
|
|
1800
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1801
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1802
|
+
agent_id TEXT NOT NULL,
|
|
1803
|
+
session_name TEXT,
|
|
1804
|
+
task_id TEXT,
|
|
1805
|
+
project_name TEXT,
|
|
1806
|
+
started_at TEXT NOT NULL
|
|
1807
|
+
);
|
|
1808
|
+
|
|
1809
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1810
|
+
ON session_agent_map(agent_id);
|
|
1811
|
+
`);
|
|
1812
|
+
try {
|
|
1813
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1814
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1815
|
+
await client.execute({
|
|
1816
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1817
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1818
|
+
FROM memories
|
|
1819
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1820
|
+
GROUP BY session_id, agent_id`,
|
|
1821
|
+
args: []
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
} catch {
|
|
1825
|
+
}
|
|
1217
1826
|
try {
|
|
1218
1827
|
await client.execute({
|
|
1219
1828
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1347,15 +1956,41 @@ async function ensureSchema() {
|
|
|
1347
1956
|
});
|
|
1348
1957
|
} catch {
|
|
1349
1958
|
}
|
|
1959
|
+
for (const col of [
|
|
1960
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1961
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1962
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1963
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1964
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1965
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1966
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1967
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1968
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1969
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1970
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1971
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1972
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1973
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1974
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1975
|
+
]) {
|
|
1976
|
+
try {
|
|
1977
|
+
await client.execute(col);
|
|
1978
|
+
} catch {
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1350
1981
|
}
|
|
1351
1982
|
async function disposeDatabase() {
|
|
1983
|
+
if (_daemonClient) {
|
|
1984
|
+
_daemonClient.close();
|
|
1985
|
+
_daemonClient = null;
|
|
1986
|
+
}
|
|
1352
1987
|
if (_client) {
|
|
1353
1988
|
_client.close();
|
|
1354
1989
|
_client = null;
|
|
1355
1990
|
_resilientClient = null;
|
|
1356
1991
|
}
|
|
1357
1992
|
}
|
|
1358
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1993
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1359
1994
|
var init_database = __esm({
|
|
1360
1995
|
"src/lib/database.ts"() {
|
|
1361
1996
|
"use strict";
|
|
@@ -1363,6 +1998,7 @@ var init_database = __esm({
|
|
|
1363
1998
|
init_employees();
|
|
1364
1999
|
_client = null;
|
|
1365
2000
|
_resilientClient = null;
|
|
2001
|
+
_daemonClient = null;
|
|
1366
2002
|
initTurso = initDatabase;
|
|
1367
2003
|
disposeTurso = disposeDatabase;
|
|
1368
2004
|
}
|
|
@@ -1436,12 +2072,12 @@ __export(shard_manager_exports, {
|
|
|
1436
2072
|
listShards: () => listShards,
|
|
1437
2073
|
shardExists: () => shardExists
|
|
1438
2074
|
});
|
|
1439
|
-
import
|
|
1440
|
-
import { existsSync as
|
|
2075
|
+
import path7 from "path";
|
|
2076
|
+
import { existsSync as existsSync6, mkdirSync, readdirSync as readdirSync2 } from "fs";
|
|
1441
2077
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1442
2078
|
function initShardManager(encryptionKey) {
|
|
1443
2079
|
_encryptionKey = encryptionKey;
|
|
1444
|
-
if (!
|
|
2080
|
+
if (!existsSync6(SHARDS_DIR)) {
|
|
1445
2081
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1446
2082
|
}
|
|
1447
2083
|
_shardingEnabled = true;
|
|
@@ -1462,7 +2098,7 @@ function getShardClient(projectName) {
|
|
|
1462
2098
|
}
|
|
1463
2099
|
const cached = _shards.get(safeName);
|
|
1464
2100
|
if (cached) return cached;
|
|
1465
|
-
const dbPath =
|
|
2101
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
1466
2102
|
const client = createClient2({
|
|
1467
2103
|
url: `file:${dbPath}`,
|
|
1468
2104
|
encryptionKey: _encryptionKey
|
|
@@ -1472,10 +2108,10 @@ function getShardClient(projectName) {
|
|
|
1472
2108
|
}
|
|
1473
2109
|
function shardExists(projectName) {
|
|
1474
2110
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1475
|
-
return
|
|
2111
|
+
return existsSync6(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
1476
2112
|
}
|
|
1477
2113
|
function listShards() {
|
|
1478
|
-
if (!
|
|
2114
|
+
if (!existsSync6(SHARDS_DIR)) return [];
|
|
1479
2115
|
return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1480
2116
|
}
|
|
1481
2117
|
async function ensureShardSchema(client) {
|
|
@@ -1661,7 +2297,7 @@ var init_shard_manager = __esm({
|
|
|
1661
2297
|
"src/lib/shard-manager.ts"() {
|
|
1662
2298
|
"use strict";
|
|
1663
2299
|
init_config();
|
|
1664
|
-
SHARDS_DIR =
|
|
2300
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
1665
2301
|
_shards = /* @__PURE__ */ new Map();
|
|
1666
2302
|
_encryptionKey = null;
|
|
1667
2303
|
_shardingEnabled = false;
|
|
@@ -1771,807 +2407,462 @@ var init_platform_procedures = __esm({
|
|
|
1771
2407
|
priority: "p0",
|
|
1772
2408
|
content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
|
|
1773
2409
|
}
|
|
1774
|
-
];
|
|
1775
|
-
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
1776
|
-
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
1777
|
-
);
|
|
1778
|
-
}
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
// src/lib/global-procedures.ts
|
|
1782
|
-
var global_procedures_exports = {};
|
|
1783
|
-
__export(global_procedures_exports, {
|
|
1784
|
-
deactivateGlobalProcedure: () => deactivateGlobalProcedure,
|
|
1785
|
-
getGlobalProceduresBlock: () => getGlobalProceduresBlock,
|
|
1786
|
-
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
1787
|
-
storeGlobalProcedure: () => storeGlobalProcedure
|
|
1788
|
-
});
|
|
1789
|
-
import { randomUUID } from "crypto";
|
|
1790
|
-
async function loadGlobalProcedures() {
|
|
1791
|
-
const client = getClient();
|
|
1792
|
-
const result = await client.execute({
|
|
1793
|
-
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
1794
|
-
args: []
|
|
1795
|
-
});
|
|
1796
|
-
const allRows = result.rows;
|
|
1797
|
-
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
1798
|
-
if (customerOnly.length > 0) {
|
|
1799
|
-
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
1800
|
-
${p.content}`).join("\n\n");
|
|
1801
|
-
} else {
|
|
1802
|
-
_customerCache = "";
|
|
1803
|
-
}
|
|
1804
|
-
_cacheLoaded = true;
|
|
1805
|
-
return customerOnly;
|
|
1806
|
-
}
|
|
1807
|
-
function getGlobalProceduresBlock() {
|
|
1808
|
-
const sections = [];
|
|
1809
|
-
if (_platformCache) sections.push(_platformCache);
|
|
1810
|
-
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
1811
|
-
if (sections.length === 0) return "";
|
|
1812
|
-
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1813
|
-
|
|
1814
|
-
${sections.join("\n\n")}
|
|
1815
|
-
`;
|
|
1816
|
-
}
|
|
1817
|
-
async function storeGlobalProcedure(input2) {
|
|
1818
|
-
const id = randomUUID();
|
|
1819
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1820
|
-
const client = getClient();
|
|
1821
|
-
await client.execute({
|
|
1822
|
-
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
1823
|
-
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
1824
|
-
args: [id, input2.title, input2.content, input2.priority ?? "p0", input2.domain ?? null, now, now]
|
|
1825
|
-
});
|
|
1826
|
-
await loadGlobalProcedures();
|
|
1827
|
-
return id;
|
|
1828
|
-
}
|
|
1829
|
-
async function deactivateGlobalProcedure(id) {
|
|
1830
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1831
|
-
const client = getClient();
|
|
1832
|
-
const result = await client.execute({
|
|
1833
|
-
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
1834
|
-
args: [now, id]
|
|
1835
|
-
});
|
|
1836
|
-
await loadGlobalProcedures();
|
|
1837
|
-
return result.rowsAffected > 0;
|
|
1838
|
-
}
|
|
1839
|
-
var _customerCache, _cacheLoaded, _platformCache;
|
|
1840
|
-
var init_global_procedures = __esm({
|
|
1841
|
-
"src/lib/global-procedures.ts"() {
|
|
1842
|
-
"use strict";
|
|
1843
|
-
init_database();
|
|
1844
|
-
init_platform_procedures();
|
|
1845
|
-
_customerCache = "";
|
|
1846
|
-
_cacheLoaded = false;
|
|
1847
|
-
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
1848
|
-
${p.content}`).join("\n\n");
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
// src/lib/notifications.ts
|
|
1853
|
-
import crypto2 from "crypto";
|
|
1854
|
-
import path7 from "path";
|
|
1855
|
-
import os4 from "os";
|
|
1856
|
-
import {
|
|
1857
|
-
readFileSync as readFileSync4,
|
|
1858
|
-
readdirSync as readdirSync3,
|
|
1859
|
-
unlinkSync as unlinkSync2,
|
|
1860
|
-
existsSync as existsSync6,
|
|
1861
|
-
rmdirSync
|
|
1862
|
-
} from "fs";
|
|
1863
|
-
async function writeNotification(notification) {
|
|
1864
|
-
try {
|
|
1865
|
-
const client = getClient();
|
|
1866
|
-
const id = crypto2.randomUUID();
|
|
1867
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1868
|
-
await client.execute({
|
|
1869
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
1870
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
1871
|
-
args: [
|
|
1872
|
-
id,
|
|
1873
|
-
notification.agentId,
|
|
1874
|
-
notification.agentRole,
|
|
1875
|
-
notification.event,
|
|
1876
|
-
notification.project,
|
|
1877
|
-
notification.summary,
|
|
1878
|
-
notification.taskFile ?? null,
|
|
1879
|
-
now
|
|
1880
|
-
]
|
|
1881
|
-
});
|
|
1882
|
-
} catch (err) {
|
|
1883
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
1884
|
-
`);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
1888
|
-
try {
|
|
1889
|
-
const client = getClient();
|
|
1890
|
-
await client.execute({
|
|
1891
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
1892
|
-
args: [taskFile]
|
|
1893
|
-
});
|
|
1894
|
-
} catch {
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
var init_notifications = __esm({
|
|
1898
|
-
"src/lib/notifications.ts"() {
|
|
1899
|
-
"use strict";
|
|
1900
|
-
init_database();
|
|
1901
|
-
}
|
|
1902
|
-
});
|
|
1903
|
-
|
|
1904
|
-
// src/lib/license.ts
|
|
1905
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1906
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1907
|
-
import path8 from "path";
|
|
1908
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
1909
|
-
async function fetchRetry(url, init) {
|
|
1910
|
-
try {
|
|
1911
|
-
return await fetch(url, init);
|
|
1912
|
-
} catch {
|
|
1913
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1914
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
function loadDeviceId() {
|
|
1918
|
-
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
1919
|
-
try {
|
|
1920
|
-
if (existsSync7(deviceJsonPath)) {
|
|
1921
|
-
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
1922
|
-
if (data.deviceId) return data.deviceId;
|
|
1923
|
-
}
|
|
1924
|
-
} catch {
|
|
1925
|
-
}
|
|
1926
|
-
try {
|
|
1927
|
-
if (existsSync7(DEVICE_ID_PATH)) {
|
|
1928
|
-
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
1929
|
-
if (id2) return id2;
|
|
1930
|
-
}
|
|
1931
|
-
} catch {
|
|
1932
|
-
}
|
|
1933
|
-
const id = randomUUID2();
|
|
1934
|
-
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1935
|
-
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
1936
|
-
return id;
|
|
1937
|
-
}
|
|
1938
|
-
function loadLicense() {
|
|
1939
|
-
try {
|
|
1940
|
-
if (!existsSync7(LICENSE_PATH)) return null;
|
|
1941
|
-
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
1942
|
-
} catch {
|
|
1943
|
-
return null;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
function saveLicense(apiKey) {
|
|
1947
|
-
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1948
|
-
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1949
|
-
}
|
|
1950
|
-
async function verifyLicenseJwt(token) {
|
|
1951
|
-
try {
|
|
1952
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1953
|
-
const { payload } = await jwtVerify(token, key, {
|
|
1954
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
1955
|
-
});
|
|
1956
|
-
const plan = payload.plan ?? "free";
|
|
1957
|
-
const email = payload.sub ?? "";
|
|
1958
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1959
|
-
return {
|
|
1960
|
-
valid: true,
|
|
1961
|
-
plan,
|
|
1962
|
-
email,
|
|
1963
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1964
|
-
deviceLimit: limits.devices,
|
|
1965
|
-
employeeLimit: limits.employees,
|
|
1966
|
-
memoryLimit: limits.memories
|
|
1967
|
-
};
|
|
1968
|
-
} catch {
|
|
1969
|
-
return null;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
async function getCachedLicense() {
|
|
1973
|
-
try {
|
|
1974
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
1975
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
1976
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1977
|
-
return await verifyLicenseJwt(raw.token);
|
|
1978
|
-
} catch {
|
|
1979
|
-
return null;
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
function readCachedToken() {
|
|
1983
|
-
try {
|
|
1984
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
1985
|
-
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
1986
|
-
return typeof raw.token === "string" ? raw.token : null;
|
|
1987
|
-
} catch {
|
|
1988
|
-
return null;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
function getRawCachedPlan() {
|
|
1992
|
-
try {
|
|
1993
|
-
const token = readCachedToken();
|
|
1994
|
-
if (!token) return null;
|
|
1995
|
-
const parts = token.split(".");
|
|
1996
|
-
if (parts.length !== 3) return null;
|
|
1997
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1998
|
-
const plan = payload.plan ?? "free";
|
|
1999
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2000
|
-
process.stderr.write(
|
|
2001
|
-
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2002
|
-
`
|
|
2003
|
-
);
|
|
2004
|
-
return {
|
|
2005
|
-
valid: true,
|
|
2006
|
-
plan,
|
|
2007
|
-
email: payload.sub ?? "",
|
|
2008
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2009
|
-
deviceLimit: limits.devices,
|
|
2010
|
-
employeeLimit: limits.employees,
|
|
2011
|
-
memoryLimit: limits.memories
|
|
2012
|
-
};
|
|
2013
|
-
} catch {
|
|
2014
|
-
return null;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
function cacheResponse(token) {
|
|
2018
|
-
try {
|
|
2019
|
-
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2020
|
-
} catch {
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
async function validateLicense(apiKey, deviceId) {
|
|
2024
|
-
const did = deviceId ?? loadDeviceId();
|
|
2025
|
-
try {
|
|
2026
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2027
|
-
method: "POST",
|
|
2028
|
-
headers: { "Content-Type": "application/json" },
|
|
2029
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2030
|
-
signal: AbortSignal.timeout(1e4)
|
|
2031
|
-
});
|
|
2032
|
-
if (res.ok) {
|
|
2033
|
-
const data = await res.json();
|
|
2034
|
-
if (data.error === "device_limit_exceeded") {
|
|
2035
|
-
const cached2 = await getCachedLicense();
|
|
2036
|
-
if (cached2) return cached2;
|
|
2037
|
-
const raw2 = getRawCachedPlan();
|
|
2038
|
-
if (raw2) return { ...raw2, valid: false };
|
|
2039
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2040
|
-
}
|
|
2041
|
-
if (data.token) {
|
|
2042
|
-
cacheResponse(data.token);
|
|
2043
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
2044
|
-
if (verified) return verified;
|
|
2045
|
-
}
|
|
2046
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2047
|
-
return {
|
|
2048
|
-
valid: data.valid,
|
|
2049
|
-
plan: data.plan,
|
|
2050
|
-
email: data.email,
|
|
2051
|
-
expiresAt: data.expiresAt,
|
|
2052
|
-
deviceLimit: limits.devices,
|
|
2053
|
-
employeeLimit: limits.employees,
|
|
2054
|
-
memoryLimit: limits.memories
|
|
2055
|
-
};
|
|
2056
|
-
}
|
|
2057
|
-
const cached = await getCachedLicense();
|
|
2058
|
-
if (cached) return cached;
|
|
2059
|
-
const raw = getRawCachedPlan();
|
|
2060
|
-
if (raw) return raw;
|
|
2061
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2062
|
-
} catch {
|
|
2063
|
-
const cached = await getCachedLicense();
|
|
2064
|
-
if (cached) return cached;
|
|
2065
|
-
const rawFallback = getRawCachedPlan();
|
|
2066
|
-
if (rawFallback) return rawFallback;
|
|
2067
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
function getCacheAgeMs() {
|
|
2071
|
-
try {
|
|
2072
|
-
const { statSync: statSync3 } = __require("fs");
|
|
2073
|
-
const s = statSync3(CACHE_PATH);
|
|
2074
|
-
return Date.now() - s.mtimeMs;
|
|
2075
|
-
} catch {
|
|
2076
|
-
return Infinity;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
async function checkLicense() {
|
|
2080
|
-
let key = loadLicense();
|
|
2081
|
-
if (!key) {
|
|
2082
|
-
try {
|
|
2083
|
-
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
2084
|
-
if (existsSync7(configPath)) {
|
|
2085
|
-
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
2086
|
-
const cloud = raw.cloud;
|
|
2087
|
-
if (cloud?.apiKey) {
|
|
2088
|
-
key = cloud.apiKey;
|
|
2089
|
-
saveLicense(key);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
} catch {
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (!key) return FREE_LICENSE;
|
|
2096
|
-
const cached = await getCachedLicense();
|
|
2097
|
-
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
2098
|
-
const deviceId = loadDeviceId();
|
|
2099
|
-
return validateLicense(key, deviceId);
|
|
2100
|
-
}
|
|
2101
|
-
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;
|
|
2102
|
-
var init_license = __esm({
|
|
2103
|
-
"src/lib/license.ts"() {
|
|
2104
|
-
"use strict";
|
|
2105
|
-
init_config();
|
|
2106
|
-
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2107
|
-
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2108
|
-
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2109
|
-
API_BASE = "https://askexe.com/cloud";
|
|
2110
|
-
RETRY_DELAY_MS = 500;
|
|
2111
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
2112
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
2113
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
2114
|
-
-----END PUBLIC KEY-----`;
|
|
2115
|
-
LICENSE_JWT_ALG = "ES256";
|
|
2116
|
-
PLAN_LIMITS = {
|
|
2117
|
-
free: { devices: 1, employees: 1, memories: 5e4 },
|
|
2118
|
-
pro: { devices: 2, employees: 5, memories: 25e4 },
|
|
2119
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
2120
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
2121
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
2122
|
-
};
|
|
2123
|
-
FREE_LICENSE = {
|
|
2124
|
-
valid: true,
|
|
2125
|
-
plan: "free",
|
|
2126
|
-
email: "",
|
|
2127
|
-
expiresAt: null,
|
|
2128
|
-
deviceLimit: 1,
|
|
2129
|
-
employeeLimit: 1,
|
|
2130
|
-
memoryLimit: 5e4
|
|
2131
|
-
};
|
|
2132
|
-
CACHE_MAX_AGE_MS = 36e5;
|
|
2410
|
+
];
|
|
2411
|
+
PLATFORM_PROCEDURE_TITLES = new Set(
|
|
2412
|
+
PLATFORM_PROCEDURES.map((p) => p.title)
|
|
2413
|
+
);
|
|
2133
2414
|
}
|
|
2134
2415
|
});
|
|
2135
2416
|
|
|
2136
|
-
// src/lib/
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
} catch {
|
|
2159
|
-
return freeLicense();
|
|
2417
|
+
// src/lib/global-procedures.ts
|
|
2418
|
+
var global_procedures_exports = {};
|
|
2419
|
+
__export(global_procedures_exports, {
|
|
2420
|
+
deactivateGlobalProcedure: () => deactivateGlobalProcedure,
|
|
2421
|
+
getGlobalProceduresBlock: () => getGlobalProceduresBlock,
|
|
2422
|
+
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
2423
|
+
storeGlobalProcedure: () => storeGlobalProcedure
|
|
2424
|
+
});
|
|
2425
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2426
|
+
async function loadGlobalProcedures() {
|
|
2427
|
+
const client = getClient();
|
|
2428
|
+
const result = await client.execute({
|
|
2429
|
+
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
2430
|
+
args: []
|
|
2431
|
+
});
|
|
2432
|
+
const allRows = result.rows;
|
|
2433
|
+
const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
|
|
2434
|
+
if (customerOnly.length > 0) {
|
|
2435
|
+
_customerCache = customerOnly.map((p) => `### ${p.title}
|
|
2436
|
+
${p.content}`).join("\n\n");
|
|
2437
|
+
} else {
|
|
2438
|
+
_customerCache = "";
|
|
2160
2439
|
}
|
|
2440
|
+
_cacheLoaded = true;
|
|
2441
|
+
return customerOnly;
|
|
2161
2442
|
}
|
|
2162
|
-
function
|
|
2163
|
-
const
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
memoryLimit: limits.memories
|
|
2172
|
-
};
|
|
2443
|
+
function getGlobalProceduresBlock() {
|
|
2444
|
+
const sections = [];
|
|
2445
|
+
if (_platformCache) sections.push(_platformCache);
|
|
2446
|
+
if (_cacheLoaded && _customerCache) sections.push(_customerCache);
|
|
2447
|
+
if (sections.length === 0) return "";
|
|
2448
|
+
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
2449
|
+
|
|
2450
|
+
${sections.join("\n\n")}
|
|
2451
|
+
`;
|
|
2173
2452
|
}
|
|
2174
|
-
async function
|
|
2175
|
-
|
|
2453
|
+
async function storeGlobalProcedure(input2) {
|
|
2454
|
+
const id = randomUUID2();
|
|
2455
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2176
2456
|
const client = getClient();
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2457
|
+
await client.execute({
|
|
2458
|
+
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
2459
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
2460
|
+
args: [id, input2.title, input2.content, input2.priority ?? "p0", input2.domain ?? null, now, now]
|
|
2461
|
+
});
|
|
2462
|
+
await loadGlobalProcedures();
|
|
2463
|
+
return id;
|
|
2182
2464
|
}
|
|
2183
|
-
async function
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2465
|
+
async function deactivateGlobalProcedure(id) {
|
|
2466
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2467
|
+
const client = getClient();
|
|
2468
|
+
const result = await client.execute({
|
|
2469
|
+
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
2470
|
+
args: [now, id]
|
|
2471
|
+
});
|
|
2472
|
+
await loadGlobalProcedures();
|
|
2473
|
+
return result.rowsAffected > 0;
|
|
2474
|
+
}
|
|
2475
|
+
var _customerCache, _cacheLoaded, _platformCache;
|
|
2476
|
+
var init_global_procedures = __esm({
|
|
2477
|
+
"src/lib/global-procedures.ts"() {
|
|
2478
|
+
"use strict";
|
|
2479
|
+
init_database();
|
|
2480
|
+
init_platform_procedures();
|
|
2481
|
+
_customerCache = "";
|
|
2482
|
+
_cacheLoaded = false;
|
|
2483
|
+
_platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
|
|
2484
|
+
${p.content}`).join("\n\n");
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
// src/lib/notifications.ts
|
|
2489
|
+
import crypto2 from "crypto";
|
|
2490
|
+
import path8 from "path";
|
|
2491
|
+
import os5 from "os";
|
|
2492
|
+
import {
|
|
2493
|
+
readFileSync as readFileSync5,
|
|
2494
|
+
readdirSync as readdirSync3,
|
|
2495
|
+
unlinkSync as unlinkSync3,
|
|
2496
|
+
existsSync as existsSync7,
|
|
2497
|
+
rmdirSync
|
|
2498
|
+
} from "fs";
|
|
2499
|
+
async function writeNotification(notification) {
|
|
2500
|
+
try {
|
|
2501
|
+
const client = getClient();
|
|
2502
|
+
const id = crypto2.randomUUID();
|
|
2503
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2504
|
+
await client.execute({
|
|
2505
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2506
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2507
|
+
args: [
|
|
2508
|
+
id,
|
|
2509
|
+
notification.agentId,
|
|
2510
|
+
notification.agentRole,
|
|
2511
|
+
notification.event,
|
|
2512
|
+
notification.project,
|
|
2513
|
+
notification.summary,
|
|
2514
|
+
notification.taskFile ?? null,
|
|
2515
|
+
now
|
|
2516
|
+
]
|
|
2517
|
+
});
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2520
|
+
`);
|
|
2191
2521
|
}
|
|
2192
2522
|
}
|
|
2193
|
-
function
|
|
2194
|
-
const license = getLicenseSync();
|
|
2195
|
-
if (license.employeeLimit < 0) return;
|
|
2196
|
-
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2197
|
-
let count = 0;
|
|
2523
|
+
async function markAsReadByTaskFile(taskFile) {
|
|
2198
2524
|
try {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
}
|
|
2525
|
+
const client = getClient();
|
|
2526
|
+
await client.execute({
|
|
2527
|
+
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
2528
|
+
args: [taskFile]
|
|
2529
|
+
});
|
|
2204
2530
|
} catch {
|
|
2205
|
-
throw new PlanLimitError(
|
|
2206
|
-
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
2207
|
-
);
|
|
2208
|
-
}
|
|
2209
|
-
if (count >= license.employeeLimit) {
|
|
2210
|
-
throw new PlanLimitError(
|
|
2211
|
-
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2212
|
-
);
|
|
2213
2531
|
}
|
|
2214
2532
|
}
|
|
2215
|
-
var
|
|
2216
|
-
|
|
2217
|
-
"src/lib/plan-limits.ts"() {
|
|
2533
|
+
var init_notifications = __esm({
|
|
2534
|
+
"src/lib/notifications.ts"() {
|
|
2218
2535
|
"use strict";
|
|
2219
2536
|
init_database();
|
|
2220
|
-
init_employees();
|
|
2221
|
-
init_license();
|
|
2222
|
-
init_config();
|
|
2223
|
-
PlanLimitError = class extends Error {
|
|
2224
|
-
constructor(message) {
|
|
2225
|
-
super(message);
|
|
2226
|
-
this.name = "PlanLimitError";
|
|
2227
|
-
}
|
|
2228
|
-
};
|
|
2229
|
-
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2230
2537
|
}
|
|
2231
2538
|
});
|
|
2232
2539
|
|
|
2233
|
-
// src/lib/
|
|
2234
|
-
import
|
|
2235
|
-
import { spawn } from "child_process";
|
|
2540
|
+
// src/lib/license.ts
|
|
2541
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
|
|
2236
2542
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2237
|
-
import
|
|
2238
|
-
import
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
2250
|
-
if (!line) continue;
|
|
2251
|
-
try {
|
|
2252
|
-
const response = JSON.parse(line);
|
|
2253
|
-
const entry = _pending.get(response.id);
|
|
2254
|
-
if (entry) {
|
|
2255
|
-
clearTimeout(entry.timer);
|
|
2256
|
-
_pending.delete(response.id);
|
|
2257
|
-
entry.resolve(response);
|
|
2258
|
-
}
|
|
2259
|
-
} catch {
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
function cleanupStaleFiles() {
|
|
2264
|
-
if (existsSync9(PID_PATH)) {
|
|
2265
|
-
try {
|
|
2266
|
-
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
2267
|
-
if (pid > 0) {
|
|
2268
|
-
try {
|
|
2269
|
-
process.kill(pid, 0);
|
|
2270
|
-
return;
|
|
2271
|
-
} catch {
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
} catch {
|
|
2275
|
-
}
|
|
2276
|
-
try {
|
|
2277
|
-
unlinkSync3(PID_PATH);
|
|
2278
|
-
} catch {
|
|
2279
|
-
}
|
|
2280
|
-
try {
|
|
2281
|
-
unlinkSync3(SOCKET_PATH);
|
|
2282
|
-
} catch {
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
function findPackageRoot() {
|
|
2287
|
-
let dir = path10.dirname(fileURLToPath(import.meta.url));
|
|
2288
|
-
const { root } = path10.parse(dir);
|
|
2289
|
-
while (dir !== root) {
|
|
2290
|
-
if (existsSync9(path10.join(dir, "package.json"))) return dir;
|
|
2291
|
-
dir = path10.dirname(dir);
|
|
2292
|
-
}
|
|
2293
|
-
return null;
|
|
2294
|
-
}
|
|
2295
|
-
function spawnDaemon() {
|
|
2296
|
-
const pkgRoot = findPackageRoot();
|
|
2297
|
-
if (!pkgRoot) {
|
|
2298
|
-
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
const daemonPath = path10.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2302
|
-
if (!existsSync9(daemonPath)) {
|
|
2303
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2304
|
-
`);
|
|
2305
|
-
return;
|
|
2306
|
-
}
|
|
2307
|
-
const resolvedPath = daemonPath;
|
|
2308
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2309
|
-
`);
|
|
2310
|
-
const logPath = path10.join(path10.dirname(SOCKET_PATH), "exed.log");
|
|
2311
|
-
let stderrFd = "ignore";
|
|
2543
|
+
import path9 from "path";
|
|
2544
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
2545
|
+
async function fetchRetry(url, init) {
|
|
2546
|
+
try {
|
|
2547
|
+
return await fetch(url, init);
|
|
2548
|
+
} catch {
|
|
2549
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
2550
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
function loadDeviceId() {
|
|
2554
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2312
2555
|
try {
|
|
2313
|
-
|
|
2556
|
+
if (existsSync8(deviceJsonPath)) {
|
|
2557
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
2558
|
+
if (data.deviceId) return data.deviceId;
|
|
2559
|
+
}
|
|
2314
2560
|
} catch {
|
|
2315
2561
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
...process.env,
|
|
2321
|
-
TMUX: void 0,
|
|
2322
|
-
// Daemon is global — must not inherit session scope
|
|
2323
|
-
TMUX_PANE: void 0,
|
|
2324
|
-
// Prevents resolveExeSession() from scoping to one session
|
|
2325
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2326
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2327
|
-
}
|
|
2328
|
-
});
|
|
2329
|
-
child.unref();
|
|
2330
|
-
if (typeof stderrFd === "number") {
|
|
2331
|
-
try {
|
|
2332
|
-
closeSync(stderrFd);
|
|
2333
|
-
} catch {
|
|
2562
|
+
try {
|
|
2563
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
2564
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
2565
|
+
if (id2) return id2;
|
|
2334
2566
|
}
|
|
2567
|
+
} catch {
|
|
2335
2568
|
}
|
|
2569
|
+
const id = randomUUID3();
|
|
2570
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2571
|
+
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
2572
|
+
return id;
|
|
2336
2573
|
}
|
|
2337
|
-
function
|
|
2574
|
+
function loadLicense() {
|
|
2338
2575
|
try {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
return true;
|
|
2576
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
2577
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
2342
2578
|
} catch {
|
|
2343
|
-
|
|
2344
|
-
const stat = statSync2(SPAWN_LOCK_PATH);
|
|
2345
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
2346
|
-
try {
|
|
2347
|
-
unlinkSync3(SPAWN_LOCK_PATH);
|
|
2348
|
-
} catch {
|
|
2349
|
-
}
|
|
2350
|
-
try {
|
|
2351
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
2352
|
-
closeSync(fd);
|
|
2353
|
-
return true;
|
|
2354
|
-
} catch {
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
} catch {
|
|
2358
|
-
}
|
|
2359
|
-
return false;
|
|
2579
|
+
return null;
|
|
2360
2580
|
}
|
|
2361
2581
|
}
|
|
2362
|
-
function
|
|
2582
|
+
function saveLicense(apiKey) {
|
|
2583
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2584
|
+
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2585
|
+
}
|
|
2586
|
+
async function verifyLicenseJwt(token) {
|
|
2363
2587
|
try {
|
|
2364
|
-
|
|
2588
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
2589
|
+
const { payload } = await jwtVerify(token, key, {
|
|
2590
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
2591
|
+
});
|
|
2592
|
+
const plan = payload.plan ?? "free";
|
|
2593
|
+
const email = payload.sub ?? "";
|
|
2594
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2595
|
+
return {
|
|
2596
|
+
valid: true,
|
|
2597
|
+
plan,
|
|
2598
|
+
email,
|
|
2599
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2600
|
+
deviceLimit: limits.devices,
|
|
2601
|
+
employeeLimit: limits.employees,
|
|
2602
|
+
memoryLimit: limits.memories
|
|
2603
|
+
};
|
|
2365
2604
|
} catch {
|
|
2605
|
+
return null;
|
|
2366
2606
|
}
|
|
2367
2607
|
}
|
|
2368
|
-
function
|
|
2369
|
-
|
|
2370
|
-
if (
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
resolve(false);
|
|
2378
|
-
}, 2e3);
|
|
2379
|
-
socket.on("connect", () => {
|
|
2380
|
-
clearTimeout(connectTimeout);
|
|
2381
|
-
_socket = socket;
|
|
2382
|
-
_connected = true;
|
|
2383
|
-
_buffer = "";
|
|
2384
|
-
socket.on("data", handleData);
|
|
2385
|
-
socket.on("close", () => {
|
|
2386
|
-
_connected = false;
|
|
2387
|
-
_socket = null;
|
|
2388
|
-
for (const [id, entry] of _pending) {
|
|
2389
|
-
clearTimeout(entry.timer);
|
|
2390
|
-
_pending.delete(id);
|
|
2391
|
-
entry.resolve({ error: "Connection closed" });
|
|
2392
|
-
}
|
|
2393
|
-
});
|
|
2394
|
-
socket.on("error", () => {
|
|
2395
|
-
_connected = false;
|
|
2396
|
-
_socket = null;
|
|
2397
|
-
});
|
|
2398
|
-
resolve(true);
|
|
2399
|
-
});
|
|
2400
|
-
socket.on("error", () => {
|
|
2401
|
-
clearTimeout(connectTimeout);
|
|
2402
|
-
resolve(false);
|
|
2403
|
-
});
|
|
2404
|
-
});
|
|
2608
|
+
async function getCachedLicense() {
|
|
2609
|
+
try {
|
|
2610
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
2611
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2612
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2613
|
+
return await verifyLicenseJwt(raw.token);
|
|
2614
|
+
} catch {
|
|
2615
|
+
return null;
|
|
2616
|
+
}
|
|
2405
2617
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
} finally {
|
|
2414
|
-
releaseSpawnLock();
|
|
2415
|
-
}
|
|
2618
|
+
function readCachedToken() {
|
|
2619
|
+
try {
|
|
2620
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
2621
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2622
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
2623
|
+
} catch {
|
|
2624
|
+
return null;
|
|
2416
2625
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
if (
|
|
2422
|
-
|
|
2626
|
+
}
|
|
2627
|
+
function getRawCachedPlan() {
|
|
2628
|
+
try {
|
|
2629
|
+
const token = readCachedToken();
|
|
2630
|
+
if (!token) return null;
|
|
2631
|
+
const parts = token.split(".");
|
|
2632
|
+
if (parts.length !== 3) return null;
|
|
2633
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2634
|
+
const plan = payload.plan ?? "free";
|
|
2635
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2636
|
+
process.stderr.write(
|
|
2637
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
2638
|
+
`
|
|
2639
|
+
);
|
|
2640
|
+
return {
|
|
2641
|
+
valid: true,
|
|
2642
|
+
plan,
|
|
2643
|
+
email: payload.sub ?? "",
|
|
2644
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2645
|
+
deviceLimit: limits.devices,
|
|
2646
|
+
employeeLimit: limits.employees,
|
|
2647
|
+
memoryLimit: limits.memories
|
|
2648
|
+
};
|
|
2649
|
+
} catch {
|
|
2650
|
+
return null;
|
|
2423
2651
|
}
|
|
2424
|
-
return false;
|
|
2425
2652
|
}
|
|
2426
|
-
function
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
}
|
|
2432
|
-
const id = randomUUID3();
|
|
2433
|
-
const timer = setTimeout(() => {
|
|
2434
|
-
_pending.delete(id);
|
|
2435
|
-
resolve({ error: "Request timeout" });
|
|
2436
|
-
}, REQUEST_TIMEOUT_MS);
|
|
2437
|
-
_pending.set(id, { resolve, timer });
|
|
2438
|
-
try {
|
|
2439
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
2440
|
-
} catch {
|
|
2441
|
-
clearTimeout(timer);
|
|
2442
|
-
_pending.delete(id);
|
|
2443
|
-
resolve({ error: "Write failed" });
|
|
2444
|
-
}
|
|
2445
|
-
});
|
|
2653
|
+
function cacheResponse(token) {
|
|
2654
|
+
try {
|
|
2655
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2656
|
+
} catch {
|
|
2657
|
+
}
|
|
2446
2658
|
}
|
|
2447
|
-
async function
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
const
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
_pending.set(id, {
|
|
2456
|
-
resolve: (data) => {
|
|
2457
|
-
if (data.health) {
|
|
2458
|
-
resolve(data.health);
|
|
2459
|
-
} else {
|
|
2460
|
-
resolve(null);
|
|
2461
|
-
}
|
|
2462
|
-
},
|
|
2463
|
-
timer
|
|
2659
|
+
async function validateLicense(apiKey, deviceId) {
|
|
2660
|
+
const did = deviceId ?? loadDeviceId();
|
|
2661
|
+
try {
|
|
2662
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
2663
|
+
method: "POST",
|
|
2664
|
+
headers: { "Content-Type": "application/json" },
|
|
2665
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
2666
|
+
signal: AbortSignal.timeout(1e4)
|
|
2464
2667
|
});
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2668
|
+
if (res.ok) {
|
|
2669
|
+
const data = await res.json();
|
|
2670
|
+
if (data.error === "device_limit_exceeded") {
|
|
2671
|
+
const cached2 = await getCachedLicense();
|
|
2672
|
+
if (cached2) return cached2;
|
|
2673
|
+
const raw2 = getRawCachedPlan();
|
|
2674
|
+
if (raw2) return { ...raw2, valid: false };
|
|
2675
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2676
|
+
}
|
|
2677
|
+
if (data.token) {
|
|
2678
|
+
cacheResponse(data.token);
|
|
2679
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
2680
|
+
if (verified) return verified;
|
|
2681
|
+
}
|
|
2682
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
2683
|
+
return {
|
|
2684
|
+
valid: data.valid,
|
|
2685
|
+
plan: data.plan,
|
|
2686
|
+
email: data.email,
|
|
2687
|
+
expiresAt: data.expiresAt,
|
|
2688
|
+
deviceLimit: limits.devices,
|
|
2689
|
+
employeeLimit: limits.employees,
|
|
2690
|
+
memoryLimit: limits.memories
|
|
2691
|
+
};
|
|
2471
2692
|
}
|
|
2472
|
-
|
|
2693
|
+
const cached = await getCachedLicense();
|
|
2694
|
+
if (cached) return cached;
|
|
2695
|
+
const raw = getRawCachedPlan();
|
|
2696
|
+
if (raw) return raw;
|
|
2697
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
2698
|
+
} catch {
|
|
2699
|
+
const cached = await getCachedLicense();
|
|
2700
|
+
if (cached) return cached;
|
|
2701
|
+
const rawFallback = getRawCachedPlan();
|
|
2702
|
+
if (rawFallback) return rawFallback;
|
|
2703
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
2704
|
+
}
|
|
2473
2705
|
}
|
|
2474
|
-
function
|
|
2475
|
-
|
|
2476
|
-
|
|
2706
|
+
function getCacheAgeMs() {
|
|
2707
|
+
try {
|
|
2708
|
+
const { statSync: statSync3 } = __require("fs");
|
|
2709
|
+
const s = statSync3(CACHE_PATH);
|
|
2710
|
+
return Date.now() - s.mtimeMs;
|
|
2711
|
+
} catch {
|
|
2712
|
+
return Infinity;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
async function checkLicense() {
|
|
2716
|
+
let key = loadLicense();
|
|
2717
|
+
if (!key) {
|
|
2477
2718
|
try {
|
|
2478
|
-
const
|
|
2479
|
-
if (
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2719
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
2720
|
+
if (existsSync8(configPath)) {
|
|
2721
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2722
|
+
const cloud = raw.cloud;
|
|
2723
|
+
if (cloud?.apiKey) {
|
|
2724
|
+
key = cloud.apiKey;
|
|
2725
|
+
saveLicense(key);
|
|
2483
2726
|
}
|
|
2484
2727
|
}
|
|
2485
2728
|
} catch {
|
|
2486
2729
|
}
|
|
2487
2730
|
}
|
|
2488
|
-
if (
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2731
|
+
if (!key) return FREE_LICENSE;
|
|
2732
|
+
const cached = await getCachedLicense();
|
|
2733
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
2734
|
+
const deviceId = loadDeviceId();
|
|
2735
|
+
return validateLicense(key, deviceId);
|
|
2736
|
+
}
|
|
2737
|
+
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;
|
|
2738
|
+
var init_license = __esm({
|
|
2739
|
+
"src/lib/license.ts"() {
|
|
2740
|
+
"use strict";
|
|
2741
|
+
init_config();
|
|
2742
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
2743
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2744
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2745
|
+
API_BASE = "https://askexe.com/cloud";
|
|
2746
|
+
RETRY_DELAY_MS = 500;
|
|
2747
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
2748
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
2749
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
2750
|
+
-----END PUBLIC KEY-----`;
|
|
2751
|
+
LICENSE_JWT_ALG = "ES256";
|
|
2752
|
+
PLAN_LIMITS = {
|
|
2753
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2754
|
+
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
2755
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
2756
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
2757
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
2758
|
+
};
|
|
2759
|
+
FREE_LICENSE = {
|
|
2760
|
+
valid: true,
|
|
2761
|
+
plan: "free",
|
|
2762
|
+
email: "",
|
|
2763
|
+
expiresAt: null,
|
|
2764
|
+
deviceLimit: 1,
|
|
2765
|
+
employeeLimit: 1,
|
|
2766
|
+
memoryLimit: 5e3
|
|
2767
|
+
};
|
|
2768
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
2497
2769
|
}
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
// src/lib/plan-limits.ts
|
|
2773
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
2774
|
+
import path10 from "path";
|
|
2775
|
+
function getLicenseSync() {
|
|
2498
2776
|
try {
|
|
2499
|
-
|
|
2777
|
+
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
2778
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
2779
|
+
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2780
|
+
const parts = raw.token.split(".");
|
|
2781
|
+
if (parts.length !== 3) return freeLicense();
|
|
2782
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
2783
|
+
const plan = payload.plan ?? "free";
|
|
2784
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
2785
|
+
return {
|
|
2786
|
+
valid: true,
|
|
2787
|
+
plan,
|
|
2788
|
+
email: payload.sub ?? "",
|
|
2789
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
2790
|
+
deviceLimit: limits.devices,
|
|
2791
|
+
employeeLimit: limits.employees,
|
|
2792
|
+
memoryLimit: limits.memories
|
|
2793
|
+
};
|
|
2500
2794
|
} catch {
|
|
2795
|
+
return freeLicense();
|
|
2501
2796
|
}
|
|
2502
|
-
spawnDaemon();
|
|
2503
2797
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
if (await connectToSocket()) break;
|
|
2534
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2535
|
-
}
|
|
2536
|
-
if (!_connected) return null;
|
|
2537
|
-
const retry = await sendRequest([text], priority);
|
|
2538
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
2539
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
2540
|
-
`);
|
|
2798
|
+
function freeLicense() {
|
|
2799
|
+
const limits = PLAN_LIMITS.free;
|
|
2800
|
+
return {
|
|
2801
|
+
valid: true,
|
|
2802
|
+
plan: "free",
|
|
2803
|
+
email: "",
|
|
2804
|
+
expiresAt: null,
|
|
2805
|
+
deviceLimit: limits.devices,
|
|
2806
|
+
employeeLimit: limits.employees,
|
|
2807
|
+
memoryLimit: limits.memories
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
async function countActiveMemories() {
|
|
2811
|
+
if (!isInitialized()) return 0;
|
|
2812
|
+
const client = getClient();
|
|
2813
|
+
const result = await client.execute(
|
|
2814
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
2815
|
+
);
|
|
2816
|
+
const row = result.rows[0];
|
|
2817
|
+
return Number(row?.cnt ?? 0);
|
|
2818
|
+
}
|
|
2819
|
+
async function assertMemoryLimit() {
|
|
2820
|
+
const license = await checkLicense();
|
|
2821
|
+
if (license.memoryLimit < 0) return;
|
|
2822
|
+
const count = await countActiveMemories();
|
|
2823
|
+
if (count >= license.memoryLimit) {
|
|
2824
|
+
throw new PlanLimitError(
|
|
2825
|
+
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
2826
|
+
);
|
|
2541
2827
|
}
|
|
2542
|
-
return null;
|
|
2543
2828
|
}
|
|
2544
|
-
function
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2829
|
+
function assertEmployeeLimitSync(rosterPath) {
|
|
2830
|
+
const license = getLicenseSync();
|
|
2831
|
+
if (license.employeeLimit < 0) return;
|
|
2832
|
+
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2833
|
+
let count = 0;
|
|
2834
|
+
try {
|
|
2835
|
+
if (existsSync9(filePath)) {
|
|
2836
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
2837
|
+
const employees = JSON.parse(raw);
|
|
2838
|
+
count = Array.isArray(employees) ? employees.length : 0;
|
|
2839
|
+
}
|
|
2840
|
+
} catch {
|
|
2841
|
+
throw new PlanLimitError(
|
|
2842
|
+
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
2843
|
+
);
|
|
2548
2844
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
_pending.delete(id);
|
|
2554
|
-
entry.resolve({ error: "Client disconnected" });
|
|
2845
|
+
if (count >= license.employeeLimit) {
|
|
2846
|
+
throw new PlanLimitError(
|
|
2847
|
+
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
2848
|
+
);
|
|
2555
2849
|
}
|
|
2556
2850
|
}
|
|
2557
|
-
var
|
|
2558
|
-
var
|
|
2559
|
-
"src/lib/
|
|
2851
|
+
var PlanLimitError, CACHE_PATH2;
|
|
2852
|
+
var init_plan_limits = __esm({
|
|
2853
|
+
"src/lib/plan-limits.ts"() {
|
|
2560
2854
|
"use strict";
|
|
2855
|
+
init_database();
|
|
2856
|
+
init_employees();
|
|
2857
|
+
init_license();
|
|
2561
2858
|
init_config();
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
_connected = false;
|
|
2570
|
-
_buffer = "";
|
|
2571
|
-
_requestCount = 0;
|
|
2572
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
2573
|
-
_pending = /* @__PURE__ */ new Map();
|
|
2574
|
-
MAX_BUFFER = 1e7;
|
|
2859
|
+
PlanLimitError = class extends Error {
|
|
2860
|
+
constructor(message) {
|
|
2861
|
+
super(message);
|
|
2862
|
+
this.name = "PlanLimitError";
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2575
2866
|
}
|
|
2576
2867
|
});
|
|
2577
2868
|
|
|
@@ -2646,7 +2937,7 @@ var init_embedder = __esm({
|
|
|
2646
2937
|
// src/lib/session-registry.ts
|
|
2647
2938
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync10 } from "fs";
|
|
2648
2939
|
import path11 from "path";
|
|
2649
|
-
import
|
|
2940
|
+
import os6 from "os";
|
|
2650
2941
|
function registerSession(entry) {
|
|
2651
2942
|
const dir = path11.dirname(REGISTRY_PATH);
|
|
2652
2943
|
if (!existsSync10(dir)) {
|
|
@@ -2673,7 +2964,7 @@ var REGISTRY_PATH;
|
|
|
2673
2964
|
var init_session_registry = __esm({
|
|
2674
2965
|
"src/lib/session-registry.ts"() {
|
|
2675
2966
|
"use strict";
|
|
2676
|
-
REGISTRY_PATH = path11.join(
|
|
2967
|
+
REGISTRY_PATH = path11.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
2677
2968
|
}
|
|
2678
2969
|
});
|
|
2679
2970
|
|
|
@@ -2872,7 +3163,7 @@ var init_provider_table = __esm({
|
|
|
2872
3163
|
// src/lib/intercom-queue.ts
|
|
2873
3164
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync11, mkdirSync as mkdirSync4 } from "fs";
|
|
2874
3165
|
import path12 from "path";
|
|
2875
|
-
import
|
|
3166
|
+
import os7 from "os";
|
|
2876
3167
|
function ensureDir() {
|
|
2877
3168
|
const dir = path12.dirname(QUEUE_PATH);
|
|
2878
3169
|
if (!existsSync11(dir)) mkdirSync4(dir, { recursive: true });
|
|
@@ -2912,9 +3203,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
2912
3203
|
var init_intercom_queue = __esm({
|
|
2913
3204
|
"src/lib/intercom-queue.ts"() {
|
|
2914
3205
|
"use strict";
|
|
2915
|
-
QUEUE_PATH = path12.join(
|
|
3206
|
+
QUEUE_PATH = path12.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
2916
3207
|
TTL_MS = 60 * 60 * 1e3;
|
|
2917
|
-
INTERCOM_LOG = path12.join(
|
|
3208
|
+
INTERCOM_LOG = path12.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2918
3209
|
}
|
|
2919
3210
|
});
|
|
2920
3211
|
|
|
@@ -3264,7 +3555,7 @@ __export(tmux_routing_exports, {
|
|
|
3264
3555
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3265
3556
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync12, appendFileSync } from "fs";
|
|
3266
3557
|
import path13 from "path";
|
|
3267
|
-
import
|
|
3558
|
+
import os8 from "os";
|
|
3268
3559
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3269
3560
|
import { unlinkSync as unlinkSync4 } from "fs";
|
|
3270
3561
|
function spawnLockPath(sessionName) {
|
|
@@ -3588,7 +3879,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3588
3879
|
return true;
|
|
3589
3880
|
}
|
|
3590
3881
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3591
|
-
if (
|
|
3882
|
+
if (isCoordinatorName(employeeName)) {
|
|
3592
3883
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3593
3884
|
}
|
|
3594
3885
|
try {
|
|
@@ -3660,7 +3951,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3660
3951
|
const transport = getTransport();
|
|
3661
3952
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3662
3953
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3663
|
-
const logDir = path13.join(
|
|
3954
|
+
const logDir = path13.join(os8.homedir(), ".exe-os", "session-logs");
|
|
3664
3955
|
const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3665
3956
|
if (!existsSync12(logDir)) {
|
|
3666
3957
|
mkdirSync5(logDir, { recursive: true });
|
|
@@ -3676,7 +3967,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3676
3967
|
} catch {
|
|
3677
3968
|
}
|
|
3678
3969
|
try {
|
|
3679
|
-
const claudeJsonPath = path13.join(
|
|
3970
|
+
const claudeJsonPath = path13.join(os8.homedir(), ".claude.json");
|
|
3680
3971
|
let claudeJson = {};
|
|
3681
3972
|
try {
|
|
3682
3973
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -3691,7 +3982,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3691
3982
|
} catch {
|
|
3692
3983
|
}
|
|
3693
3984
|
try {
|
|
3694
|
-
const settingsDir = path13.join(
|
|
3985
|
+
const settingsDir = path13.join(os8.homedir(), ".claude", "projects");
|
|
3695
3986
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3696
3987
|
const projSettingsDir = path13.join(settingsDir, normalizedKey);
|
|
3697
3988
|
const settingsPath = path13.join(projSettingsDir, "settings.json");
|
|
@@ -3739,7 +4030,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3739
4030
|
let legacyFallbackWarned = false;
|
|
3740
4031
|
if (!useExeAgent && !useBinSymlink) {
|
|
3741
4032
|
const identityPath = path13.join(
|
|
3742
|
-
|
|
4033
|
+
os8.homedir(),
|
|
3743
4034
|
".exe-os",
|
|
3744
4035
|
"identity",
|
|
3745
4036
|
`${employeeName}.md`
|
|
@@ -3769,7 +4060,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3769
4060
|
}
|
|
3770
4061
|
let sessionContextFlag = "";
|
|
3771
4062
|
try {
|
|
3772
|
-
const ctxDir = path13.join(
|
|
4063
|
+
const ctxDir = path13.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3773
4064
|
mkdirSync5(ctxDir, { recursive: true });
|
|
3774
4065
|
const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3775
4066
|
const ctxContent = [
|
|
@@ -3881,13 +4172,13 @@ var init_tmux_routing = __esm({
|
|
|
3881
4172
|
init_intercom_queue();
|
|
3882
4173
|
init_plan_limits();
|
|
3883
4174
|
init_employees();
|
|
3884
|
-
SPAWN_LOCK_DIR = path13.join(
|
|
3885
|
-
SESSION_CACHE = path13.join(
|
|
4175
|
+
SPAWN_LOCK_DIR = path13.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
4176
|
+
SESSION_CACHE = path13.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3886
4177
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3887
4178
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3888
4179
|
VERIFY_PANE_LINES = 200;
|
|
3889
4180
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3890
|
-
INTERCOM_LOG2 = path13.join(
|
|
4181
|
+
INTERCOM_LOG2 = path13.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
3891
4182
|
DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3892
4183
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3893
4184
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
@@ -3921,6 +4212,7 @@ var init_task_scope = __esm({
|
|
|
3921
4212
|
// src/lib/tasks-crud.ts
|
|
3922
4213
|
import crypto4 from "crypto";
|
|
3923
4214
|
import path14 from "path";
|
|
4215
|
+
import os9 from "os";
|
|
3924
4216
|
import { execSync as execSync7 } from "child_process";
|
|
3925
4217
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3926
4218
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
@@ -3964,6 +4256,35 @@ function extractParentFromContext(contextBody) {
|
|
|
3964
4256
|
function slugify(title) {
|
|
3965
4257
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3966
4258
|
}
|
|
4259
|
+
function buildKeywordIndex() {
|
|
4260
|
+
const idx = /* @__PURE__ */ new Map();
|
|
4261
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
4262
|
+
for (const kw of keywords) {
|
|
4263
|
+
const existing = idx.get(kw) ?? [];
|
|
4264
|
+
existing.push(role);
|
|
4265
|
+
idx.set(kw, existing);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
return idx;
|
|
4269
|
+
}
|
|
4270
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
4271
|
+
const employees = loadEmployeesSync();
|
|
4272
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
4273
|
+
if (!employee) return void 0;
|
|
4274
|
+
const assigneeRole = employee.role;
|
|
4275
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
4276
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
4277
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
4278
|
+
if (text.includes(keyword)) {
|
|
4279
|
+
for (const role of roles) matchedRoles.add(role);
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
if (matchedRoles.size === 0) return void 0;
|
|
4283
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
4284
|
+
if (assigneeRole === "COO") return void 0;
|
|
4285
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
4286
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
4287
|
+
}
|
|
3967
4288
|
async function resolveTask(client, identifier, scopeSession) {
|
|
3968
4289
|
const scope = sessionScopeFilter(scopeSession);
|
|
3969
4290
|
let result = await client.execute({
|
|
@@ -4013,7 +4334,14 @@ async function createTaskCore(input2) {
|
|
|
4013
4334
|
const id = crypto4.randomUUID();
|
|
4014
4335
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4015
4336
|
const slug = slugify(input2.title);
|
|
4016
|
-
|
|
4337
|
+
let earlySessionScope = null;
|
|
4338
|
+
try {
|
|
4339
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4340
|
+
earlySessionScope = resolveExeSession2();
|
|
4341
|
+
} catch {
|
|
4342
|
+
}
|
|
4343
|
+
const scope = earlySessionScope ?? "default";
|
|
4344
|
+
const taskFile = input2.taskFile ?? `tasks/${scope}/${input2.assignedTo}/${slug}.md`;
|
|
4017
4345
|
let blockedById = null;
|
|
4018
4346
|
const initialStatus = input2.blockedBy ? "blocked" : "open";
|
|
4019
4347
|
if (input2.blockedBy) {
|
|
@@ -4053,6 +4381,13 @@ async function createTaskCore(input2) {
|
|
|
4053
4381
|
if (dupCheck.rows.length > 0) {
|
|
4054
4382
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
4055
4383
|
}
|
|
4384
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
4385
|
+
const laneWarning = checkLaneAffinity(input2.title, input2.context, input2.assignedTo);
|
|
4386
|
+
if (laneWarning) {
|
|
4387
|
+
warning = warning ? `${warning}
|
|
4388
|
+
${laneWarning}` : laneWarning;
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4056
4391
|
if (input2.baseDir) {
|
|
4057
4392
|
try {
|
|
4058
4393
|
await mkdir4(path14.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
@@ -4063,12 +4398,7 @@ async function createTaskCore(input2) {
|
|
|
4063
4398
|
}
|
|
4064
4399
|
}
|
|
4065
4400
|
const complexity = input2.complexity ?? "standard";
|
|
4066
|
-
|
|
4067
|
-
try {
|
|
4068
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4069
|
-
sessionScope = resolveExeSession2();
|
|
4070
|
-
} catch {
|
|
4071
|
-
}
|
|
4401
|
+
const sessionScope = earlySessionScope;
|
|
4072
4402
|
await client.execute({
|
|
4073
4403
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
4074
4404
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -4095,6 +4425,39 @@ async function createTaskCore(input2) {
|
|
|
4095
4425
|
now
|
|
4096
4426
|
]
|
|
4097
4427
|
});
|
|
4428
|
+
if (input2.baseDir) {
|
|
4429
|
+
try {
|
|
4430
|
+
const EXE_OS_DIR = path14.join(os9.homedir(), ".exe-os");
|
|
4431
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
4432
|
+
const mdDir = path14.dirname(mdPath);
|
|
4433
|
+
if (!existsSync13(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4434
|
+
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
4435
|
+
const mdContent = `# ${input2.title}
|
|
4436
|
+
|
|
4437
|
+
**ID:** ${id}
|
|
4438
|
+
**Status:** ${initialStatus}
|
|
4439
|
+
**Priority:** ${input2.priority}
|
|
4440
|
+
**Assigned by:** ${input2.assignedBy}
|
|
4441
|
+
**Assigned to:** ${input2.assignedTo}
|
|
4442
|
+
**Project:** ${input2.projectName}
|
|
4443
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
4444
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
4445
|
+
**Reviewer:** ${reviewer}
|
|
4446
|
+
|
|
4447
|
+
## Context
|
|
4448
|
+
|
|
4449
|
+
${input2.context}
|
|
4450
|
+
|
|
4451
|
+
## MANDATORY: When done
|
|
4452
|
+
|
|
4453
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4454
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4455
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4456
|
+
`;
|
|
4457
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4458
|
+
} catch {
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4098
4461
|
return {
|
|
4099
4462
|
id,
|
|
4100
4463
|
title: input2.title,
|
|
@@ -4287,7 +4650,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4287
4650
|
return { row, taskFile, now, taskId };
|
|
4288
4651
|
}
|
|
4289
4652
|
}
|
|
4290
|
-
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId
|
|
4653
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || isCoordinatorName(input2.callerAgentId))) {
|
|
4291
4654
|
process.stderr.write(
|
|
4292
4655
|
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
4293
4656
|
`
|
|
@@ -4399,12 +4762,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
4399
4762
|
} catch {
|
|
4400
4763
|
}
|
|
4401
4764
|
}
|
|
4402
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4765
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4403
4766
|
var init_tasks_crud = __esm({
|
|
4404
4767
|
"src/lib/tasks-crud.ts"() {
|
|
4405
4768
|
"use strict";
|
|
4406
4769
|
init_database();
|
|
4407
4770
|
init_task_scope();
|
|
4771
|
+
init_employees();
|
|
4772
|
+
LANE_KEYWORDS = {
|
|
4773
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
4774
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
4775
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
4776
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
4777
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
4778
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
4779
|
+
};
|
|
4780
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
4408
4781
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
4409
4782
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
4410
4783
|
}
|
|
@@ -4434,7 +4807,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
4434
4807
|
const result2 = await client.execute({
|
|
4435
4808
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4436
4809
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
4437
|
-
AND
|
|
4810
|
+
AND session_scope = ?`,
|
|
4438
4811
|
args: [sinceIso, sessionScope]
|
|
4439
4812
|
});
|
|
4440
4813
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -4452,7 +4825,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
4452
4825
|
const result2 = await client.execute({
|
|
4453
4826
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
4454
4827
|
WHERE status = 'needs_review'
|
|
4455
|
-
AND
|
|
4828
|
+
AND session_scope = ?
|
|
4456
4829
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
4457
4830
|
args: [sessionScope, limit]
|
|
4458
4831
|
});
|
|
@@ -4573,14 +4946,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4573
4946
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
4574
4947
|
const agent = parts[1];
|
|
4575
4948
|
const slug = parts.slice(2).join("-");
|
|
4576
|
-
const
|
|
4949
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
4577
4950
|
const result = await client.execute({
|
|
4578
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
4579
|
-
args: [now,
|
|
4951
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
4952
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
4580
4953
|
});
|
|
4581
4954
|
if (result.rowsAffected > 0) {
|
|
4582
4955
|
process.stderr.write(
|
|
4583
|
-
`[review-cleanup] Cascaded original task to done
|
|
4956
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
4584
4957
|
`
|
|
4585
4958
|
);
|
|
4586
4959
|
}
|
|
@@ -4720,7 +5093,7 @@ function findSessionForProject(projectName) {
|
|
|
4720
5093
|
const sessions = listSessions();
|
|
4721
5094
|
for (const s of sessions) {
|
|
4722
5095
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4723
|
-
if (proj === projectName &&
|
|
5096
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4724
5097
|
}
|
|
4725
5098
|
return null;
|
|
4726
5099
|
}
|
|
@@ -4766,7 +5139,7 @@ var init_session_scope = __esm({
|
|
|
4766
5139
|
|
|
4767
5140
|
// src/lib/tasks-notify.ts
|
|
4768
5141
|
async function dispatchTaskToEmployee(input2) {
|
|
4769
|
-
if (
|
|
5142
|
+
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
4770
5143
|
let crossProject = false;
|
|
4771
5144
|
if (input2.projectName) {
|
|
4772
5145
|
try {
|
|
@@ -5245,7 +5618,7 @@ async function updateTask(input2) {
|
|
|
5245
5618
|
}
|
|
5246
5619
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
5247
5620
|
if (isTerminal) {
|
|
5248
|
-
const isCoordinator =
|
|
5621
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5249
5622
|
if (!isCoordinator) {
|
|
5250
5623
|
notifyTaskDone();
|
|
5251
5624
|
}
|
|
@@ -5270,7 +5643,7 @@ async function updateTask(input2) {
|
|
|
5270
5643
|
}
|
|
5271
5644
|
}
|
|
5272
5645
|
}
|
|
5273
|
-
if (input2.status === "done" &&
|
|
5646
|
+
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5274
5647
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5275
5648
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5276
5649
|
taskId,
|
|
@@ -5286,7 +5659,7 @@ async function updateTask(input2) {
|
|
|
5286
5659
|
});
|
|
5287
5660
|
}
|
|
5288
5661
|
let nextTask;
|
|
5289
|
-
if (isTerminal &&
|
|
5662
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
5290
5663
|
try {
|
|
5291
5664
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
5292
5665
|
} catch {
|
|
@@ -5556,6 +5929,7 @@ function detectError(data) {
|
|
|
5556
5929
|
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
5557
5930
|
import { execSync } from "child_process";
|
|
5558
5931
|
import path from "path";
|
|
5932
|
+
import os from "os";
|
|
5559
5933
|
var STATUS_RE = /^\*\*Status:\*\*\s*(\w+)/m;
|
|
5560
5934
|
var TITLE_RE = /^# (.+)/m;
|
|
5561
5935
|
|
|
@@ -5565,19 +5939,20 @@ init_project_name();
|
|
|
5565
5939
|
// src/lib/store.ts
|
|
5566
5940
|
init_memory();
|
|
5567
5941
|
init_database();
|
|
5942
|
+
import { createHash } from "crypto";
|
|
5568
5943
|
|
|
5569
5944
|
// src/lib/keychain.ts
|
|
5570
5945
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
5571
|
-
import { existsSync as
|
|
5572
|
-
import
|
|
5573
|
-
import
|
|
5946
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5947
|
+
import path6 from "path";
|
|
5948
|
+
import os4 from "os";
|
|
5574
5949
|
var SERVICE = "exe-mem";
|
|
5575
5950
|
var ACCOUNT = "master-key";
|
|
5576
5951
|
function getKeyDir() {
|
|
5577
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5952
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os4.homedir(), ".exe-os");
|
|
5578
5953
|
}
|
|
5579
5954
|
function getKeyPath() {
|
|
5580
|
-
return
|
|
5955
|
+
return path6.join(getKeyDir(), "master.key");
|
|
5581
5956
|
}
|
|
5582
5957
|
async function tryKeytar() {
|
|
5583
5958
|
try {
|
|
@@ -5598,13 +5973,21 @@ async function getMasterKey() {
|
|
|
5598
5973
|
}
|
|
5599
5974
|
}
|
|
5600
5975
|
const keyPath = getKeyPath();
|
|
5601
|
-
if (!
|
|
5976
|
+
if (!existsSync5(keyPath)) {
|
|
5977
|
+
process.stderr.write(
|
|
5978
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5979
|
+
`
|
|
5980
|
+
);
|
|
5602
5981
|
return null;
|
|
5603
5982
|
}
|
|
5604
5983
|
try {
|
|
5605
5984
|
const content = await readFile3(keyPath, "utf-8");
|
|
5606
5985
|
return Buffer.from(content.trim(), "base64");
|
|
5607
|
-
} catch {
|
|
5986
|
+
} catch (err) {
|
|
5987
|
+
process.stderr.write(
|
|
5988
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5989
|
+
`
|
|
5990
|
+
);
|
|
5608
5991
|
return null;
|
|
5609
5992
|
}
|
|
5610
5993
|
}
|
|
@@ -5693,12 +6076,52 @@ function classifyTier(record) {
|
|
|
5693
6076
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
5694
6077
|
return 3;
|
|
5695
6078
|
}
|
|
6079
|
+
function inferFilePaths(record) {
|
|
6080
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
6081
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
6082
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
6083
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
6084
|
+
}
|
|
6085
|
+
function inferCommitHash(record) {
|
|
6086
|
+
if (record.tool_name !== "Bash") return null;
|
|
6087
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
6088
|
+
return match ? match[1] : null;
|
|
6089
|
+
}
|
|
6090
|
+
function inferLanguageType(record) {
|
|
6091
|
+
const text = record.raw_text;
|
|
6092
|
+
if (!text || text.length < 10) return null;
|
|
6093
|
+
const trimmed = text.trimStart();
|
|
6094
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
6095
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
6096
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
6097
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
6098
|
+
return "mixed";
|
|
6099
|
+
}
|
|
6100
|
+
function inferDomain(record) {
|
|
6101
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
6102
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
6103
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
6104
|
+
return null;
|
|
6105
|
+
}
|
|
5696
6106
|
async function writeMemory(record) {
|
|
5697
6107
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
5698
6108
|
throw new Error(
|
|
5699
6109
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
5700
6110
|
);
|
|
5701
6111
|
}
|
|
6112
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
6113
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
try {
|
|
6117
|
+
const client = getClient();
|
|
6118
|
+
const existing = await client.execute({
|
|
6119
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
6120
|
+
args: [contentHash, record.agent_id]
|
|
6121
|
+
});
|
|
6122
|
+
if (existing.rows.length > 0) return;
|
|
6123
|
+
} catch {
|
|
6124
|
+
}
|
|
5702
6125
|
const dbRow = {
|
|
5703
6126
|
id: record.id,
|
|
5704
6127
|
agent_id: record.agent_id,
|
|
@@ -5728,7 +6151,23 @@ async function writeMemory(record) {
|
|
|
5728
6151
|
supersedes_id: record.supersedes_id ?? null,
|
|
5729
6152
|
draft: record.draft ? 1 : 0,
|
|
5730
6153
|
memory_type: record.memory_type ?? "raw",
|
|
5731
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
6154
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
6155
|
+
content_hash: contentHash,
|
|
6156
|
+
intent: record.intent ?? null,
|
|
6157
|
+
outcome: record.outcome ?? null,
|
|
6158
|
+
domain: record.domain ?? inferDomain(record),
|
|
6159
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
6160
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
6161
|
+
chain_position: record.chain_position ?? null,
|
|
6162
|
+
review_status: record.review_status ?? null,
|
|
6163
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
6164
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
6165
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
6166
|
+
duration_ms: record.duration_ms ?? null,
|
|
6167
|
+
token_cost: record.token_cost ?? null,
|
|
6168
|
+
audience: record.audience ?? null,
|
|
6169
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
6170
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
5732
6171
|
};
|
|
5733
6172
|
_pendingRecords.push(dbRow);
|
|
5734
6173
|
orgBus.emit({
|
|
@@ -5786,80 +6225,85 @@ async function flushBatch() {
|
|
|
5786
6225
|
const draft = row.draft ? 1 : 0;
|
|
5787
6226
|
const memoryType = row.memory_type ?? "raw";
|
|
5788
6227
|
const trajectory = row.trajectory ?? null;
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
6228
|
+
const contentHash = row.content_hash ?? null;
|
|
6229
|
+
const intent = row.intent ?? null;
|
|
6230
|
+
const outcome = row.outcome ?? null;
|
|
6231
|
+
const domain = row.domain ?? null;
|
|
6232
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
6233
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
6234
|
+
const chainPosition = row.chain_position ?? null;
|
|
6235
|
+
const reviewStatus = row.review_status ?? null;
|
|
6236
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
6237
|
+
const filePaths = row.file_paths ?? null;
|
|
6238
|
+
const commitHash = row.commit_hash ?? null;
|
|
6239
|
+
const durationMs = row.duration_ms ?? null;
|
|
6240
|
+
const tokenCost = row.token_cost ?? null;
|
|
6241
|
+
const audience = row.audience ?? null;
|
|
6242
|
+
const languageType = row.language_type ?? null;
|
|
6243
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
6244
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
5799
6245
|
tool_name, project_name,
|
|
5800
6246
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5801
6247
|
confidence, last_accessed,
|
|
5802
6248
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5803
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
trajectory
|
|
5862
|
-
]
|
|
6249
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
6250
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
6251
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
6252
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
6253
|
+
const metaArgs = [
|
|
6254
|
+
intent,
|
|
6255
|
+
outcome,
|
|
6256
|
+
domain,
|
|
6257
|
+
referencedEntities,
|
|
6258
|
+
retrievalCount,
|
|
6259
|
+
chainPosition,
|
|
6260
|
+
reviewStatus,
|
|
6261
|
+
contextWindowPct,
|
|
6262
|
+
filePaths,
|
|
6263
|
+
commitHash,
|
|
6264
|
+
durationMs,
|
|
6265
|
+
tokenCost,
|
|
6266
|
+
audience,
|
|
6267
|
+
languageType,
|
|
6268
|
+
parentMemoryId
|
|
6269
|
+
];
|
|
6270
|
+
const baseArgs = [
|
|
6271
|
+
row.id,
|
|
6272
|
+
row.agent_id,
|
|
6273
|
+
row.agent_role,
|
|
6274
|
+
row.session_id,
|
|
6275
|
+
row.timestamp,
|
|
6276
|
+
row.tool_name,
|
|
6277
|
+
row.project_name,
|
|
6278
|
+
row.has_error,
|
|
6279
|
+
row.raw_text
|
|
6280
|
+
];
|
|
6281
|
+
const sharedArgs = [
|
|
6282
|
+
row.version,
|
|
6283
|
+
taskId,
|
|
6284
|
+
importance,
|
|
6285
|
+
status,
|
|
6286
|
+
confidence,
|
|
6287
|
+
lastAccessed,
|
|
6288
|
+
workspaceId,
|
|
6289
|
+
documentId,
|
|
6290
|
+
userId,
|
|
6291
|
+
charOffset,
|
|
6292
|
+
pageNumber,
|
|
6293
|
+
sourcePath,
|
|
6294
|
+
sourceType,
|
|
6295
|
+
tier,
|
|
6296
|
+
supersedesId,
|
|
6297
|
+
draft,
|
|
6298
|
+
memoryType,
|
|
6299
|
+
trajectory,
|
|
6300
|
+
contentHash
|
|
6301
|
+
];
|
|
6302
|
+
return {
|
|
6303
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
6304
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
6305
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6306
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
5863
6307
|
};
|
|
5864
6308
|
};
|
|
5865
6309
|
const globalClient = getClient();
|