@askexenow/exe-os 0.9.8 → 0.9.9
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 +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1295 -856
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +677 -388
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +440 -250
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +404 -212
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +412 -220
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +538 -324
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +935 -587
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +306 -248
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
|
@@ -78,6 +78,44 @@ var init_db_retry = __esm({
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
// src/lib/secure-files.ts
|
|
82
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
83
|
+
import { chmod, mkdir } from "fs/promises";
|
|
84
|
+
async function ensurePrivateDir(dirPath) {
|
|
85
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
86
|
+
try {
|
|
87
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function ensurePrivateDirSync(dirPath) {
|
|
92
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
93
|
+
try {
|
|
94
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function enforcePrivateFile(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function enforcePrivateFileSync(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
111
|
+
var init_secure_files = __esm({
|
|
112
|
+
"src/lib/secure-files.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
PRIVATE_DIR_MODE = 448;
|
|
115
|
+
PRIVATE_FILE_MODE = 384;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
81
119
|
// src/lib/config.ts
|
|
82
120
|
var config_exports = {};
|
|
83
121
|
__export(config_exports, {
|
|
@@ -94,8 +132,8 @@ __export(config_exports, {
|
|
|
94
132
|
migrateConfig: () => migrateConfig,
|
|
95
133
|
saveConfig: () => saveConfig
|
|
96
134
|
});
|
|
97
|
-
import { readFile, writeFile
|
|
98
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
135
|
+
import { readFile, writeFile } from "fs/promises";
|
|
136
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
99
137
|
import path2 from "path";
|
|
100
138
|
import os from "os";
|
|
101
139
|
function resolveDataDir() {
|
|
@@ -103,7 +141,7 @@ function resolveDataDir() {
|
|
|
103
141
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
104
142
|
const newDir = path2.join(os.homedir(), ".exe-os");
|
|
105
143
|
const legacyDir = path2.join(os.homedir(), ".exe-mem");
|
|
106
|
-
if (!
|
|
144
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
107
145
|
try {
|
|
108
146
|
renameSync(legacyDir, newDir);
|
|
109
147
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -166,9 +204,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
166
204
|
}
|
|
167
205
|
async function loadConfig() {
|
|
168
206
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
169
|
-
await
|
|
207
|
+
await ensurePrivateDir(dir);
|
|
170
208
|
const configPath = path2.join(dir, "config.json");
|
|
171
|
-
if (!
|
|
209
|
+
if (!existsSync2(configPath)) {
|
|
172
210
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
173
211
|
}
|
|
174
212
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -181,6 +219,7 @@ async function loadConfig() {
|
|
|
181
219
|
`);
|
|
182
220
|
try {
|
|
183
221
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
222
|
+
await enforcePrivateFile(configPath);
|
|
184
223
|
} catch {
|
|
185
224
|
}
|
|
186
225
|
}
|
|
@@ -199,7 +238,7 @@ async function loadConfig() {
|
|
|
199
238
|
function loadConfigSync() {
|
|
200
239
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
201
240
|
const configPath = path2.join(dir, "config.json");
|
|
202
|
-
if (!
|
|
241
|
+
if (!existsSync2(configPath)) {
|
|
203
242
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
204
243
|
}
|
|
205
244
|
try {
|
|
@@ -217,12 +256,10 @@ function loadConfigSync() {
|
|
|
217
256
|
}
|
|
218
257
|
async function saveConfig(config) {
|
|
219
258
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
220
|
-
await
|
|
259
|
+
await ensurePrivateDir(dir);
|
|
221
260
|
const configPath = path2.join(dir, "config.json");
|
|
222
261
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
223
|
-
|
|
224
|
-
await chmod(configPath, 384);
|
|
225
|
-
}
|
|
262
|
+
await enforcePrivateFile(configPath);
|
|
226
263
|
}
|
|
227
264
|
async function loadConfigFrom(configPath) {
|
|
228
265
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -242,6 +279,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
242
279
|
var init_config = __esm({
|
|
243
280
|
"src/lib/config.ts"() {
|
|
244
281
|
"use strict";
|
|
282
|
+
init_secure_files();
|
|
245
283
|
EXE_AI_DIR = resolveDataDir();
|
|
246
284
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
247
285
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -320,7 +358,7 @@ var init_config = __esm({
|
|
|
320
358
|
|
|
321
359
|
// src/lib/employees.ts
|
|
322
360
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
323
|
-
import { existsSync as
|
|
361
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
324
362
|
import { execSync as execSync2 } from "child_process";
|
|
325
363
|
import path3 from "path";
|
|
326
364
|
import os2 from "os";
|
|
@@ -337,7 +375,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
337
375
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
338
376
|
}
|
|
339
377
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
340
|
-
if (!
|
|
378
|
+
if (!existsSync3(employeesPath)) {
|
|
341
379
|
return [];
|
|
342
380
|
}
|
|
343
381
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -348,7 +386,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
348
386
|
}
|
|
349
387
|
}
|
|
350
388
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
351
|
-
if (!
|
|
389
|
+
if (!existsSync3(employeesPath)) return [];
|
|
352
390
|
try {
|
|
353
391
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
354
392
|
} catch {
|
|
@@ -1296,6 +1334,7 @@ async function ensureSchema() {
|
|
|
1296
1334
|
project TEXT NOT NULL,
|
|
1297
1335
|
summary TEXT NOT NULL,
|
|
1298
1336
|
task_file TEXT,
|
|
1337
|
+
session_scope TEXT,
|
|
1299
1338
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1300
1339
|
created_at TEXT NOT NULL
|
|
1301
1340
|
);
|
|
@@ -1304,7 +1343,7 @@ async function ensureSchema() {
|
|
|
1304
1343
|
ON notifications(read);
|
|
1305
1344
|
|
|
1306
1345
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1307
|
-
ON notifications(agent_id);
|
|
1346
|
+
ON notifications(agent_id, session_scope);
|
|
1308
1347
|
|
|
1309
1348
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1310
1349
|
ON notifications(task_file);
|
|
@@ -1342,6 +1381,7 @@ async function ensureSchema() {
|
|
|
1342
1381
|
target_agent TEXT NOT NULL,
|
|
1343
1382
|
target_project TEXT,
|
|
1344
1383
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1384
|
+
session_scope TEXT,
|
|
1345
1385
|
content TEXT NOT NULL,
|
|
1346
1386
|
priority TEXT DEFAULT 'normal',
|
|
1347
1387
|
status TEXT DEFAULT 'pending',
|
|
@@ -1355,10 +1395,31 @@ async function ensureSchema() {
|
|
|
1355
1395
|
);
|
|
1356
1396
|
|
|
1357
1397
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1358
|
-
ON messages(target_agent, status);
|
|
1398
|
+
ON messages(target_agent, session_scope, status);
|
|
1359
1399
|
|
|
1360
1400
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1361
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1401
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1402
|
+
`);
|
|
1403
|
+
try {
|
|
1404
|
+
await client.execute({
|
|
1405
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1406
|
+
args: []
|
|
1407
|
+
});
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
try {
|
|
1411
|
+
await client.execute({
|
|
1412
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1413
|
+
args: []
|
|
1414
|
+
});
|
|
1415
|
+
} catch {
|
|
1416
|
+
}
|
|
1417
|
+
await client.executeMultiple(`
|
|
1418
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1419
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1420
|
+
|
|
1421
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1422
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1362
1423
|
`);
|
|
1363
1424
|
try {
|
|
1364
1425
|
await client.execute({
|
|
@@ -1942,6 +2003,13 @@ async function ensureSchema() {
|
|
|
1942
2003
|
} catch {
|
|
1943
2004
|
}
|
|
1944
2005
|
}
|
|
2006
|
+
try {
|
|
2007
|
+
await client.execute({
|
|
2008
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2009
|
+
args: []
|
|
2010
|
+
});
|
|
2011
|
+
} catch {
|
|
2012
|
+
}
|
|
1945
2013
|
}
|
|
1946
2014
|
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1947
2015
|
var init_database = __esm({
|
|
@@ -1964,6 +2032,7 @@ var shard_manager_exports = {};
|
|
|
1964
2032
|
__export(shard_manager_exports, {
|
|
1965
2033
|
disposeShards: () => disposeShards,
|
|
1966
2034
|
ensureShardSchema: () => ensureShardSchema,
|
|
2035
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1967
2036
|
getReadyShardClient: () => getReadyShardClient,
|
|
1968
2037
|
getShardClient: () => getShardClient,
|
|
1969
2038
|
getShardsDir: () => getShardsDir,
|
|
@@ -1973,14 +2042,17 @@ __export(shard_manager_exports, {
|
|
|
1973
2042
|
shardExists: () => shardExists
|
|
1974
2043
|
});
|
|
1975
2044
|
import path6 from "path";
|
|
1976
|
-
import { existsSync as
|
|
2045
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1977
2046
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1978
2047
|
function initShardManager(encryptionKey) {
|
|
1979
2048
|
_encryptionKey = encryptionKey;
|
|
1980
|
-
if (!
|
|
1981
|
-
|
|
2049
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2050
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1982
2051
|
}
|
|
1983
2052
|
_shardingEnabled = true;
|
|
2053
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2054
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2055
|
+
_evictionTimer.unref();
|
|
1984
2056
|
}
|
|
1985
2057
|
function isShardingEnabled() {
|
|
1986
2058
|
return _shardingEnabled;
|
|
@@ -1997,21 +2069,28 @@ function getShardClient(projectName) {
|
|
|
1997
2069
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1998
2070
|
}
|
|
1999
2071
|
const cached = _shards.get(safeName);
|
|
2000
|
-
if (cached)
|
|
2072
|
+
if (cached) {
|
|
2073
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2074
|
+
return cached;
|
|
2075
|
+
}
|
|
2076
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2077
|
+
evictLRU();
|
|
2078
|
+
}
|
|
2001
2079
|
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
2002
2080
|
const client = createClient2({
|
|
2003
2081
|
url: `file:${dbPath}`,
|
|
2004
2082
|
encryptionKey: _encryptionKey
|
|
2005
2083
|
});
|
|
2006
2084
|
_shards.set(safeName, client);
|
|
2085
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2007
2086
|
return client;
|
|
2008
2087
|
}
|
|
2009
2088
|
function shardExists(projectName) {
|
|
2010
2089
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2011
|
-
return
|
|
2090
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
2012
2091
|
}
|
|
2013
2092
|
function listShards() {
|
|
2014
|
-
if (!
|
|
2093
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
2015
2094
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2016
2095
|
}
|
|
2017
2096
|
async function ensureShardSchema(client) {
|
|
@@ -2063,6 +2142,8 @@ async function ensureShardSchema(client) {
|
|
|
2063
2142
|
for (const col of [
|
|
2064
2143
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2065
2144
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2145
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2146
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2066
2147
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2067
2148
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2068
2149
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2200,21 +2281,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2200
2281
|
await ensureShardSchema(client);
|
|
2201
2282
|
return client;
|
|
2202
2283
|
}
|
|
2284
|
+
function evictLRU() {
|
|
2285
|
+
let oldest = null;
|
|
2286
|
+
let oldestTime = Infinity;
|
|
2287
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2288
|
+
if (time < oldestTime) {
|
|
2289
|
+
oldestTime = time;
|
|
2290
|
+
oldest = name;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (oldest) {
|
|
2294
|
+
const client = _shards.get(oldest);
|
|
2295
|
+
if (client) {
|
|
2296
|
+
client.close();
|
|
2297
|
+
}
|
|
2298
|
+
_shards.delete(oldest);
|
|
2299
|
+
_shardLastAccess.delete(oldest);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function evictIdleShards() {
|
|
2303
|
+
const now = Date.now();
|
|
2304
|
+
const toEvict = [];
|
|
2305
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2306
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2307
|
+
toEvict.push(name);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
for (const name of toEvict) {
|
|
2311
|
+
const client = _shards.get(name);
|
|
2312
|
+
if (client) {
|
|
2313
|
+
client.close();
|
|
2314
|
+
}
|
|
2315
|
+
_shards.delete(name);
|
|
2316
|
+
_shardLastAccess.delete(name);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function getOpenShardCount() {
|
|
2320
|
+
return _shards.size;
|
|
2321
|
+
}
|
|
2203
2322
|
function disposeShards() {
|
|
2323
|
+
if (_evictionTimer) {
|
|
2324
|
+
clearInterval(_evictionTimer);
|
|
2325
|
+
_evictionTimer = null;
|
|
2326
|
+
}
|
|
2204
2327
|
for (const [, client] of _shards) {
|
|
2205
2328
|
client.close();
|
|
2206
2329
|
}
|
|
2207
2330
|
_shards.clear();
|
|
2331
|
+
_shardLastAccess.clear();
|
|
2208
2332
|
_shardingEnabled = false;
|
|
2209
2333
|
_encryptionKey = null;
|
|
2210
2334
|
}
|
|
2211
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2335
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2212
2336
|
var init_shard_manager = __esm({
|
|
2213
2337
|
"src/lib/shard-manager.ts"() {
|
|
2214
2338
|
"use strict";
|
|
2215
2339
|
init_config();
|
|
2216
2340
|
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2341
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2342
|
+
MAX_OPEN_SHARDS = 10;
|
|
2343
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2217
2344
|
_shards = /* @__PURE__ */ new Map();
|
|
2345
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2346
|
+
_evictionTimer = null;
|
|
2218
2347
|
_encryptionKey = null;
|
|
2219
2348
|
_shardingEnabled = false;
|
|
2220
2349
|
}
|
|
@@ -2407,13 +2536,50 @@ ${p.content}`).join("\n\n");
|
|
|
2407
2536
|
}
|
|
2408
2537
|
});
|
|
2409
2538
|
|
|
2539
|
+
// src/lib/daemon-auth.ts
|
|
2540
|
+
import crypto from "crypto";
|
|
2541
|
+
import path7 from "path";
|
|
2542
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2543
|
+
function normalizeToken(token) {
|
|
2544
|
+
if (!token) return null;
|
|
2545
|
+
const trimmed = token.trim();
|
|
2546
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2547
|
+
}
|
|
2548
|
+
function readDaemonToken() {
|
|
2549
|
+
try {
|
|
2550
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
2551
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
2552
|
+
} catch {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
function ensureDaemonToken(seed) {
|
|
2557
|
+
const existing = readDaemonToken();
|
|
2558
|
+
if (existing) return existing;
|
|
2559
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
2560
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
2561
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
2562
|
+
`, "utf8");
|
|
2563
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
2564
|
+
return token;
|
|
2565
|
+
}
|
|
2566
|
+
var DAEMON_TOKEN_PATH;
|
|
2567
|
+
var init_daemon_auth = __esm({
|
|
2568
|
+
"src/lib/daemon-auth.ts"() {
|
|
2569
|
+
"use strict";
|
|
2570
|
+
init_config();
|
|
2571
|
+
init_secure_files();
|
|
2572
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2410
2576
|
// src/lib/exe-daemon-client.ts
|
|
2411
2577
|
import net from "net";
|
|
2412
2578
|
import os5 from "os";
|
|
2413
2579
|
import { spawn } from "child_process";
|
|
2414
2580
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2415
|
-
import { existsSync as
|
|
2416
|
-
import
|
|
2581
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
2582
|
+
import path8 from "path";
|
|
2417
2583
|
import { fileURLToPath } from "url";
|
|
2418
2584
|
function handleData(chunk) {
|
|
2419
2585
|
_buffer += chunk.toString();
|
|
@@ -2441,9 +2607,9 @@ function handleData(chunk) {
|
|
|
2441
2607
|
}
|
|
2442
2608
|
}
|
|
2443
2609
|
function cleanupStaleFiles() {
|
|
2444
|
-
if (
|
|
2610
|
+
if (existsSync7(PID_PATH)) {
|
|
2445
2611
|
try {
|
|
2446
|
-
const pid = parseInt(
|
|
2612
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2447
2613
|
if (pid > 0) {
|
|
2448
2614
|
try {
|
|
2449
2615
|
process.kill(pid, 0);
|
|
@@ -2464,11 +2630,11 @@ function cleanupStaleFiles() {
|
|
|
2464
2630
|
}
|
|
2465
2631
|
}
|
|
2466
2632
|
function findPackageRoot() {
|
|
2467
|
-
let dir =
|
|
2468
|
-
const { root } =
|
|
2633
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
2634
|
+
const { root } = path8.parse(dir);
|
|
2469
2635
|
while (dir !== root) {
|
|
2470
|
-
if (
|
|
2471
|
-
dir =
|
|
2636
|
+
if (existsSync7(path8.join(dir, "package.json"))) return dir;
|
|
2637
|
+
dir = path8.dirname(dir);
|
|
2472
2638
|
}
|
|
2473
2639
|
return null;
|
|
2474
2640
|
}
|
|
@@ -2494,16 +2660,17 @@ function spawnDaemon() {
|
|
|
2494
2660
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2495
2661
|
return;
|
|
2496
2662
|
}
|
|
2497
|
-
const daemonPath =
|
|
2498
|
-
if (!
|
|
2663
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
2664
|
+
if (!existsSync7(daemonPath)) {
|
|
2499
2665
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2500
2666
|
`);
|
|
2501
2667
|
return;
|
|
2502
2668
|
}
|
|
2503
2669
|
const resolvedPath = daemonPath;
|
|
2670
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2504
2671
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2505
2672
|
`);
|
|
2506
|
-
const logPath =
|
|
2673
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
2507
2674
|
let stderrFd = "ignore";
|
|
2508
2675
|
try {
|
|
2509
2676
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2521,7 +2688,8 @@ function spawnDaemon() {
|
|
|
2521
2688
|
TMUX_PANE: void 0,
|
|
2522
2689
|
// Prevents resolveExeSession() from scoping to one session
|
|
2523
2690
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2524
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2691
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2692
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
2525
2693
|
}
|
|
2526
2694
|
});
|
|
2527
2695
|
child.unref();
|
|
@@ -2631,13 +2799,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2631
2799
|
return;
|
|
2632
2800
|
}
|
|
2633
2801
|
const id = randomUUID2();
|
|
2802
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2634
2803
|
const timer = setTimeout(() => {
|
|
2635
2804
|
_pending.delete(id);
|
|
2636
2805
|
resolve({ error: "Request timeout" });
|
|
2637
2806
|
}, timeoutMs);
|
|
2638
2807
|
_pending.set(id, { resolve, timer });
|
|
2639
2808
|
try {
|
|
2640
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2809
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2641
2810
|
} catch {
|
|
2642
2811
|
clearTimeout(timer);
|
|
2643
2812
|
_pending.delete(id);
|
|
@@ -2666,9 +2835,9 @@ function killAndRespawnDaemon() {
|
|
|
2666
2835
|
}
|
|
2667
2836
|
try {
|
|
2668
2837
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2669
|
-
if (
|
|
2838
|
+
if (existsSync7(PID_PATH)) {
|
|
2670
2839
|
try {
|
|
2671
|
-
const pid = parseInt(
|
|
2840
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
2672
2841
|
if (pid > 0) {
|
|
2673
2842
|
try {
|
|
2674
2843
|
process.kill(pid, "SIGKILL");
|
|
@@ -2785,17 +2954,19 @@ function disconnectClient() {
|
|
|
2785
2954
|
entry.resolve({ error: "Client disconnected" });
|
|
2786
2955
|
}
|
|
2787
2956
|
}
|
|
2788
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2957
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
2789
2958
|
var init_exe_daemon_client = __esm({
|
|
2790
2959
|
"src/lib/exe-daemon-client.ts"() {
|
|
2791
2960
|
"use strict";
|
|
2792
2961
|
init_config();
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2962
|
+
init_daemon_auth();
|
|
2963
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
2964
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
2965
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2796
2966
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2797
2967
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2798
2968
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2969
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2799
2970
|
_socket = null;
|
|
2800
2971
|
_connected = false;
|
|
2801
2972
|
_buffer = "";
|
|
@@ -2847,10 +3018,10 @@ async function disposeEmbedder() {
|
|
|
2847
3018
|
async function embedDirect(text) {
|
|
2848
3019
|
const llamaCpp = await import("node-llama-cpp");
|
|
2849
3020
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2850
|
-
const { existsSync:
|
|
2851
|
-
const
|
|
2852
|
-
const modelPath =
|
|
2853
|
-
if (!
|
|
3021
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
3022
|
+
const path12 = await import("path");
|
|
3023
|
+
const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
3024
|
+
if (!existsSync10(modelPath)) {
|
|
2854
3025
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2855
3026
|
}
|
|
2856
3027
|
const llama = await llamaCpp.getLlama();
|
|
@@ -2879,9 +3050,12 @@ var init_embedder = __esm({
|
|
|
2879
3050
|
});
|
|
2880
3051
|
|
|
2881
3052
|
// src/lib/license.ts
|
|
2882
|
-
import { readFileSync as
|
|
3053
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
2883
3054
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2884
|
-
import
|
|
3055
|
+
import { createRequire as createRequire2 } from "module";
|
|
3056
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3057
|
+
import os6 from "os";
|
|
3058
|
+
import path9 from "path";
|
|
2885
3059
|
import { jwtVerify, importSPKI } from "jose";
|
|
2886
3060
|
async function fetchRetry(url, init) {
|
|
2887
3061
|
try {
|
|
@@ -2892,37 +3066,37 @@ async function fetchRetry(url, init) {
|
|
|
2892
3066
|
}
|
|
2893
3067
|
}
|
|
2894
3068
|
function loadDeviceId() {
|
|
2895
|
-
const deviceJsonPath =
|
|
3069
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
2896
3070
|
try {
|
|
2897
|
-
if (
|
|
2898
|
-
const data = JSON.parse(
|
|
3071
|
+
if (existsSync8(deviceJsonPath)) {
|
|
3072
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2899
3073
|
if (data.deviceId) return data.deviceId;
|
|
2900
3074
|
}
|
|
2901
3075
|
} catch {
|
|
2902
3076
|
}
|
|
2903
3077
|
try {
|
|
2904
|
-
if (
|
|
2905
|
-
const id2 =
|
|
3078
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
3079
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2906
3080
|
if (id2) return id2;
|
|
2907
3081
|
}
|
|
2908
3082
|
} catch {
|
|
2909
3083
|
}
|
|
2910
3084
|
const id = randomUUID3();
|
|
2911
|
-
|
|
2912
|
-
|
|
3085
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3086
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2913
3087
|
return id;
|
|
2914
3088
|
}
|
|
2915
3089
|
function loadLicense() {
|
|
2916
3090
|
try {
|
|
2917
|
-
if (!
|
|
2918
|
-
return
|
|
3091
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
3092
|
+
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
2919
3093
|
} catch {
|
|
2920
3094
|
return null;
|
|
2921
3095
|
}
|
|
2922
3096
|
}
|
|
2923
3097
|
function saveLicense(apiKey) {
|
|
2924
|
-
|
|
2925
|
-
|
|
3098
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3099
|
+
writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
2926
3100
|
}
|
|
2927
3101
|
async function verifyLicenseJwt(token) {
|
|
2928
3102
|
try {
|
|
@@ -2948,8 +3122,8 @@ async function verifyLicenseJwt(token) {
|
|
|
2948
3122
|
}
|
|
2949
3123
|
async function getCachedLicense() {
|
|
2950
3124
|
try {
|
|
2951
|
-
if (!
|
|
2952
|
-
const raw = JSON.parse(
|
|
3125
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3126
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2953
3127
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2954
3128
|
return await verifyLicenseJwt(raw.token);
|
|
2955
3129
|
} catch {
|
|
@@ -2958,8 +3132,8 @@ async function getCachedLicense() {
|
|
|
2958
3132
|
}
|
|
2959
3133
|
function readCachedToken() {
|
|
2960
3134
|
try {
|
|
2961
|
-
if (!
|
|
2962
|
-
const raw = JSON.parse(
|
|
3135
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3136
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
2963
3137
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2964
3138
|
} catch {
|
|
2965
3139
|
return null;
|
|
@@ -2993,56 +3167,130 @@ function getRawCachedPlan() {
|
|
|
2993
3167
|
}
|
|
2994
3168
|
function cacheResponse(token) {
|
|
2995
3169
|
try {
|
|
2996
|
-
|
|
3170
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
2997
3171
|
} catch {
|
|
2998
3172
|
}
|
|
2999
3173
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3174
|
+
function loadPrismaForLicense() {
|
|
3175
|
+
if (_prismaFailed) return null;
|
|
3176
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
3177
|
+
if (!dbUrl) {
|
|
3178
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
3179
|
+
if (!existsSync8(path9.join(exeDbRoot, "package.json"))) {
|
|
3180
|
+
_prismaFailed = true;
|
|
3181
|
+
return null;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
if (!_prismaPromise) {
|
|
3185
|
+
_prismaPromise = (async () => {
|
|
3186
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3187
|
+
if (explicitPath) {
|
|
3188
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3189
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3190
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3191
|
+
return new Ctor2();
|
|
3192
|
+
}
|
|
3193
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
3194
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
3195
|
+
const entry = req.resolve("@prisma/client");
|
|
3196
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3197
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3198
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3199
|
+
return new Ctor();
|
|
3200
|
+
})().catch((err) => {
|
|
3201
|
+
_prismaFailed = true;
|
|
3202
|
+
_prismaPromise = null;
|
|
3203
|
+
throw err;
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
return _prismaPromise;
|
|
3207
|
+
}
|
|
3208
|
+
async function validateViaPostgres(apiKey) {
|
|
3209
|
+
const loader = loadPrismaForLicense();
|
|
3210
|
+
if (!loader) return null;
|
|
3211
|
+
try {
|
|
3212
|
+
const prisma = await loader;
|
|
3213
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
3214
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
3215
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
3216
|
+
apiKey
|
|
3217
|
+
);
|
|
3218
|
+
if (!rows || rows.length === 0) return null;
|
|
3219
|
+
const row = rows[0];
|
|
3220
|
+
if (row.status !== "active") return null;
|
|
3221
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
3222
|
+
const plan = row.plan;
|
|
3223
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3224
|
+
return {
|
|
3225
|
+
valid: true,
|
|
3226
|
+
plan,
|
|
3227
|
+
email: row.email,
|
|
3228
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
3229
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
3230
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
3231
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
3232
|
+
};
|
|
3233
|
+
} catch {
|
|
3234
|
+
return null;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
3002
3238
|
try {
|
|
3003
3239
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3004
3240
|
method: "POST",
|
|
3005
3241
|
headers: { "Content-Type": "application/json" },
|
|
3006
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
3242
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3007
3243
|
signal: AbortSignal.timeout(1e4)
|
|
3008
3244
|
});
|
|
3009
|
-
if (res.ok)
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3245
|
+
if (!res.ok) return null;
|
|
3246
|
+
const data = await res.json();
|
|
3247
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
3248
|
+
if (!data.valid) return null;
|
|
3249
|
+
if (data.token) {
|
|
3250
|
+
cacheResponse(data.token);
|
|
3251
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
3252
|
+
if (verified) return verified;
|
|
3253
|
+
}
|
|
3254
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3255
|
+
return {
|
|
3256
|
+
valid: data.valid,
|
|
3257
|
+
plan: data.plan,
|
|
3258
|
+
email: data.email,
|
|
3259
|
+
expiresAt: data.expiresAt,
|
|
3260
|
+
deviceLimit: limits.devices,
|
|
3261
|
+
employeeLimit: limits.employees,
|
|
3262
|
+
memoryLimit: limits.memories
|
|
3263
|
+
};
|
|
3264
|
+
} catch {
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
async function validateLicense(apiKey, deviceId) {
|
|
3269
|
+
const did = deviceId ?? loadDeviceId();
|
|
3270
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
3271
|
+
if (pgResult) {
|
|
3272
|
+
try {
|
|
3273
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
return pgResult;
|
|
3277
|
+
}
|
|
3278
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
3279
|
+
if (cfResult) return cfResult;
|
|
3280
|
+
const cached = await getCachedLicense();
|
|
3281
|
+
if (cached) return cached;
|
|
3282
|
+
try {
|
|
3283
|
+
if (existsSync8(CACHE_PATH)) {
|
|
3284
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
3285
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
3286
|
+
return raw.pgLicense;
|
|
3022
3287
|
}
|
|
3023
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3024
|
-
return {
|
|
3025
|
-
valid: data.valid,
|
|
3026
|
-
plan: data.plan,
|
|
3027
|
-
email: data.email,
|
|
3028
|
-
expiresAt: data.expiresAt,
|
|
3029
|
-
deviceLimit: limits.devices,
|
|
3030
|
-
employeeLimit: limits.employees,
|
|
3031
|
-
memoryLimit: limits.memories
|
|
3032
|
-
};
|
|
3033
3288
|
}
|
|
3034
|
-
const cached = await getCachedLicense();
|
|
3035
|
-
if (cached) return cached;
|
|
3036
|
-
const raw = getRawCachedPlan();
|
|
3037
|
-
if (raw) return raw;
|
|
3038
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3039
3289
|
} catch {
|
|
3040
|
-
const cached = await getCachedLicense();
|
|
3041
|
-
if (cached) return cached;
|
|
3042
|
-
const rawFallback = getRawCachedPlan();
|
|
3043
|
-
if (rawFallback) return rawFallback;
|
|
3044
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
3045
3290
|
}
|
|
3291
|
+
const rawFallback = getRawCachedPlan();
|
|
3292
|
+
if (rawFallback) return rawFallback;
|
|
3293
|
+
return { ...FREE_LICENSE, valid: false };
|
|
3046
3294
|
}
|
|
3047
3295
|
function getCacheAgeMs() {
|
|
3048
3296
|
try {
|
|
@@ -3057,9 +3305,9 @@ async function checkLicense() {
|
|
|
3057
3305
|
let key = loadLicense();
|
|
3058
3306
|
if (!key) {
|
|
3059
3307
|
try {
|
|
3060
|
-
const configPath =
|
|
3061
|
-
if (
|
|
3062
|
-
const raw = JSON.parse(
|
|
3308
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
3309
|
+
if (existsSync8(configPath)) {
|
|
3310
|
+
const raw = JSON.parse(readFileSync5(configPath, "utf8"));
|
|
3063
3311
|
const cloud = raw.cloud;
|
|
3064
3312
|
if (cloud?.apiKey) {
|
|
3065
3313
|
key = cloud.apiKey;
|
|
@@ -3085,14 +3333,14 @@ function isFeatureAllowed(license, feature) {
|
|
|
3085
3333
|
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
3086
3334
|
}
|
|
3087
3335
|
}
|
|
3088
|
-
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;
|
|
3336
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS;
|
|
3089
3337
|
var init_license = __esm({
|
|
3090
3338
|
"src/lib/license.ts"() {
|
|
3091
3339
|
"use strict";
|
|
3092
3340
|
init_config();
|
|
3093
|
-
LICENSE_PATH =
|
|
3094
|
-
CACHE_PATH =
|
|
3095
|
-
DEVICE_ID_PATH =
|
|
3341
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3342
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3343
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
3096
3344
|
API_BASE = "https://askexe.com/cloud";
|
|
3097
3345
|
RETRY_DELAY_MS = 500;
|
|
3098
3346
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -3116,6 +3364,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
3116
3364
|
employeeLimit: 1,
|
|
3117
3365
|
memoryLimit: 5e3
|
|
3118
3366
|
};
|
|
3367
|
+
_prismaPromise = null;
|
|
3368
|
+
_prismaFailed = false;
|
|
3119
3369
|
CACHE_MAX_AGE_MS = 36e5;
|
|
3120
3370
|
}
|
|
3121
3371
|
});
|
|
@@ -3131,12 +3381,12 @@ __export(plan_limits_exports, {
|
|
|
3131
3381
|
countActiveMemories: () => countActiveMemories,
|
|
3132
3382
|
getLicenseSync: () => getLicenseSync
|
|
3133
3383
|
});
|
|
3134
|
-
import { readFileSync as
|
|
3135
|
-
import
|
|
3384
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
3385
|
+
import path10 from "path";
|
|
3136
3386
|
function getLicenseSync() {
|
|
3137
3387
|
try {
|
|
3138
|
-
if (!
|
|
3139
|
-
const raw = JSON.parse(
|
|
3388
|
+
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
3389
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
3140
3390
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3141
3391
|
const parts = raw.token.split(".");
|
|
3142
3392
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3203,8 +3453,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3203
3453
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3204
3454
|
let count = 0;
|
|
3205
3455
|
try {
|
|
3206
|
-
if (
|
|
3207
|
-
const raw =
|
|
3456
|
+
if (existsSync9(filePath)) {
|
|
3457
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
3208
3458
|
const employees = JSON.parse(raw);
|
|
3209
3459
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3210
3460
|
}
|
|
@@ -3241,14 +3491,14 @@ var init_plan_limits = __esm({
|
|
|
3241
3491
|
this.name = "PlanLimitError";
|
|
3242
3492
|
}
|
|
3243
3493
|
};
|
|
3244
|
-
CACHE_PATH2 =
|
|
3494
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3245
3495
|
}
|
|
3246
3496
|
});
|
|
3247
3497
|
|
|
3248
3498
|
// src/adapters/claude/hooks/response-ingest-worker.ts
|
|
3249
|
-
import
|
|
3250
|
-
import { writeFileSync as
|
|
3251
|
-
import
|
|
3499
|
+
import crypto2 from "crypto";
|
|
3500
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
3501
|
+
import path11 from "path";
|
|
3252
3502
|
|
|
3253
3503
|
// src/lib/project-name.ts
|
|
3254
3504
|
import { execSync } from "child_process";
|
|
@@ -3293,7 +3543,7 @@ import { createHash } from "crypto";
|
|
|
3293
3543
|
|
|
3294
3544
|
// src/lib/keychain.ts
|
|
3295
3545
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3296
|
-
import { existsSync as
|
|
3546
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3297
3547
|
import path5 from "path";
|
|
3298
3548
|
import os4 from "os";
|
|
3299
3549
|
var SERVICE = "exe-mem";
|
|
@@ -3323,7 +3573,7 @@ async function getMasterKey() {
|
|
|
3323
3573
|
}
|
|
3324
3574
|
}
|
|
3325
3575
|
const keyPath = getKeyPath();
|
|
3326
|
-
if (!
|
|
3576
|
+
if (!existsSync4(keyPath)) {
|
|
3327
3577
|
process.stderr.write(
|
|
3328
3578
|
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
3329
3579
|
`
|
|
@@ -3782,7 +4032,7 @@ async function main() {
|
|
|
3782
4032
|
process.exit(0);
|
|
3783
4033
|
}
|
|
3784
4034
|
await writeMemory({
|
|
3785
|
-
id:
|
|
4035
|
+
id: crypto2.randomUUID(),
|
|
3786
4036
|
agent_id: agentId,
|
|
3787
4037
|
agent_role: agentRole,
|
|
3788
4038
|
session_id: sessionId,
|
|
@@ -3798,8 +4048,8 @@ async function main() {
|
|
|
3798
4048
|
if (needsBackfill) {
|
|
3799
4049
|
try {
|
|
3800
4050
|
const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3801
|
-
const flagPath =
|
|
3802
|
-
|
|
4051
|
+
const flagPath = path11.join(exeDir, "session-cache", "needs-backfill");
|
|
4052
|
+
writeFileSync4(flagPath, "1");
|
|
3803
4053
|
} catch (err) {
|
|
3804
4054
|
process.stderr.write(`[response-ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
|
|
3805
4055
|
`);
|