@askexenow/exe-os 0.8.0 → 0.8.1
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/README.md +178 -79
- package/dist/bin/backfill-responses.js +160 -8
- package/dist/bin/backfill-vectors.js +130 -1
- package/dist/bin/cleanup-stale-review-tasks.js +130 -1
- package/dist/bin/cli.js +10111 -7540
- package/dist/bin/exe-agent.js +159 -1
- package/dist/bin/exe-assign.js +235 -16
- package/dist/bin/exe-boot.js +344 -472
- package/dist/bin/exe-call.js +145 -1
- package/dist/bin/exe-cloud.js +11 -0
- package/dist/bin/exe-dispatch.js +37 -24
- package/dist/bin/exe-doctor.js +130 -1
- package/dist/bin/exe-export-behaviors.js +150 -7
- package/dist/bin/exe-forget.js +822 -665
- package/dist/bin/exe-gateway.js +470 -62
- package/dist/bin/exe-heartbeat.js +133 -2
- package/dist/bin/exe-kill.js +150 -7
- package/dist/bin/exe-launch-agent.js +150 -7
- package/dist/bin/exe-new-employee.js +756 -224
- package/dist/bin/exe-pending-messages.js +132 -2
- package/dist/bin/exe-pending-notifications.js +130 -1
- package/dist/bin/exe-pending-reviews.js +132 -2
- package/dist/bin/exe-review.js +160 -8
- package/dist/bin/exe-search.js +2473 -2008
- package/dist/bin/exe-session-cleanup.js +238 -51
- package/dist/bin/exe-settings.js +11 -0
- package/dist/bin/exe-status.js +130 -1
- package/dist/bin/exe-team.js +130 -1
- package/dist/bin/git-sweep.js +272 -16
- package/dist/bin/graph-backfill.js +150 -7
- package/dist/bin/graph-export.js +150 -7
- package/dist/bin/install.js +5 -0
- package/dist/bin/scan-tasks.js +238 -19
- package/dist/bin/setup.js +1776 -10
- package/dist/bin/shard-migrate.js +150 -7
- package/dist/bin/update.js +9 -6
- package/dist/bin/wiki-sync.js +150 -7
- package/dist/gateway/index.js +470 -62
- package/dist/hooks/bug-report-worker.js +195 -35
- package/dist/hooks/commit-complete.js +272 -16
- package/dist/hooks/error-recall.js +2313 -1847
- package/dist/hooks/exe-heartbeat-hook.js +5 -0
- package/dist/hooks/ingest-worker.js +330 -58
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/instructions-loaded.js +199 -10
- package/dist/hooks/notification.js +199 -10
- package/dist/hooks/post-compact.js +199 -10
- package/dist/hooks/pre-compact.js +199 -10
- package/dist/hooks/pre-tool-use.js +199 -10
- package/dist/hooks/prompt-ingest-worker.js +179 -14
- package/dist/hooks/prompt-submit.js +781 -285
- package/dist/hooks/response-ingest-worker.js +1900 -1405
- package/dist/hooks/session-end.js +456 -12
- package/dist/hooks/session-start.js +2188 -1724
- package/dist/hooks/stop.js +200 -10
- package/dist/hooks/subagent-stop.js +199 -10
- package/dist/hooks/summary-worker.js +604 -334
- package/dist/index.js +554 -61
- package/dist/lib/cloud-sync.js +5 -0
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +5 -0
- package/dist/lib/database.js +104 -0
- package/dist/lib/device-registry.js +109 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employee-templates.js +53 -26
- package/dist/lib/employees.js +5 -0
- package/dist/lib/exe-daemon-client.js +5 -0
- package/dist/lib/exe-daemon.js +493 -79
- package/dist/lib/file-grep.js +20 -4
- package/dist/lib/hybrid-search.js +1435 -190
- package/dist/lib/identity-templates.js +126 -5
- package/dist/lib/identity.js +5 -0
- package/dist/lib/license.js +5 -0
- package/dist/lib/messaging.js +37 -24
- package/dist/lib/schedules.js +130 -1
- package/dist/lib/skill-learning.js +11 -0
- package/dist/lib/status-brief.js +5 -0
- package/dist/lib/store.js +199 -10
- package/dist/lib/task-router.js +72 -6
- package/dist/lib/tasks.js +179 -50
- package/dist/lib/tmux-routing.js +179 -46
- package/dist/mcp/server.js +2129 -1855
- package/dist/mcp/tools/create-task.js +86 -36
- package/dist/mcp/tools/deactivate-behavior.js +5 -0
- package/dist/mcp/tools/list-tasks.js +39 -11
- package/dist/mcp/tools/send-message.js +37 -24
- package/dist/mcp/tools/update-task.js +153 -38
- package/dist/runtime/index.js +451 -59
- package/dist/tui/App.js +454 -59
- package/package.json +1 -1
|
@@ -38,6 +38,9 @@ async function initDatabase(config) {
|
|
|
38
38
|
}
|
|
39
39
|
_client = createClient(opts);
|
|
40
40
|
}
|
|
41
|
+
function isInitialized() {
|
|
42
|
+
return _client !== null;
|
|
43
|
+
}
|
|
41
44
|
function getClient() {
|
|
42
45
|
if (!_client) {
|
|
43
46
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -231,6 +234,27 @@ async function ensureSchema() {
|
|
|
231
234
|
});
|
|
232
235
|
} catch {
|
|
233
236
|
}
|
|
237
|
+
try {
|
|
238
|
+
await client.execute({
|
|
239
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
240
|
+
args: []
|
|
241
|
+
});
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
await client.execute({
|
|
246
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
247
|
+
args: []
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
await client.execute({
|
|
253
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
254
|
+
args: []
|
|
255
|
+
});
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
234
258
|
try {
|
|
235
259
|
await client.execute({
|
|
236
260
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -641,6 +665,15 @@ async function ensureSchema() {
|
|
|
641
665
|
} catch {
|
|
642
666
|
}
|
|
643
667
|
}
|
|
668
|
+
for (const col of [
|
|
669
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
670
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
671
|
+
]) {
|
|
672
|
+
try {
|
|
673
|
+
await client.execute(col);
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
644
677
|
await client.executeMultiple(`
|
|
645
678
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
646
679
|
ON memories(workspace_id);
|
|
@@ -705,6 +738,34 @@ async function ensureSchema() {
|
|
|
705
738
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
706
739
|
ON conversations(channel_id);
|
|
707
740
|
`);
|
|
741
|
+
try {
|
|
742
|
+
await client.execute({
|
|
743
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
744
|
+
args: []
|
|
745
|
+
});
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
await client.execute({
|
|
750
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
751
|
+
args: []
|
|
752
|
+
});
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
await client.execute({
|
|
757
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
758
|
+
args: []
|
|
759
|
+
});
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
await client.execute({
|
|
764
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
765
|
+
args: []
|
|
766
|
+
});
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
708
769
|
await client.executeMultiple(`
|
|
709
770
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
710
771
|
content_text,
|
|
@@ -731,6 +792,52 @@ async function ensureSchema() {
|
|
|
731
792
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
732
793
|
END;
|
|
733
794
|
`);
|
|
795
|
+
try {
|
|
796
|
+
await client.execute({
|
|
797
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
798
|
+
args: []
|
|
799
|
+
});
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
await client.execute(
|
|
804
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
805
|
+
);
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
await client.execute({
|
|
810
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
811
|
+
args: []
|
|
812
|
+
});
|
|
813
|
+
await client.execute({
|
|
814
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
815
|
+
args: []
|
|
816
|
+
});
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
await client.execute({
|
|
821
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
822
|
+
args: []
|
|
823
|
+
});
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
await client.execute(
|
|
828
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
829
|
+
);
|
|
830
|
+
} catch {
|
|
831
|
+
}
|
|
832
|
+
for (const col of [
|
|
833
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
834
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
835
|
+
]) {
|
|
836
|
+
try {
|
|
837
|
+
await client.execute(col);
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
734
841
|
}
|
|
735
842
|
var _client, initTurso;
|
|
736
843
|
var init_database = __esm({
|
|
@@ -968,6 +1075,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
968
1075
|
const userSL = raw.sessionLifecycle ?? {};
|
|
969
1076
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
970
1077
|
}
|
|
1078
|
+
function normalizeAutoUpdate(raw) {
|
|
1079
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1080
|
+
const userAU = raw.autoUpdate ?? {};
|
|
1081
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1082
|
+
}
|
|
971
1083
|
async function loadConfig() {
|
|
972
1084
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
973
1085
|
await mkdir2(dir, { recursive: true });
|
|
@@ -990,6 +1102,7 @@ async function loadConfig() {
|
|
|
990
1102
|
}
|
|
991
1103
|
normalizeScalingRoadmap(migratedCfg);
|
|
992
1104
|
normalizeSessionLifecycle(migratedCfg);
|
|
1105
|
+
normalizeAutoUpdate(migratedCfg);
|
|
993
1106
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
994
1107
|
if (config.dbPath.startsWith("~")) {
|
|
995
1108
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -1012,6 +1125,7 @@ function loadConfigSync() {
|
|
|
1012
1125
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1013
1126
|
normalizeScalingRoadmap(migratedCfg);
|
|
1014
1127
|
normalizeSessionLifecycle(migratedCfg);
|
|
1128
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1015
1129
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
1016
1130
|
} catch {
|
|
1017
1131
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
@@ -1031,6 +1145,7 @@ async function loadConfigFrom(configPath) {
|
|
|
1031
1145
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1032
1146
|
normalizeScalingRoadmap(migratedCfg);
|
|
1033
1147
|
normalizeSessionLifecycle(migratedCfg);
|
|
1148
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1034
1149
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1035
1150
|
} catch {
|
|
1036
1151
|
return { ...DEFAULT_CONFIG };
|
|
@@ -1102,6 +1217,11 @@ var init_config = __esm({
|
|
|
1102
1217
|
idleKillTicksRequired: 3,
|
|
1103
1218
|
idleKillIntercomAckWindowMs: 1e4,
|
|
1104
1219
|
maxAutoInstances: 10
|
|
1220
|
+
},
|
|
1221
|
+
autoUpdate: {
|
|
1222
|
+
checkOnBoot: true,
|
|
1223
|
+
autoInstall: false,
|
|
1224
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1105
1225
|
}
|
|
1106
1226
|
};
|
|
1107
1227
|
CONFIG_MIGRATIONS = [
|
|
@@ -1235,13 +1355,27 @@ async function ensureShardSchema(client) {
|
|
|
1235
1355
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1236
1356
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1237
1357
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1238
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
1358
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1359
|
+
// Source provenance columns (must match database.ts)
|
|
1360
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1361
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1362
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1363
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1239
1364
|
]) {
|
|
1240
1365
|
try {
|
|
1241
1366
|
await client.execute(col);
|
|
1242
1367
|
} catch {
|
|
1243
1368
|
}
|
|
1244
1369
|
}
|
|
1370
|
+
for (const idx of [
|
|
1371
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1372
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1373
|
+
]) {
|
|
1374
|
+
try {
|
|
1375
|
+
await client.execute(idx);
|
|
1376
|
+
} catch {
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1245
1379
|
try {
|
|
1246
1380
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1247
1381
|
} catch {
|
|
@@ -1345,139 +1479,453 @@ var init_shard_manager = __esm({
|
|
|
1345
1479
|
}
|
|
1346
1480
|
});
|
|
1347
1481
|
|
|
1348
|
-
// src/lib/
|
|
1349
|
-
import
|
|
1350
|
-
import {
|
|
1351
|
-
import {
|
|
1352
|
-
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
1482
|
+
// src/lib/employees.ts
|
|
1483
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1484
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
1485
|
+
import { execSync } from "child_process";
|
|
1353
1486
|
import path5 from "path";
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const response = JSON.parse(line);
|
|
1364
|
-
const entry = _pending.get(response.id);
|
|
1365
|
-
if (entry) {
|
|
1366
|
-
clearTimeout(entry.timer);
|
|
1367
|
-
_pending.delete(response.id);
|
|
1368
|
-
entry.resolve(response);
|
|
1369
|
-
}
|
|
1370
|
-
} catch {
|
|
1371
|
-
}
|
|
1487
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1488
|
+
if (!existsSync5(employeesPath)) {
|
|
1489
|
+
return [];
|
|
1490
|
+
}
|
|
1491
|
+
const raw = await readFile3(employeesPath, "utf-8");
|
|
1492
|
+
try {
|
|
1493
|
+
return JSON.parse(raw);
|
|
1494
|
+
} catch {
|
|
1495
|
+
return [];
|
|
1372
1496
|
}
|
|
1373
1497
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1498
|
+
var EMPLOYEES_PATH;
|
|
1499
|
+
var init_employees = __esm({
|
|
1500
|
+
"src/lib/employees.ts"() {
|
|
1501
|
+
"use strict";
|
|
1502
|
+
init_config();
|
|
1503
|
+
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
// src/lib/license.ts
|
|
1508
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
1509
|
+
import { randomUUID } from "crypto";
|
|
1510
|
+
import path6 from "path";
|
|
1511
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
1512
|
+
function loadDeviceId() {
|
|
1513
|
+
const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
|
|
1514
|
+
try {
|
|
1515
|
+
if (existsSync6(deviceJsonPath)) {
|
|
1516
|
+
const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
|
|
1517
|
+
if (data.deviceId) return data.deviceId;
|
|
1390
1518
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1519
|
+
} catch {
|
|
1520
|
+
}
|
|
1521
|
+
try {
|
|
1522
|
+
if (existsSync6(DEVICE_ID_PATH)) {
|
|
1523
|
+
const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
|
|
1524
|
+
if (id2) return id2;
|
|
1394
1525
|
}
|
|
1526
|
+
} catch {
|
|
1395
1527
|
}
|
|
1528
|
+
const id = randomUUID();
|
|
1529
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1530
|
+
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1531
|
+
return id;
|
|
1396
1532
|
}
|
|
1397
|
-
function
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1533
|
+
function loadLicense() {
|
|
1534
|
+
try {
|
|
1535
|
+
if (!existsSync6(LICENSE_PATH)) return null;
|
|
1536
|
+
return readFileSync3(LICENSE_PATH, "utf8").trim();
|
|
1537
|
+
} catch {
|
|
1538
|
+
return null;
|
|
1403
1539
|
}
|
|
1404
|
-
return null;
|
|
1405
1540
|
}
|
|
1406
|
-
function
|
|
1407
|
-
const pkgRoot = findPackageRoot();
|
|
1408
|
-
if (!pkgRoot) {
|
|
1409
|
-
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1413
|
-
if (!existsSync5(daemonPath)) {
|
|
1414
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1415
|
-
`);
|
|
1416
|
-
return;
|
|
1417
|
-
}
|
|
1418
|
-
const resolvedPath = daemonPath;
|
|
1419
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1420
|
-
`);
|
|
1421
|
-
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
1422
|
-
let stderrFd = "ignore";
|
|
1541
|
+
async function verifyLicenseJwt(token) {
|
|
1423
1542
|
try {
|
|
1424
|
-
|
|
1543
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1544
|
+
const { payload } = await jwtVerify(token, key, {
|
|
1545
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
1546
|
+
});
|
|
1547
|
+
const plan = payload.plan ?? "free";
|
|
1548
|
+
const email = payload.sub ?? "";
|
|
1549
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1550
|
+
return {
|
|
1551
|
+
valid: true,
|
|
1552
|
+
plan,
|
|
1553
|
+
email,
|
|
1554
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1555
|
+
deviceLimit: limits.devices,
|
|
1556
|
+
employeeLimit: limits.employees,
|
|
1557
|
+
memoryLimit: limits.memories
|
|
1558
|
+
};
|
|
1425
1559
|
} catch {
|
|
1426
|
-
|
|
1427
|
-
const child = spawn(process.execPath, [resolvedPath], {
|
|
1428
|
-
detached: true,
|
|
1429
|
-
stdio: ["ignore", "ignore", stderrFd],
|
|
1430
|
-
env: {
|
|
1431
|
-
...process.env,
|
|
1432
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1433
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1434
|
-
}
|
|
1435
|
-
});
|
|
1436
|
-
child.unref();
|
|
1437
|
-
if (typeof stderrFd === "number") {
|
|
1438
|
-
try {
|
|
1439
|
-
closeSync(stderrFd);
|
|
1440
|
-
} catch {
|
|
1441
|
-
}
|
|
1560
|
+
return null;
|
|
1442
1561
|
}
|
|
1443
1562
|
}
|
|
1444
|
-
function
|
|
1563
|
+
async function getCachedLicense() {
|
|
1445
1564
|
try {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
return
|
|
1565
|
+
if (!existsSync6(CACHE_PATH)) return null;
|
|
1566
|
+
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
1567
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1568
|
+
return await verifyLicenseJwt(raw.token);
|
|
1449
1569
|
} catch {
|
|
1450
|
-
|
|
1451
|
-
const stat = statSync(SPAWN_LOCK_PATH);
|
|
1452
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1453
|
-
try {
|
|
1454
|
-
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1455
|
-
} catch {
|
|
1456
|
-
}
|
|
1457
|
-
try {
|
|
1458
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1459
|
-
closeSync(fd);
|
|
1460
|
-
return true;
|
|
1461
|
-
} catch {
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
} catch {
|
|
1465
|
-
}
|
|
1466
|
-
return false;
|
|
1570
|
+
return null;
|
|
1467
1571
|
}
|
|
1468
1572
|
}
|
|
1469
|
-
function
|
|
1573
|
+
function cacheResponse(token) {
|
|
1470
1574
|
try {
|
|
1471
|
-
|
|
1575
|
+
writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1472
1576
|
} catch {
|
|
1473
1577
|
}
|
|
1474
1578
|
}
|
|
1475
|
-
function
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1579
|
+
async function validateLicense(apiKey, deviceId) {
|
|
1580
|
+
const did = deviceId ?? loadDeviceId();
|
|
1581
|
+
try {
|
|
1582
|
+
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
1583
|
+
method: "POST",
|
|
1584
|
+
headers: { "Content-Type": "application/json" },
|
|
1585
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
1586
|
+
signal: AbortSignal.timeout(1e4)
|
|
1587
|
+
});
|
|
1588
|
+
if (res.ok) {
|
|
1589
|
+
const data = await res.json();
|
|
1590
|
+
if (data.error === "device_limit_exceeded") {
|
|
1591
|
+
const cached2 = await getCachedLicense();
|
|
1592
|
+
if (cached2) return cached2;
|
|
1593
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1594
|
+
}
|
|
1595
|
+
if (data.token) {
|
|
1596
|
+
cacheResponse(data.token);
|
|
1597
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
1598
|
+
if (verified) return verified;
|
|
1599
|
+
}
|
|
1600
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1601
|
+
return {
|
|
1602
|
+
valid: data.valid,
|
|
1603
|
+
plan: data.plan,
|
|
1604
|
+
email: data.email,
|
|
1605
|
+
expiresAt: data.expiresAt,
|
|
1606
|
+
deviceLimit: limits.devices,
|
|
1607
|
+
employeeLimit: limits.employees,
|
|
1608
|
+
memoryLimit: limits.memories
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
const cached = await getCachedLicense();
|
|
1612
|
+
if (cached) return cached;
|
|
1613
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1614
|
+
} catch {
|
|
1615
|
+
const cached = await getCachedLicense();
|
|
1616
|
+
if (cached) return cached;
|
|
1617
|
+
return FREE_LICENSE;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async function checkLicense() {
|
|
1621
|
+
const key = loadLicense();
|
|
1622
|
+
if (!key) return FREE_LICENSE;
|
|
1623
|
+
const cached = await getCachedLicense();
|
|
1624
|
+
if (cached) return cached;
|
|
1625
|
+
const deviceId = loadDeviceId();
|
|
1626
|
+
return validateLicense(key, deviceId);
|
|
1627
|
+
}
|
|
1628
|
+
function isFeatureAllowed(license, feature) {
|
|
1629
|
+
switch (feature) {
|
|
1630
|
+
case "cloud_sync":
|
|
1631
|
+
case "external_agents":
|
|
1632
|
+
case "wiki":
|
|
1633
|
+
return license.plan !== "free";
|
|
1634
|
+
case "unlimited_employees":
|
|
1635
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
|
|
1639
|
+
var init_license = __esm({
|
|
1640
|
+
"src/lib/license.ts"() {
|
|
1641
|
+
"use strict";
|
|
1642
|
+
init_config();
|
|
1643
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
1644
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
1645
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1646
|
+
API_BASE = "https://askexe.com/cloud";
|
|
1647
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1648
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1649
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1650
|
+
-----END PUBLIC KEY-----`;
|
|
1651
|
+
LICENSE_JWT_ALG = "ES256";
|
|
1652
|
+
PLAN_LIMITS = {
|
|
1653
|
+
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1654
|
+
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
1655
|
+
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
1656
|
+
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1657
|
+
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1658
|
+
};
|
|
1659
|
+
FREE_LICENSE = {
|
|
1660
|
+
valid: true,
|
|
1661
|
+
plan: "free",
|
|
1662
|
+
email: "",
|
|
1663
|
+
expiresAt: null,
|
|
1664
|
+
deviceLimit: 1,
|
|
1665
|
+
employeeLimit: 1,
|
|
1666
|
+
memoryLimit: 5e3
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
// src/lib/plan-limits.ts
|
|
1672
|
+
var plan_limits_exports = {};
|
|
1673
|
+
__export(plan_limits_exports, {
|
|
1674
|
+
PlanLimitError: () => PlanLimitError,
|
|
1675
|
+
assertEmployeeLimit: () => assertEmployeeLimit,
|
|
1676
|
+
assertEmployeeLimitSync: () => assertEmployeeLimitSync,
|
|
1677
|
+
assertFeature: () => assertFeature,
|
|
1678
|
+
assertMemoryLimit: () => assertMemoryLimit,
|
|
1679
|
+
countActiveMemories: () => countActiveMemories,
|
|
1680
|
+
getLicenseSync: () => getLicenseSync
|
|
1681
|
+
});
|
|
1682
|
+
import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
|
|
1683
|
+
import path7 from "path";
|
|
1684
|
+
function getLicenseSync() {
|
|
1685
|
+
try {
|
|
1686
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
1687
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH2, "utf8"));
|
|
1688
|
+
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1689
|
+
const parts = raw.token.split(".");
|
|
1690
|
+
if (parts.length !== 3) return freeLicense();
|
|
1691
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1692
|
+
const plan = payload.plan ?? "free";
|
|
1693
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1694
|
+
return {
|
|
1695
|
+
valid: true,
|
|
1696
|
+
plan,
|
|
1697
|
+
email: payload.sub ?? "",
|
|
1698
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1699
|
+
deviceLimit: limits.devices,
|
|
1700
|
+
employeeLimit: limits.employees,
|
|
1701
|
+
memoryLimit: limits.memories
|
|
1702
|
+
};
|
|
1703
|
+
} catch {
|
|
1704
|
+
return freeLicense();
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function freeLicense() {
|
|
1708
|
+
const limits = PLAN_LIMITS.free;
|
|
1709
|
+
return {
|
|
1710
|
+
valid: true,
|
|
1711
|
+
plan: "free",
|
|
1712
|
+
email: "",
|
|
1713
|
+
expiresAt: null,
|
|
1714
|
+
deviceLimit: limits.devices,
|
|
1715
|
+
employeeLimit: limits.employees,
|
|
1716
|
+
memoryLimit: limits.memories
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
async function countActiveMemories() {
|
|
1720
|
+
if (!isInitialized()) return 0;
|
|
1721
|
+
const client = getClient();
|
|
1722
|
+
const result = await client.execute(
|
|
1723
|
+
"SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL"
|
|
1724
|
+
);
|
|
1725
|
+
const row = result.rows[0];
|
|
1726
|
+
return Number(row?.cnt ?? 0);
|
|
1727
|
+
}
|
|
1728
|
+
async function assertMemoryLimit() {
|
|
1729
|
+
const license = await checkLicense();
|
|
1730
|
+
if (license.memoryLimit < 0) return;
|
|
1731
|
+
const count = await countActiveMemories();
|
|
1732
|
+
if (count >= license.memoryLimit) {
|
|
1733
|
+
throw new PlanLimitError(
|
|
1734
|
+
`Memory limit reached: ${count}/${license.memoryLimit} active memories on the ${license.plan} plan. Upgrade at https://askexe.com to store more.`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async function assertEmployeeLimit(license, rosterPath) {
|
|
1739
|
+
const lic = license ?? await checkLicense();
|
|
1740
|
+
if (lic.employeeLimit < 0) return;
|
|
1741
|
+
const employees = await loadEmployees(rosterPath ?? EMPLOYEES_PATH);
|
|
1742
|
+
if (employees.length >= lic.employeeLimit) {
|
|
1743
|
+
throw new PlanLimitError(
|
|
1744
|
+
`Employee limit reached: ${employees.length}/${lic.employeeLimit} employees on the ${lic.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function assertEmployeeLimitSync(rosterPath) {
|
|
1749
|
+
const license = getLicenseSync();
|
|
1750
|
+
if (license.employeeLimit < 0) return;
|
|
1751
|
+
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1752
|
+
let count = 0;
|
|
1753
|
+
try {
|
|
1754
|
+
if (existsSync7(filePath)) {
|
|
1755
|
+
const raw = readFileSync4(filePath, "utf8");
|
|
1756
|
+
const employees = JSON.parse(raw);
|
|
1757
|
+
count = Array.isArray(employees) ? employees.length : 0;
|
|
1758
|
+
}
|
|
1759
|
+
} catch {
|
|
1760
|
+
throw new PlanLimitError(
|
|
1761
|
+
`Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
if (count >= license.employeeLimit) {
|
|
1765
|
+
throw new PlanLimitError(
|
|
1766
|
+
`Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async function assertFeature(feature) {
|
|
1771
|
+
const license = await checkLicense();
|
|
1772
|
+
if (!isFeatureAllowed(license, feature)) {
|
|
1773
|
+
throw new PlanLimitError(
|
|
1774
|
+
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
var PlanLimitError, CACHE_PATH2;
|
|
1779
|
+
var init_plan_limits = __esm({
|
|
1780
|
+
"src/lib/plan-limits.ts"() {
|
|
1781
|
+
"use strict";
|
|
1782
|
+
init_database();
|
|
1783
|
+
init_employees();
|
|
1784
|
+
init_license();
|
|
1785
|
+
init_config();
|
|
1786
|
+
PlanLimitError = class extends Error {
|
|
1787
|
+
constructor(message) {
|
|
1788
|
+
super(message);
|
|
1789
|
+
this.name = "PlanLimitError";
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
// src/lib/exe-daemon-client.ts
|
|
1797
|
+
import net from "net";
|
|
1798
|
+
import { spawn } from "child_process";
|
|
1799
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1800
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1801
|
+
import path8 from "path";
|
|
1802
|
+
import { fileURLToPath } from "url";
|
|
1803
|
+
function handleData(chunk) {
|
|
1804
|
+
_buffer += chunk.toString();
|
|
1805
|
+
let newlineIdx;
|
|
1806
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1807
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
1808
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
1809
|
+
if (!line) continue;
|
|
1810
|
+
try {
|
|
1811
|
+
const response = JSON.parse(line);
|
|
1812
|
+
const entry = _pending.get(response.id);
|
|
1813
|
+
if (entry) {
|
|
1814
|
+
clearTimeout(entry.timer);
|
|
1815
|
+
_pending.delete(response.id);
|
|
1816
|
+
entry.resolve(response);
|
|
1817
|
+
}
|
|
1818
|
+
} catch {
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function cleanupStaleFiles() {
|
|
1823
|
+
if (existsSync8(PID_PATH)) {
|
|
1824
|
+
try {
|
|
1825
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1826
|
+
if (pid > 0) {
|
|
1827
|
+
try {
|
|
1828
|
+
process.kill(pid, 0);
|
|
1829
|
+
return;
|
|
1830
|
+
} catch {
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
} catch {
|
|
1834
|
+
}
|
|
1835
|
+
try {
|
|
1836
|
+
unlinkSync2(PID_PATH);
|
|
1837
|
+
} catch {
|
|
1838
|
+
}
|
|
1839
|
+
try {
|
|
1840
|
+
unlinkSync2(SOCKET_PATH);
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
function findPackageRoot() {
|
|
1846
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1847
|
+
const { root } = path8.parse(dir);
|
|
1848
|
+
while (dir !== root) {
|
|
1849
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1850
|
+
dir = path8.dirname(dir);
|
|
1851
|
+
}
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
function spawnDaemon() {
|
|
1855
|
+
const pkgRoot = findPackageRoot();
|
|
1856
|
+
if (!pkgRoot) {
|
|
1857
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1861
|
+
if (!existsSync8(daemonPath)) {
|
|
1862
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1863
|
+
`);
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const resolvedPath = daemonPath;
|
|
1867
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1868
|
+
`);
|
|
1869
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1870
|
+
let stderrFd = "ignore";
|
|
1871
|
+
try {
|
|
1872
|
+
stderrFd = openSync(logPath, "a");
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
1876
|
+
detached: true,
|
|
1877
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
1878
|
+
env: {
|
|
1879
|
+
...process.env,
|
|
1880
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1881
|
+
EXE_DAEMON_PID: PID_PATH
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
child.unref();
|
|
1885
|
+
if (typeof stderrFd === "number") {
|
|
1886
|
+
try {
|
|
1887
|
+
closeSync(stderrFd);
|
|
1888
|
+
} catch {
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
function acquireSpawnLock() {
|
|
1893
|
+
try {
|
|
1894
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1895
|
+
closeSync(fd);
|
|
1896
|
+
return true;
|
|
1897
|
+
} catch {
|
|
1898
|
+
try {
|
|
1899
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
1900
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1901
|
+
try {
|
|
1902
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
try {
|
|
1906
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1907
|
+
closeSync(fd);
|
|
1908
|
+
return true;
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
} catch {
|
|
1913
|
+
}
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
function releaseSpawnLock() {
|
|
1918
|
+
try {
|
|
1919
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1920
|
+
} catch {
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
function connectToSocket() {
|
|
1924
|
+
return new Promise((resolve) => {
|
|
1925
|
+
if (_socket && _connected) {
|
|
1926
|
+
resolve(true);
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1481
1929
|
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
1482
1930
|
const connectTimeout = setTimeout(() => {
|
|
1483
1931
|
socket.destroy();
|
|
@@ -1536,7 +1984,7 @@ function sendRequest(texts, priority) {
|
|
|
1536
1984
|
resolve({ error: "Not connected" });
|
|
1537
1985
|
return;
|
|
1538
1986
|
}
|
|
1539
|
-
const id =
|
|
1987
|
+
const id = randomUUID2();
|
|
1540
1988
|
const timer = setTimeout(() => {
|
|
1541
1989
|
_pending.delete(id);
|
|
1542
1990
|
resolve({ error: "Request timeout" });
|
|
@@ -1554,7 +2002,7 @@ function sendRequest(texts, priority) {
|
|
|
1554
2002
|
async function pingDaemon() {
|
|
1555
2003
|
if (!_socket || !_connected) return null;
|
|
1556
2004
|
return new Promise((resolve) => {
|
|
1557
|
-
const id =
|
|
2005
|
+
const id = randomUUID2();
|
|
1558
2006
|
const timer = setTimeout(() => {
|
|
1559
2007
|
_pending.delete(id);
|
|
1560
2008
|
resolve(null);
|
|
@@ -1580,9 +2028,9 @@ async function pingDaemon() {
|
|
|
1580
2028
|
}
|
|
1581
2029
|
function killAndRespawnDaemon() {
|
|
1582
2030
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1583
|
-
if (
|
|
2031
|
+
if (existsSync8(PID_PATH)) {
|
|
1584
2032
|
try {
|
|
1585
|
-
const pid = parseInt(
|
|
2033
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1586
2034
|
if (pid > 0) {
|
|
1587
2035
|
try {
|
|
1588
2036
|
process.kill(pid, "SIGKILL");
|
|
@@ -1666,9 +2114,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1666
2114
|
"src/lib/exe-daemon-client.ts"() {
|
|
1667
2115
|
"use strict";
|
|
1668
2116
|
init_config();
|
|
1669
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1670
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1671
|
-
SPAWN_LOCK_PATH =
|
|
2117
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
2118
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
2119
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1672
2120
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1673
2121
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1674
2122
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1828,213 +2276,6 @@ var init_compress = __esm({
|
|
|
1828
2276
|
}
|
|
1829
2277
|
});
|
|
1830
2278
|
|
|
1831
|
-
// src/lib/employees.ts
|
|
1832
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1833
|
-
import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
|
|
1834
|
-
import { execSync } from "child_process";
|
|
1835
|
-
import path6 from "path";
|
|
1836
|
-
var EMPLOYEES_PATH;
|
|
1837
|
-
var init_employees = __esm({
|
|
1838
|
-
"src/lib/employees.ts"() {
|
|
1839
|
-
"use strict";
|
|
1840
|
-
init_config();
|
|
1841
|
-
EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
|
|
1842
|
-
}
|
|
1843
|
-
});
|
|
1844
|
-
|
|
1845
|
-
// src/lib/license.ts
|
|
1846
|
-
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1847
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1848
|
-
import path7 from "path";
|
|
1849
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
1850
|
-
function loadDeviceId() {
|
|
1851
|
-
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
1852
|
-
try {
|
|
1853
|
-
if (existsSync7(deviceJsonPath)) {
|
|
1854
|
-
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
1855
|
-
if (data.deviceId) return data.deviceId;
|
|
1856
|
-
}
|
|
1857
|
-
} catch {
|
|
1858
|
-
}
|
|
1859
|
-
try {
|
|
1860
|
-
if (existsSync7(DEVICE_ID_PATH)) {
|
|
1861
|
-
const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
|
|
1862
|
-
if (id2) return id2;
|
|
1863
|
-
}
|
|
1864
|
-
} catch {
|
|
1865
|
-
}
|
|
1866
|
-
const id = randomUUID2();
|
|
1867
|
-
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1868
|
-
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1869
|
-
return id;
|
|
1870
|
-
}
|
|
1871
|
-
function loadLicense() {
|
|
1872
|
-
try {
|
|
1873
|
-
if (!existsSync7(LICENSE_PATH)) return null;
|
|
1874
|
-
return readFileSync4(LICENSE_PATH, "utf8").trim();
|
|
1875
|
-
} catch {
|
|
1876
|
-
return null;
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
async function verifyLicenseJwt(token) {
|
|
1880
|
-
try {
|
|
1881
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1882
|
-
const { payload } = await jwtVerify(token, key, {
|
|
1883
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
1884
|
-
});
|
|
1885
|
-
const plan = payload.plan ?? "free";
|
|
1886
|
-
const email = payload.sub ?? "";
|
|
1887
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1888
|
-
return {
|
|
1889
|
-
valid: true,
|
|
1890
|
-
plan,
|
|
1891
|
-
email,
|
|
1892
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1893
|
-
deviceLimit: limits.devices,
|
|
1894
|
-
employeeLimit: limits.employees,
|
|
1895
|
-
memoryLimit: limits.memories
|
|
1896
|
-
};
|
|
1897
|
-
} catch {
|
|
1898
|
-
return null;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
async function getCachedLicense() {
|
|
1902
|
-
try {
|
|
1903
|
-
if (!existsSync7(CACHE_PATH)) return null;
|
|
1904
|
-
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
1905
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1906
|
-
return await verifyLicenseJwt(raw.token);
|
|
1907
|
-
} catch {
|
|
1908
|
-
return null;
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
function cacheResponse(token) {
|
|
1912
|
-
try {
|
|
1913
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1914
|
-
} catch {
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
async function validateLicense(apiKey, deviceId) {
|
|
1918
|
-
const did = deviceId ?? loadDeviceId();
|
|
1919
|
-
try {
|
|
1920
|
-
const res = await fetch(`${API_BASE}/auth/activate`, {
|
|
1921
|
-
method: "POST",
|
|
1922
|
-
headers: { "Content-Type": "application/json" },
|
|
1923
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
1924
|
-
signal: AbortSignal.timeout(1e4)
|
|
1925
|
-
});
|
|
1926
|
-
if (res.ok) {
|
|
1927
|
-
const data = await res.json();
|
|
1928
|
-
if (data.error === "device_limit_exceeded") {
|
|
1929
|
-
const cached2 = await getCachedLicense();
|
|
1930
|
-
if (cached2) return cached2;
|
|
1931
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1932
|
-
}
|
|
1933
|
-
if (data.token) {
|
|
1934
|
-
cacheResponse(data.token);
|
|
1935
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
1936
|
-
if (verified) return verified;
|
|
1937
|
-
}
|
|
1938
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1939
|
-
return {
|
|
1940
|
-
valid: data.valid,
|
|
1941
|
-
plan: data.plan,
|
|
1942
|
-
email: data.email,
|
|
1943
|
-
expiresAt: data.expiresAt,
|
|
1944
|
-
deviceLimit: limits.devices,
|
|
1945
|
-
employeeLimit: limits.employees,
|
|
1946
|
-
memoryLimit: limits.memories
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
const cached = await getCachedLicense();
|
|
1950
|
-
if (cached) return cached;
|
|
1951
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1952
|
-
} catch {
|
|
1953
|
-
const cached = await getCachedLicense();
|
|
1954
|
-
if (cached) return cached;
|
|
1955
|
-
return FREE_LICENSE;
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
async function checkLicense() {
|
|
1959
|
-
const key = loadLicense();
|
|
1960
|
-
if (!key) return FREE_LICENSE;
|
|
1961
|
-
const cached = await getCachedLicense();
|
|
1962
|
-
if (cached) return cached;
|
|
1963
|
-
const deviceId = loadDeviceId();
|
|
1964
|
-
return validateLicense(key, deviceId);
|
|
1965
|
-
}
|
|
1966
|
-
function isFeatureAllowed(license, feature) {
|
|
1967
|
-
switch (feature) {
|
|
1968
|
-
case "cloud_sync":
|
|
1969
|
-
case "external_agents":
|
|
1970
|
-
case "wiki":
|
|
1971
|
-
return license.plan !== "free";
|
|
1972
|
-
case "unlimited_employees":
|
|
1973
|
-
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
|
|
1977
|
-
var init_license = __esm({
|
|
1978
|
-
"src/lib/license.ts"() {
|
|
1979
|
-
"use strict";
|
|
1980
|
-
init_config();
|
|
1981
|
-
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
1982
|
-
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1983
|
-
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1984
|
-
API_BASE = "https://askexe.com/cloud";
|
|
1985
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1986
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1987
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1988
|
-
-----END PUBLIC KEY-----`;
|
|
1989
|
-
LICENSE_JWT_ALG = "ES256";
|
|
1990
|
-
PLAN_LIMITS = {
|
|
1991
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1992
|
-
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
1993
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
1994
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1995
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1996
|
-
};
|
|
1997
|
-
FREE_LICENSE = {
|
|
1998
|
-
valid: true,
|
|
1999
|
-
plan: "free",
|
|
2000
|
-
email: "",
|
|
2001
|
-
expiresAt: null,
|
|
2002
|
-
deviceLimit: 1,
|
|
2003
|
-
employeeLimit: 1,
|
|
2004
|
-
memoryLimit: 5e3
|
|
2005
|
-
};
|
|
2006
|
-
}
|
|
2007
|
-
});
|
|
2008
|
-
|
|
2009
|
-
// src/lib/plan-limits.ts
|
|
2010
|
-
import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
|
|
2011
|
-
import path8 from "path";
|
|
2012
|
-
async function assertFeature(feature) {
|
|
2013
|
-
const license = await checkLicense();
|
|
2014
|
-
if (!isFeatureAllowed(license, feature)) {
|
|
2015
|
-
throw new PlanLimitError(
|
|
2016
|
-
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
2017
|
-
);
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
var PlanLimitError, CACHE_PATH2;
|
|
2021
|
-
var init_plan_limits = __esm({
|
|
2022
|
-
"src/lib/plan-limits.ts"() {
|
|
2023
|
-
"use strict";
|
|
2024
|
-
init_database();
|
|
2025
|
-
init_employees();
|
|
2026
|
-
init_license();
|
|
2027
|
-
init_config();
|
|
2028
|
-
PlanLimitError = class extends Error {
|
|
2029
|
-
constructor(message) {
|
|
2030
|
-
super(message);
|
|
2031
|
-
this.name = "PlanLimitError";
|
|
2032
|
-
}
|
|
2033
|
-
};
|
|
2034
|
-
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2035
|
-
}
|
|
2036
|
-
});
|
|
2037
|
-
|
|
2038
2279
|
// src/lib/cloud-sync.ts
|
|
2039
2280
|
var cloud_sync_exports = {};
|
|
2040
2281
|
__export(cloud_sync_exports, {
|
|
@@ -2260,6 +2501,11 @@ async function initStore(options) {
|
|
|
2260
2501
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
2261
2502
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2262
2503
|
}
|
|
2504
|
+
function classifyTier(record) {
|
|
2505
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
2506
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2507
|
+
return 3;
|
|
2508
|
+
}
|
|
2263
2509
|
async function writeMemory(record) {
|
|
2264
2510
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
2265
2511
|
throw new Error(
|
|
@@ -2287,7 +2533,11 @@ async function writeMemory(record) {
|
|
|
2287
2533
|
document_id: record.document_id ?? null,
|
|
2288
2534
|
user_id: record.user_id ?? null,
|
|
2289
2535
|
char_offset: record.char_offset ?? null,
|
|
2290
|
-
page_number: record.page_number ?? null
|
|
2536
|
+
page_number: record.page_number ?? null,
|
|
2537
|
+
source_path: record.source_path ?? null,
|
|
2538
|
+
source_type: record.source_type ?? null,
|
|
2539
|
+
tier: record.tier ?? classifyTier(record),
|
|
2540
|
+
supersedes_id: record.supersedes_id ?? null
|
|
2291
2541
|
};
|
|
2292
2542
|
_pendingRecords.push(dbRow);
|
|
2293
2543
|
if (_flushTimer === null) {
|
|
@@ -2319,20 +2569,26 @@ async function flushBatch() {
|
|
|
2319
2569
|
const userId = row.user_id ?? null;
|
|
2320
2570
|
const charOffset = row.char_offset ?? null;
|
|
2321
2571
|
const pageNumber = row.page_number ?? null;
|
|
2572
|
+
const sourcePath = row.source_path ?? null;
|
|
2573
|
+
const sourceType = row.source_type ?? null;
|
|
2574
|
+
const tier = row.tier ?? 3;
|
|
2575
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
2322
2576
|
return {
|
|
2323
2577
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
2324
2578
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2325
2579
|
tool_name, project_name,
|
|
2326
2580
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2327
2581
|
confidence, last_accessed,
|
|
2328
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2329
|
-
|
|
2582
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2583
|
+
source_path, source_type, tier, supersedes_id)
|
|
2584
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
2330
2585
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2331
2586
|
tool_name, project_name,
|
|
2332
2587
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2333
2588
|
confidence, last_accessed,
|
|
2334
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2335
|
-
|
|
2589
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2590
|
+
source_path, source_type, tier, supersedes_id)
|
|
2591
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2336
2592
|
args: hasVector ? [
|
|
2337
2593
|
row.id,
|
|
2338
2594
|
row.agent_id,
|
|
@@ -2354,7 +2610,11 @@ async function flushBatch() {
|
|
|
2354
2610
|
documentId,
|
|
2355
2611
|
userId,
|
|
2356
2612
|
charOffset,
|
|
2357
|
-
pageNumber
|
|
2613
|
+
pageNumber,
|
|
2614
|
+
sourcePath,
|
|
2615
|
+
sourceType,
|
|
2616
|
+
tier,
|
|
2617
|
+
supersedesId
|
|
2358
2618
|
] : [
|
|
2359
2619
|
row.id,
|
|
2360
2620
|
row.agent_id,
|
|
@@ -2375,7 +2635,11 @@ async function flushBatch() {
|
|
|
2375
2635
|
documentId,
|
|
2376
2636
|
userId,
|
|
2377
2637
|
charOffset,
|
|
2378
|
-
pageNumber
|
|
2638
|
+
pageNumber,
|
|
2639
|
+
sourcePath,
|
|
2640
|
+
sourceType,
|
|
2641
|
+
tier,
|
|
2642
|
+
supersedesId
|
|
2379
2643
|
]
|
|
2380
2644
|
};
|
|
2381
2645
|
};
|
|
@@ -2523,6 +2787,12 @@ async function main() {
|
|
|
2523
2787
|
primaryProject = p;
|
|
2524
2788
|
}
|
|
2525
2789
|
}
|
|
2790
|
+
try {
|
|
2791
|
+
const { assertMemoryLimit: assertMemoryLimit2 } = await Promise.resolve().then(() => (init_plan_limits(), plan_limits_exports));
|
|
2792
|
+
await assertMemoryLimit2();
|
|
2793
|
+
} catch {
|
|
2794
|
+
process.exit(0);
|
|
2795
|
+
}
|
|
2526
2796
|
await writeMemory({
|
|
2527
2797
|
id: crypto4.randomUUID(),
|
|
2528
2798
|
agent_id: agentId,
|