@askexenow/exe-os 0.8.38 → 0.8.39
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 +17 -8
- package/dist/bin/backfill-conversations.js +46 -10
- package/dist/bin/backfill-responses.js +46 -10
- package/dist/bin/backfill-vectors.js +42 -8
- package/dist/bin/cleanup-stale-review-tasks.js +37 -8
- package/dist/bin/cli.js +281 -154
- package/dist/bin/exe-agent.js +19 -4
- package/dist/bin/exe-assign.js +39 -5
- package/dist/bin/exe-boot.js +237 -111
- package/dist/bin/exe-call.js +11 -6
- package/dist/bin/exe-cloud.js +99 -28
- package/dist/bin/exe-dispatch.js +1 -1
- package/dist/bin/exe-doctor.js +37 -8
- package/dist/bin/exe-export-behaviors.js +39 -10
- package/dist/bin/exe-forget.js +38 -9
- package/dist/bin/exe-gateway.js +109 -42
- package/dist/bin/exe-heartbeat.js +49 -20
- package/dist/bin/exe-kill.js +39 -10
- package/dist/bin/exe-launch-agent.js +58 -22
- package/dist/bin/exe-link.js +184 -85
- package/dist/bin/exe-new-employee.js +21 -7
- package/dist/bin/exe-pending-messages.js +46 -17
- package/dist/bin/exe-pending-notifications.js +37 -8
- package/dist/bin/exe-pending-reviews.js +47 -18
- package/dist/bin/exe-rename.js +21 -7
- package/dist/bin/exe-review.js +34 -5
- package/dist/bin/exe-search.js +47 -10
- package/dist/bin/exe-session-cleanup.js +56 -19
- package/dist/bin/exe-settings.js +63 -2
- package/dist/bin/exe-status.js +34 -5
- package/dist/bin/exe-team.js +34 -5
- package/dist/bin/git-sweep.js +38 -9
- package/dist/bin/graph-backfill.js +37 -8
- package/dist/bin/graph-export.js +37 -8
- package/dist/bin/install.js +1 -1
- package/dist/bin/scan-tasks.js +40 -11
- package/dist/bin/setup.js +58 -24
- package/dist/bin/shard-migrate.js +37 -8
- package/dist/bin/wiki-sync.js +39 -9
- package/dist/gateway/index.js +102 -37
- package/dist/hooks/bug-report-worker.js +62 -28
- package/dist/hooks/commit-complete.js +38 -9
- package/dist/hooks/error-recall.js +49 -8
- package/dist/hooks/exe-heartbeat-hook.js +3 -2
- package/dist/hooks/ingest-worker.js +151 -37
- package/dist/hooks/ingest.js +74 -28
- package/dist/hooks/instructions-loaded.js +39 -9
- package/dist/hooks/notification.js +37 -7
- package/dist/hooks/post-compact.js +37 -7
- package/dist/hooks/pre-compact.js +35 -6
- package/dist/hooks/pre-tool-use.js +52 -14
- package/dist/hooks/prompt-ingest-worker.js +56 -10
- package/dist/hooks/prompt-submit.js +61 -23
- package/dist/hooks/response-ingest-worker.js +57 -11
- package/dist/hooks/session-end.js +43 -10
- package/dist/hooks/session-start.js +46 -8
- package/dist/hooks/stop.js +37 -7
- package/dist/hooks/subagent-stop.js +37 -7
- package/dist/hooks/summary-worker.js +317 -99
- package/dist/index.js +87 -22
- package/dist/lib/cloud-sync.js +172 -78
- package/dist/lib/config.js +4 -1
- package/dist/lib/consolidation.js +5 -4
- package/dist/lib/database.js +1 -0
- package/dist/lib/device-registry.js +2 -1
- package/dist/lib/embedder.js +9 -1
- package/dist/lib/employees.js +11 -6
- package/dist/lib/exe-daemon-client.js +6 -1
- package/dist/lib/exe-daemon.js +71 -28
- package/dist/lib/hybrid-search.js +47 -10
- package/dist/lib/identity.js +1 -1
- package/dist/lib/keychain.js +2 -1
- package/dist/lib/license.js +13 -4
- package/dist/lib/messaging.js +1 -1
- package/dist/lib/reminders.js +2 -2
- package/dist/lib/schedules.js +37 -8
- package/dist/lib/skill-learning.js +1 -1
- package/dist/lib/store.js +37 -8
- package/dist/lib/tasks.js +1 -1
- package/dist/lib/tmux-routing.js +1 -1
- package/dist/mcp/server.js +97 -43
- package/dist/mcp/tools/complete-reminder.js +1 -1
- package/dist/mcp/tools/create-task.js +14 -6
- package/dist/mcp/tools/deactivate-behavior.js +2 -2
- package/dist/mcp/tools/list-reminders.js +1 -1
- package/dist/mcp/tools/list-tasks.js +1 -1
- package/dist/mcp/tools/send-message.js +1 -1
- package/dist/mcp/tools/update-task.js +1 -1
- package/dist/runtime/index.js +35 -6
- package/dist/tui/App.js +177 -95
- package/package.json +3 -3
package/dist/bin/exe-link.js
CHANGED
|
@@ -31,15 +31,15 @@ __export(config_exports, {
|
|
|
31
31
|
migrateConfig: () => migrateConfig,
|
|
32
32
|
saveConfig: () => saveConfig
|
|
33
33
|
});
|
|
34
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
34
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
35
35
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
36
36
|
import path2 from "path";
|
|
37
|
-
import
|
|
37
|
+
import os2 from "os";
|
|
38
38
|
function resolveDataDir() {
|
|
39
39
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
40
40
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
41
|
-
const newDir = path2.join(
|
|
42
|
-
const legacyDir = path2.join(
|
|
41
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
42
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
43
43
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
44
44
|
try {
|
|
45
45
|
renameSync(legacyDir, newDir);
|
|
@@ -126,7 +126,7 @@ async function loadConfig() {
|
|
|
126
126
|
normalizeAutoUpdate(migratedCfg);
|
|
127
127
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
128
128
|
if (config.dbPath.startsWith("~")) {
|
|
129
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
129
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
130
130
|
}
|
|
131
131
|
return config;
|
|
132
132
|
} catch {
|
|
@@ -157,6 +157,9 @@ async function saveConfig(config) {
|
|
|
157
157
|
await mkdir2(dir, { recursive: true });
|
|
158
158
|
const configPath = path2.join(dir, "config.json");
|
|
159
159
|
await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
160
|
+
if (config.cloud?.apiKey) {
|
|
161
|
+
await chmod2(configPath, 384);
|
|
162
|
+
}
|
|
160
163
|
}
|
|
161
164
|
async function loadConfigFrom(configPath) {
|
|
162
165
|
const raw = await readFile2(configPath, "utf-8");
|
|
@@ -381,15 +384,20 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
381
384
|
await mkdir3(path3.dirname(employeesPath), { recursive: true });
|
|
382
385
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
383
386
|
}
|
|
387
|
+
function findExeBin() {
|
|
388
|
+
try {
|
|
389
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
390
|
+
} catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
384
394
|
function registerBinSymlinks(name) {
|
|
385
395
|
const created = [];
|
|
386
396
|
const skipped = [];
|
|
387
397
|
const errors = [];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
} catch {
|
|
392
|
-
errors.push("Could not find 'exe' in PATH");
|
|
398
|
+
const exeBinPath = findExeBin();
|
|
399
|
+
if (!exeBinPath) {
|
|
400
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
393
401
|
return { created, skipped, errors };
|
|
394
402
|
}
|
|
395
403
|
const binDir = path3.dirname(exeBinPath);
|
|
@@ -430,6 +438,14 @@ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4
|
|
|
430
438
|
import { randomUUID } from "crypto";
|
|
431
439
|
import path4 from "path";
|
|
432
440
|
import { jwtVerify, importSPKI } from "jose";
|
|
441
|
+
async function fetchRetry(url, init) {
|
|
442
|
+
try {
|
|
443
|
+
return await fetch(url, init);
|
|
444
|
+
} catch {
|
|
445
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
446
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
433
449
|
function loadDeviceId() {
|
|
434
450
|
const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
|
|
435
451
|
try {
|
|
@@ -500,7 +516,7 @@ function cacheResponse(token) {
|
|
|
500
516
|
async function validateLicense(apiKey, deviceId) {
|
|
501
517
|
const did = deviceId ?? loadDeviceId();
|
|
502
518
|
try {
|
|
503
|
-
const res = await
|
|
519
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
504
520
|
method: "POST",
|
|
505
521
|
headers: { "Content-Type": "application/json" },
|
|
506
522
|
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
@@ -565,7 +581,7 @@ function isFeatureAllowed(license, feature) {
|
|
|
565
581
|
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
566
582
|
}
|
|
567
583
|
}
|
|
568
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
584
|
+
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;
|
|
569
585
|
var init_license = __esm({
|
|
570
586
|
"src/lib/license.ts"() {
|
|
571
587
|
"use strict";
|
|
@@ -574,6 +590,7 @@ var init_license = __esm({
|
|
|
574
590
|
CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
575
591
|
DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
|
|
576
592
|
API_BASE = "https://askexe.com/cloud";
|
|
593
|
+
RETRY_DELAY_MS = 500;
|
|
577
594
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
578
595
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
579
596
|
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
@@ -631,6 +648,7 @@ var init_plan_limits = __esm({
|
|
|
631
648
|
// src/lib/cloud-sync.ts
|
|
632
649
|
var cloud_sync_exports = {};
|
|
633
650
|
__export(cloud_sync_exports, {
|
|
651
|
+
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
634
652
|
buildRosterBlob: () => buildRosterBlob,
|
|
635
653
|
cloudPull: () => cloudPull,
|
|
636
654
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
@@ -650,9 +668,10 @@ __export(cloud_sync_exports, {
|
|
|
650
668
|
cloudPushTasks: () => cloudPushTasks,
|
|
651
669
|
cloudSync: () => cloudSync,
|
|
652
670
|
mergeConfig: () => mergeConfig,
|
|
653
|
-
mergeRosterFromRemote: () => mergeRosterFromRemote
|
|
671
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
672
|
+
recordRosterDeletion: () => recordRosterDeletion
|
|
654
673
|
});
|
|
655
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
674
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
|
|
656
675
|
import path6 from "path";
|
|
657
676
|
import { homedir } from "os";
|
|
658
677
|
function logError(msg) {
|
|
@@ -663,16 +682,47 @@ function logError(msg) {
|
|
|
663
682
|
} catch {
|
|
664
683
|
}
|
|
665
684
|
}
|
|
685
|
+
async function withRosterLock(fn) {
|
|
686
|
+
if (existsSync6(ROSTER_LOCK_PATH)) {
|
|
687
|
+
try {
|
|
688
|
+
const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
689
|
+
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
690
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
691
|
+
}
|
|
692
|
+
} catch (err) {
|
|
693
|
+
if (err instanceof Error && err.message.includes("already in progress")) throw err;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
|
|
697
|
+
try {
|
|
698
|
+
return await fn();
|
|
699
|
+
} finally {
|
|
700
|
+
try {
|
|
701
|
+
unlinkSync(ROSTER_LOCK_PATH);
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
666
706
|
async function fetchWithRetry(url, init) {
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
707
|
+
const MAX_RETRIES = 3;
|
|
708
|
+
const BASE_DELAY_MS = 200;
|
|
709
|
+
let lastError;
|
|
710
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
711
|
+
try {
|
|
712
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
713
|
+
const resp = await fetch(url, { ...init, signal });
|
|
714
|
+
if (resp.status >= 500 && attempt < MAX_RETRIES) {
|
|
715
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
return resp;
|
|
719
|
+
} catch (err) {
|
|
720
|
+
lastError = err;
|
|
721
|
+
if (attempt === MAX_RETRIES) throw err;
|
|
722
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
|
|
723
|
+
}
|
|
674
724
|
}
|
|
675
|
-
|
|
725
|
+
throw lastError;
|
|
676
726
|
}
|
|
677
727
|
function assertSecureEndpoint(endpoint) {
|
|
678
728
|
if (endpoint.startsWith("https://")) return;
|
|
@@ -700,10 +750,15 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
700
750
|
headers: {
|
|
701
751
|
Authorization: `Bearer ${config.apiKey}`,
|
|
702
752
|
"Content-Type": "application/json",
|
|
703
|
-
"X-Device-Id": loadDeviceId()
|
|
753
|
+
"X-Device-Id": loadDeviceId(),
|
|
754
|
+
"X-Expected-Version": String(maxVersion)
|
|
704
755
|
},
|
|
705
756
|
body: JSON.stringify({ version: maxVersion, blob })
|
|
706
757
|
});
|
|
758
|
+
if (resp.status === 409) {
|
|
759
|
+
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
707
762
|
return resp.ok;
|
|
708
763
|
} catch (err) {
|
|
709
764
|
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -790,18 +845,21 @@ async function cloudSync(config) {
|
|
|
790
845
|
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
791
846
|
);
|
|
792
847
|
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
793
|
-
const recordsResult = await client.execute({
|
|
794
|
-
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
795
|
-
tool_name, project_name, has_error, raw_text, version,
|
|
796
|
-
author_device_id, scope
|
|
797
|
-
FROM memories
|
|
798
|
-
WHERE version > ?
|
|
799
|
-
AND (scope IS NULL OR scope != 'personal')
|
|
800
|
-
ORDER BY version ASC`,
|
|
801
|
-
args: [lastPushVersion]
|
|
802
|
-
});
|
|
803
848
|
let pushed = 0;
|
|
804
|
-
|
|
849
|
+
let batchCursor = lastPushVersion;
|
|
850
|
+
while (true) {
|
|
851
|
+
const recordsResult = await client.execute({
|
|
852
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
853
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
854
|
+
author_device_id, scope
|
|
855
|
+
FROM memories
|
|
856
|
+
WHERE version > ?
|
|
857
|
+
AND (scope IS NULL OR scope != 'personal')
|
|
858
|
+
ORDER BY version ASC
|
|
859
|
+
LIMIT ?`,
|
|
860
|
+
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
861
|
+
});
|
|
862
|
+
if (recordsResult.rows.length === 0) break;
|
|
805
863
|
const records = recordsResult.rows.map((row) => ({
|
|
806
864
|
id: row.id,
|
|
807
865
|
agent_id: row.agent_id,
|
|
@@ -818,13 +876,14 @@ async function cloudSync(config) {
|
|
|
818
876
|
}));
|
|
819
877
|
const maxVersion = Number(records[records.length - 1].version);
|
|
820
878
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
821
|
-
if (pushOk)
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
879
|
+
if (!pushOk) break;
|
|
880
|
+
await client.execute({
|
|
881
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
882
|
+
args: [String(maxVersion)]
|
|
883
|
+
});
|
|
884
|
+
pushed += records.length;
|
|
885
|
+
batchCursor = maxVersion;
|
|
886
|
+
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
828
887
|
}
|
|
829
888
|
try {
|
|
830
889
|
await cloudPushRoster(config);
|
|
@@ -906,6 +965,27 @@ async function cloudSync(config) {
|
|
|
906
965
|
documents: documentsResult
|
|
907
966
|
};
|
|
908
967
|
}
|
|
968
|
+
function recordRosterDeletion(name) {
|
|
969
|
+
let deletions = [];
|
|
970
|
+
try {
|
|
971
|
+
if (existsSync6(ROSTER_DELETIONS_PATH)) {
|
|
972
|
+
deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
973
|
+
}
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
if (!deletions.includes(name)) deletions.push(name);
|
|
977
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
978
|
+
}
|
|
979
|
+
function consumeRosterDeletions() {
|
|
980
|
+
try {
|
|
981
|
+
if (!existsSync6(ROSTER_DELETIONS_PATH)) return [];
|
|
982
|
+
const deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
983
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
|
|
984
|
+
return deletions;
|
|
985
|
+
} catch {
|
|
986
|
+
return [];
|
|
987
|
+
}
|
|
988
|
+
}
|
|
909
989
|
function buildRosterBlob(paths) {
|
|
910
990
|
const rosterPath = paths?.rosterPath ?? path6.join(EXE_AI_DIR, "exe-employees.json");
|
|
911
991
|
const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
|
|
@@ -933,9 +1013,10 @@ function buildRosterBlob(paths) {
|
|
|
933
1013
|
} catch {
|
|
934
1014
|
}
|
|
935
1015
|
}
|
|
936
|
-
const
|
|
1016
|
+
const deletedNames = consumeRosterDeletions();
|
|
1017
|
+
const content = JSON.stringify({ roster, identities, config, deletedNames });
|
|
937
1018
|
const hash = Buffer.from(content).length;
|
|
938
|
-
return { roster, identities, config, version: hash };
|
|
1019
|
+
return { roster, identities, config, deletedNames, version: hash };
|
|
939
1020
|
}
|
|
940
1021
|
async function cloudPushRoster(config) {
|
|
941
1022
|
assertSecureEndpoint(config.endpoint);
|
|
@@ -1018,38 +1099,50 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
1018
1099
|
writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
1019
1100
|
}
|
|
1020
1101
|
async function mergeRosterFromRemote(remote, paths) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if (
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1102
|
+
return withRosterLock(async () => {
|
|
1103
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
1104
|
+
const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
|
|
1105
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
1106
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
1107
|
+
let added = 0;
|
|
1108
|
+
for (const remoteEmp of remote.roster) {
|
|
1109
|
+
if (localNames.has(remoteEmp.name)) continue;
|
|
1110
|
+
localEmployees.push(remoteEmp);
|
|
1111
|
+
localNames.add(remoteEmp.name);
|
|
1112
|
+
added++;
|
|
1113
|
+
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
1114
|
+
if (!existsSync6(identityDir)) mkdirSync2(identityDir, { recursive: true });
|
|
1115
|
+
const idPath = path6.join(identityDir, `${remoteEmp.name}.md`);
|
|
1116
|
+
if (!existsSync6(idPath)) {
|
|
1117
|
+
writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
try {
|
|
1121
|
+
registerBinSymlinks(remoteEmp.name);
|
|
1122
|
+
} catch {
|
|
1036
1123
|
}
|
|
1037
1124
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1125
|
+
let removed = 0;
|
|
1126
|
+
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
1127
|
+
const toRemove = new Set(remote.deletedNames);
|
|
1128
|
+
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
1129
|
+
removed = localEmployees.length - filtered.length;
|
|
1130
|
+
if (removed > 0) {
|
|
1131
|
+
localEmployees.length = 0;
|
|
1132
|
+
localEmployees.push(...filtered);
|
|
1133
|
+
}
|
|
1041
1134
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
await saveEmployees(localEmployees, rosterPath);
|
|
1045
|
-
}
|
|
1046
|
-
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
1047
|
-
try {
|
|
1048
|
-
mergeConfig(remote.config, paths?.configPath);
|
|
1049
|
-
} catch {
|
|
1135
|
+
if (added > 0 || removed > 0) {
|
|
1136
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
1050
1137
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1138
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
1139
|
+
try {
|
|
1140
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return { added };
|
|
1145
|
+
});
|
|
1053
1146
|
}
|
|
1054
1147
|
async function cloudPushBlob(route, data, metaKey, config) {
|
|
1055
1148
|
if (data.length === 0) return { ok: true };
|
|
@@ -1117,7 +1210,7 @@ async function cloudPullBlob(route, config) {
|
|
|
1117
1210
|
}
|
|
1118
1211
|
async function cloudPushBehaviors(config) {
|
|
1119
1212
|
const client = getClient();
|
|
1120
|
-
const result = await client.execute("SELECT * FROM behaviors");
|
|
1213
|
+
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
1121
1214
|
const rows = result.rows;
|
|
1122
1215
|
const { ok } = await cloudPushBlob(
|
|
1123
1216
|
"/sync/push-behaviors",
|
|
@@ -1165,13 +1258,13 @@ async function cloudPullBehaviors(config) {
|
|
|
1165
1258
|
async function cloudPushGraphRAG(config) {
|
|
1166
1259
|
const client = getClient();
|
|
1167
1260
|
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
1168
|
-
client.execute("SELECT * FROM entities"),
|
|
1169
|
-
client.execute("SELECT * FROM relationships"),
|
|
1170
|
-
client.execute("SELECT * FROM entity_aliases"),
|
|
1171
|
-
client.execute("SELECT * FROM entity_memories"),
|
|
1172
|
-
client.execute("SELECT * FROM relationship_memories"),
|
|
1173
|
-
client.execute("SELECT * FROM hyperedges"),
|
|
1174
|
-
client.execute("SELECT * FROM hyperedge_nodes")
|
|
1261
|
+
client.execute("SELECT * FROM entities LIMIT 50000"),
|
|
1262
|
+
client.execute("SELECT * FROM relationships LIMIT 50000"),
|
|
1263
|
+
client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
|
|
1264
|
+
client.execute("SELECT * FROM entity_memories LIMIT 50000"),
|
|
1265
|
+
client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
|
|
1266
|
+
client.execute("SELECT * FROM hyperedges LIMIT 50000"),
|
|
1267
|
+
client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
|
|
1175
1268
|
]);
|
|
1176
1269
|
const blob = {
|
|
1177
1270
|
entities: entities.rows,
|
|
@@ -1273,7 +1366,7 @@ async function cloudPullGraphRAG(config) {
|
|
|
1273
1366
|
}
|
|
1274
1367
|
async function cloudPushTasks(config) {
|
|
1275
1368
|
const client = getClient();
|
|
1276
|
-
const result = await client.execute("SELECT * FROM tasks");
|
|
1369
|
+
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
1277
1370
|
const rows = result.rows;
|
|
1278
1371
|
const { ok } = await cloudPushBlob(
|
|
1279
1372
|
"/sync/push-tasks",
|
|
@@ -1319,7 +1412,7 @@ async function cloudPullTasks(config) {
|
|
|
1319
1412
|
}
|
|
1320
1413
|
async function cloudPushConversations(config) {
|
|
1321
1414
|
const client = getClient();
|
|
1322
|
-
const result = await client.execute("SELECT * FROM conversations");
|
|
1415
|
+
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
1323
1416
|
const rows = result.rows;
|
|
1324
1417
|
const { ok } = await cloudPushBlob(
|
|
1325
1418
|
"/sync/push-conversations",
|
|
@@ -1369,8 +1462,8 @@ async function cloudPullConversations(config) {
|
|
|
1369
1462
|
async function cloudPushDocuments(config) {
|
|
1370
1463
|
const client = getClient();
|
|
1371
1464
|
const [workspaces, documents] = await Promise.all([
|
|
1372
|
-
client.execute("SELECT * FROM workspaces"),
|
|
1373
|
-
client.execute("SELECT * FROM documents")
|
|
1465
|
+
client.execute("SELECT * FROM workspaces LIMIT 1000"),
|
|
1466
|
+
client.execute("SELECT * FROM documents LIMIT 10000")
|
|
1374
1467
|
]);
|
|
1375
1468
|
const blob = {
|
|
1376
1469
|
workspaces: workspaces.rows,
|
|
@@ -1423,7 +1516,7 @@ async function cloudPullDocuments(config) {
|
|
|
1423
1516
|
}
|
|
1424
1517
|
return { pulled };
|
|
1425
1518
|
}
|
|
1426
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
|
|
1519
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
1427
1520
|
var init_cloud_sync = __esm({
|
|
1428
1521
|
"src/lib/cloud-sync.ts"() {
|
|
1429
1522
|
"use strict";
|
|
@@ -1436,6 +1529,10 @@ var init_cloud_sync = __esm({
|
|
|
1436
1529
|
init_employees();
|
|
1437
1530
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
1438
1531
|
FETCH_TIMEOUT_MS = 3e4;
|
|
1532
|
+
PUSH_BATCH_SIZE = 5e3;
|
|
1533
|
+
ROSTER_LOCK_PATH = path6.join(EXE_AI_DIR, "roster-merge.lock");
|
|
1534
|
+
LOCK_STALE_MS = 3e4;
|
|
1535
|
+
ROSTER_DELETIONS_PATH = path6.join(EXE_AI_DIR, "roster-deletions.json");
|
|
1439
1536
|
}
|
|
1440
1537
|
});
|
|
1441
1538
|
|
|
@@ -1446,11 +1543,12 @@ import { createInterface } from "readline";
|
|
|
1446
1543
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
1447
1544
|
import { existsSync } from "fs";
|
|
1448
1545
|
import path from "path";
|
|
1546
|
+
import os from "os";
|
|
1449
1547
|
import crypto from "crypto";
|
|
1450
1548
|
var SERVICE = "exe-mem";
|
|
1451
1549
|
var ACCOUNT = "master-key";
|
|
1452
1550
|
function getKeyDir() {
|
|
1453
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(
|
|
1551
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
1454
1552
|
}
|
|
1455
1553
|
function getKeyPath() {
|
|
1456
1554
|
return path.join(getKeyDir(), "master.key");
|
|
@@ -1590,6 +1688,7 @@ async function main() {
|
|
|
1590
1688
|
`);
|
|
1591
1689
|
console.log("Write this down and enter it on your new device with /exe-link import.");
|
|
1592
1690
|
console.log("Anyone with this phrase can decrypt your memories.");
|
|
1691
|
+
console.log("\u26A0 Clear your terminal history after copying.");
|
|
1593
1692
|
} else if (mode === "import") {
|
|
1594
1693
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1595
1694
|
const mnemonic = await new Promise((resolve) => {
|
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// src/lib/config.ts
|
|
19
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
19
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
20
20
|
import { readFileSync, existsSync, renameSync } from "fs";
|
|
21
21
|
import path from "path";
|
|
22
22
|
import os from "os";
|
|
@@ -849,15 +849,20 @@ function addEmployee(employees, employee) {
|
|
|
849
849
|
}
|
|
850
850
|
return [...employees, normalized];
|
|
851
851
|
}
|
|
852
|
+
function findExeBin() {
|
|
853
|
+
try {
|
|
854
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
855
|
+
} catch {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
852
859
|
function registerBinSymlinks(name) {
|
|
853
860
|
const created = [];
|
|
854
861
|
const skipped = [];
|
|
855
862
|
const errors = [];
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
} catch {
|
|
860
|
-
errors.push("Could not find 'exe' in PATH");
|
|
863
|
+
const exeBinPath = findExeBin();
|
|
864
|
+
if (!exeBinPath) {
|
|
865
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
861
866
|
return { created, skipped, errors };
|
|
862
867
|
}
|
|
863
868
|
const binDir = path2.dirname(exeBinPath);
|
|
@@ -1238,6 +1243,15 @@ var LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
|
|
|
1238
1243
|
var CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
|
|
1239
1244
|
var DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
|
|
1240
1245
|
var API_BASE = "https://askexe.com/cloud";
|
|
1246
|
+
var RETRY_DELAY_MS = 500;
|
|
1247
|
+
async function fetchRetry(url, init) {
|
|
1248
|
+
try {
|
|
1249
|
+
return await fetch(url, init);
|
|
1250
|
+
} catch {
|
|
1251
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1252
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1241
1255
|
var LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1242
1256
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1243
1257
|
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
@@ -1329,7 +1343,7 @@ function cacheResponse(token) {
|
|
|
1329
1343
|
async function validateLicense(apiKey, deviceId) {
|
|
1330
1344
|
const did = deviceId ?? loadDeviceId();
|
|
1331
1345
|
try {
|
|
1332
|
-
const res = await
|
|
1346
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1333
1347
|
method: "POST",
|
|
1334
1348
|
headers: { "Content-Type": "application/json" },
|
|
1335
1349
|
body: JSON.stringify({ apiKey, deviceId: did }),
|