@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
package/dist/lib/cloud-sync.js
CHANGED
|
@@ -63,9 +63,34 @@ var init_db_retry = __esm({
|
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
// src/lib/secure-files.ts
|
|
67
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
68
|
+
import { chmod, mkdir } from "fs/promises";
|
|
69
|
+
function ensurePrivateDirSync(dirPath) {
|
|
70
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
71
|
+
try {
|
|
72
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function enforcePrivateFileSync(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
83
|
+
var init_secure_files = __esm({
|
|
84
|
+
"src/lib/secure-files.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
PRIVATE_DIR_MODE = 448;
|
|
87
|
+
PRIVATE_FILE_MODE = 384;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
66
91
|
// src/lib/config.ts
|
|
67
|
-
import { readFile, writeFile
|
|
68
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
92
|
+
import { readFile, writeFile } from "fs/promises";
|
|
93
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
69
94
|
import path from "path";
|
|
70
95
|
import os from "os";
|
|
71
96
|
function resolveDataDir() {
|
|
@@ -73,7 +98,7 @@ function resolveDataDir() {
|
|
|
73
98
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
74
99
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
75
100
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
76
|
-
if (!
|
|
101
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
77
102
|
try {
|
|
78
103
|
renameSync(legacyDir, newDir);
|
|
79
104
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -88,6 +113,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
88
113
|
var init_config = __esm({
|
|
89
114
|
"src/lib/config.ts"() {
|
|
90
115
|
"use strict";
|
|
116
|
+
init_secure_files();
|
|
91
117
|
EXE_AI_DIR = resolveDataDir();
|
|
92
118
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
93
119
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -156,7 +182,7 @@ var init_config = __esm({
|
|
|
156
182
|
|
|
157
183
|
// src/lib/employees.ts
|
|
158
184
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
159
|
-
import { existsSync as
|
|
185
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
160
186
|
import { execSync } from "child_process";
|
|
161
187
|
import path2 from "path";
|
|
162
188
|
import os2 from "os";
|
|
@@ -173,7 +199,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
173
199
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
174
200
|
}
|
|
175
201
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
176
|
-
if (!
|
|
202
|
+
if (!existsSync3(employeesPath)) {
|
|
177
203
|
return [];
|
|
178
204
|
}
|
|
179
205
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -188,7 +214,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
188
214
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
189
215
|
}
|
|
190
216
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
191
|
-
if (!
|
|
217
|
+
if (!existsSync3(employeesPath)) return [];
|
|
192
218
|
try {
|
|
193
219
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
194
220
|
} catch {
|
|
@@ -222,7 +248,7 @@ function registerBinSymlinks(name) {
|
|
|
222
248
|
for (const suffix of ["", "-opencode"]) {
|
|
223
249
|
const linkName = `${name}${suffix}`;
|
|
224
250
|
const linkPath = path2.join(binDir, linkName);
|
|
225
|
-
if (
|
|
251
|
+
if (existsSync3(linkPath)) {
|
|
226
252
|
skipped.push(linkName);
|
|
227
253
|
continue;
|
|
228
254
|
}
|
|
@@ -831,13 +857,50 @@ var init_database_adapter = __esm({
|
|
|
831
857
|
}
|
|
832
858
|
});
|
|
833
859
|
|
|
860
|
+
// src/lib/daemon-auth.ts
|
|
861
|
+
import crypto from "crypto";
|
|
862
|
+
import path4 from "path";
|
|
863
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
864
|
+
function normalizeToken(token) {
|
|
865
|
+
if (!token) return null;
|
|
866
|
+
const trimmed = token.trim();
|
|
867
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
868
|
+
}
|
|
869
|
+
function readDaemonToken() {
|
|
870
|
+
try {
|
|
871
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
872
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
873
|
+
} catch {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function ensureDaemonToken(seed) {
|
|
878
|
+
const existing = readDaemonToken();
|
|
879
|
+
if (existing) return existing;
|
|
880
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
881
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
882
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
883
|
+
`, "utf8");
|
|
884
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
885
|
+
return token;
|
|
886
|
+
}
|
|
887
|
+
var DAEMON_TOKEN_PATH;
|
|
888
|
+
var init_daemon_auth = __esm({
|
|
889
|
+
"src/lib/daemon-auth.ts"() {
|
|
890
|
+
"use strict";
|
|
891
|
+
init_config();
|
|
892
|
+
init_secure_files();
|
|
893
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
834
897
|
// src/lib/exe-daemon-client.ts
|
|
835
898
|
import net from "net";
|
|
836
899
|
import os4 from "os";
|
|
837
900
|
import { spawn } from "child_process";
|
|
838
901
|
import { randomUUID } from "crypto";
|
|
839
|
-
import { existsSync as
|
|
840
|
-
import
|
|
902
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
903
|
+
import path5 from "path";
|
|
841
904
|
import { fileURLToPath } from "url";
|
|
842
905
|
function handleData(chunk) {
|
|
843
906
|
_buffer += chunk.toString();
|
|
@@ -865,9 +928,9 @@ function handleData(chunk) {
|
|
|
865
928
|
}
|
|
866
929
|
}
|
|
867
930
|
function cleanupStaleFiles() {
|
|
868
|
-
if (
|
|
931
|
+
if (existsSync5(PID_PATH)) {
|
|
869
932
|
try {
|
|
870
|
-
const pid = parseInt(
|
|
933
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
871
934
|
if (pid > 0) {
|
|
872
935
|
try {
|
|
873
936
|
process.kill(pid, 0);
|
|
@@ -888,11 +951,11 @@ function cleanupStaleFiles() {
|
|
|
888
951
|
}
|
|
889
952
|
}
|
|
890
953
|
function findPackageRoot() {
|
|
891
|
-
let dir =
|
|
892
|
-
const { root } =
|
|
954
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
955
|
+
const { root } = path5.parse(dir);
|
|
893
956
|
while (dir !== root) {
|
|
894
|
-
if (
|
|
895
|
-
dir =
|
|
957
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
958
|
+
dir = path5.dirname(dir);
|
|
896
959
|
}
|
|
897
960
|
return null;
|
|
898
961
|
}
|
|
@@ -918,16 +981,17 @@ function spawnDaemon() {
|
|
|
918
981
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
919
982
|
return;
|
|
920
983
|
}
|
|
921
|
-
const daemonPath =
|
|
922
|
-
if (!
|
|
984
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
985
|
+
if (!existsSync5(daemonPath)) {
|
|
923
986
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
924
987
|
`);
|
|
925
988
|
return;
|
|
926
989
|
}
|
|
927
990
|
const resolvedPath = daemonPath;
|
|
991
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
928
992
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
929
993
|
`);
|
|
930
|
-
const logPath =
|
|
994
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
931
995
|
let stderrFd = "ignore";
|
|
932
996
|
try {
|
|
933
997
|
stderrFd = openSync(logPath, "a");
|
|
@@ -945,7 +1009,8 @@ function spawnDaemon() {
|
|
|
945
1009
|
TMUX_PANE: void 0,
|
|
946
1010
|
// Prevents resolveExeSession() from scoping to one session
|
|
947
1011
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
948
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1012
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1013
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
949
1014
|
}
|
|
950
1015
|
});
|
|
951
1016
|
child.unref();
|
|
@@ -1052,13 +1117,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1052
1117
|
return;
|
|
1053
1118
|
}
|
|
1054
1119
|
const id = randomUUID();
|
|
1120
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1055
1121
|
const timer = setTimeout(() => {
|
|
1056
1122
|
_pending.delete(id);
|
|
1057
1123
|
resolve({ error: "Request timeout" });
|
|
1058
1124
|
}, timeoutMs);
|
|
1059
1125
|
_pending.set(id, { resolve, timer });
|
|
1060
1126
|
try {
|
|
1061
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1127
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1062
1128
|
} catch {
|
|
1063
1129
|
clearTimeout(timer);
|
|
1064
1130
|
_pending.delete(id);
|
|
@@ -1069,17 +1135,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1069
1135
|
function isClientConnected() {
|
|
1070
1136
|
return _connected;
|
|
1071
1137
|
}
|
|
1072
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1138
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1073
1139
|
var init_exe_daemon_client = __esm({
|
|
1074
1140
|
"src/lib/exe-daemon-client.ts"() {
|
|
1075
1141
|
"use strict";
|
|
1076
1142
|
init_config();
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1143
|
+
init_daemon_auth();
|
|
1144
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1145
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1146
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1080
1147
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1081
1148
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1082
1149
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1150
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1083
1151
|
_socket = null;
|
|
1084
1152
|
_connected = false;
|
|
1085
1153
|
_buffer = "";
|
|
@@ -1658,6 +1726,7 @@ async function ensureSchema() {
|
|
|
1658
1726
|
project TEXT NOT NULL,
|
|
1659
1727
|
summary TEXT NOT NULL,
|
|
1660
1728
|
task_file TEXT,
|
|
1729
|
+
session_scope TEXT,
|
|
1661
1730
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1662
1731
|
created_at TEXT NOT NULL
|
|
1663
1732
|
);
|
|
@@ -1666,7 +1735,7 @@ async function ensureSchema() {
|
|
|
1666
1735
|
ON notifications(read);
|
|
1667
1736
|
|
|
1668
1737
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1669
|
-
ON notifications(agent_id);
|
|
1738
|
+
ON notifications(agent_id, session_scope);
|
|
1670
1739
|
|
|
1671
1740
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1672
1741
|
ON notifications(task_file);
|
|
@@ -1704,6 +1773,7 @@ async function ensureSchema() {
|
|
|
1704
1773
|
target_agent TEXT NOT NULL,
|
|
1705
1774
|
target_project TEXT,
|
|
1706
1775
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1776
|
+
session_scope TEXT,
|
|
1707
1777
|
content TEXT NOT NULL,
|
|
1708
1778
|
priority TEXT DEFAULT 'normal',
|
|
1709
1779
|
status TEXT DEFAULT 'pending',
|
|
@@ -1717,10 +1787,31 @@ async function ensureSchema() {
|
|
|
1717
1787
|
);
|
|
1718
1788
|
|
|
1719
1789
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1720
|
-
ON messages(target_agent, status);
|
|
1790
|
+
ON messages(target_agent, session_scope, status);
|
|
1721
1791
|
|
|
1722
1792
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1723
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1793
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1794
|
+
`);
|
|
1795
|
+
try {
|
|
1796
|
+
await client.execute({
|
|
1797
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1798
|
+
args: []
|
|
1799
|
+
});
|
|
1800
|
+
} catch {
|
|
1801
|
+
}
|
|
1802
|
+
try {
|
|
1803
|
+
await client.execute({
|
|
1804
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1805
|
+
args: []
|
|
1806
|
+
});
|
|
1807
|
+
} catch {
|
|
1808
|
+
}
|
|
1809
|
+
await client.executeMultiple(`
|
|
1810
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1811
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1812
|
+
|
|
1813
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1814
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1724
1815
|
`);
|
|
1725
1816
|
try {
|
|
1726
1817
|
await client.execute({
|
|
@@ -2304,6 +2395,13 @@ async function ensureSchema() {
|
|
|
2304
2395
|
} catch {
|
|
2305
2396
|
}
|
|
2306
2397
|
}
|
|
2398
|
+
try {
|
|
2399
|
+
await client.execute({
|
|
2400
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2401
|
+
args: []
|
|
2402
|
+
});
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2307
2405
|
}
|
|
2308
2406
|
async function disposeDatabase() {
|
|
2309
2407
|
if (_walCheckpointTimer) {
|
|
@@ -2360,8 +2458,8 @@ __export(crdt_sync_exports, {
|
|
|
2360
2458
|
rebuildFromDb: () => rebuildFromDb
|
|
2361
2459
|
});
|
|
2362
2460
|
import * as Y from "yjs";
|
|
2363
|
-
import { readFileSync as
|
|
2364
|
-
import
|
|
2461
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2462
|
+
import path7 from "path";
|
|
2365
2463
|
import { homedir } from "os";
|
|
2366
2464
|
function getStatePath() {
|
|
2367
2465
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -2373,9 +2471,9 @@ function initCrdtDoc() {
|
|
|
2373
2471
|
if (doc) return doc;
|
|
2374
2472
|
doc = new Y.Doc();
|
|
2375
2473
|
const sp = getStatePath();
|
|
2376
|
-
if (
|
|
2474
|
+
if (existsSync7(sp)) {
|
|
2377
2475
|
try {
|
|
2378
|
-
const state =
|
|
2476
|
+
const state = readFileSync6(sp);
|
|
2379
2477
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
2380
2478
|
} catch {
|
|
2381
2479
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -2517,10 +2615,10 @@ function persistState() {
|
|
|
2517
2615
|
if (!doc) return;
|
|
2518
2616
|
try {
|
|
2519
2617
|
const sp = getStatePath();
|
|
2520
|
-
const dir =
|
|
2521
|
-
if (!
|
|
2618
|
+
const dir = path7.dirname(sp);
|
|
2619
|
+
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
2522
2620
|
const state = Y.encodeStateAsUpdate(doc);
|
|
2523
|
-
|
|
2621
|
+
writeFileSync4(sp, Buffer.from(state));
|
|
2524
2622
|
} catch {
|
|
2525
2623
|
}
|
|
2526
2624
|
}
|
|
@@ -2561,7 +2659,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
2561
2659
|
var init_crdt_sync = __esm({
|
|
2562
2660
|
"src/lib/crdt-sync.ts"() {
|
|
2563
2661
|
"use strict";
|
|
2564
|
-
DEFAULT_STATE_PATH =
|
|
2662
|
+
DEFAULT_STATE_PATH = path7.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
2565
2663
|
_statePathOverride = null;
|
|
2566
2664
|
doc = null;
|
|
2567
2665
|
}
|
|
@@ -2577,14 +2675,14 @@ __export(keychain_exports, {
|
|
|
2577
2675
|
setMasterKey: () => setMasterKey
|
|
2578
2676
|
});
|
|
2579
2677
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2580
|
-
import { existsSync as
|
|
2581
|
-
import
|
|
2582
|
-
import
|
|
2678
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2679
|
+
import path8 from "path";
|
|
2680
|
+
import os6 from "os";
|
|
2583
2681
|
function getKeyDir() {
|
|
2584
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2682
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os6.homedir(), ".exe-os");
|
|
2585
2683
|
}
|
|
2586
2684
|
function getKeyPath() {
|
|
2587
|
-
return
|
|
2685
|
+
return path8.join(getKeyDir(), "master.key");
|
|
2588
2686
|
}
|
|
2589
2687
|
async function tryKeytar() {
|
|
2590
2688
|
try {
|
|
@@ -2605,9 +2703,9 @@ async function getMasterKey() {
|
|
|
2605
2703
|
}
|
|
2606
2704
|
}
|
|
2607
2705
|
const keyPath = getKeyPath();
|
|
2608
|
-
if (!
|
|
2706
|
+
if (!existsSync8(keyPath)) {
|
|
2609
2707
|
process.stderr.write(
|
|
2610
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2708
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os6.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2611
2709
|
`
|
|
2612
2710
|
);
|
|
2613
2711
|
return null;
|
|
@@ -2648,7 +2746,7 @@ async function deleteMasterKey() {
|
|
|
2648
2746
|
}
|
|
2649
2747
|
}
|
|
2650
2748
|
const keyPath = getKeyPath();
|
|
2651
|
-
if (
|
|
2749
|
+
if (existsSync8(keyPath)) {
|
|
2652
2750
|
await unlink(keyPath);
|
|
2653
2751
|
}
|
|
2654
2752
|
}
|
|
@@ -2692,13 +2790,13 @@ var init_keychain = __esm({
|
|
|
2692
2790
|
|
|
2693
2791
|
// src/lib/cloud-sync.ts
|
|
2694
2792
|
init_database();
|
|
2695
|
-
import { readFileSync as
|
|
2696
|
-
import
|
|
2697
|
-
import
|
|
2793
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
2794
|
+
import crypto3 from "crypto";
|
|
2795
|
+
import path9 from "path";
|
|
2698
2796
|
import { homedir as homedir2 } from "os";
|
|
2699
2797
|
|
|
2700
2798
|
// src/lib/crypto.ts
|
|
2701
|
-
import
|
|
2799
|
+
import crypto2 from "crypto";
|
|
2702
2800
|
var ALGORITHM = "aes-256-gcm";
|
|
2703
2801
|
var IV_LENGTH = 12;
|
|
2704
2802
|
var TAG_LENGTH = 16;
|
|
@@ -2709,7 +2807,7 @@ function initSyncCrypto(masterKey) {
|
|
|
2709
2807
|
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
2710
2808
|
}
|
|
2711
2809
|
_syncKey = Buffer.from(
|
|
2712
|
-
|
|
2810
|
+
crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
2713
2811
|
);
|
|
2714
2812
|
}
|
|
2715
2813
|
function isSyncCryptoInitialized() {
|
|
@@ -2723,8 +2821,8 @@ function requireSyncKey() {
|
|
|
2723
2821
|
}
|
|
2724
2822
|
function encryptSyncBlob(data) {
|
|
2725
2823
|
const key = requireSyncKey();
|
|
2726
|
-
const iv =
|
|
2727
|
-
const cipher =
|
|
2824
|
+
const iv = crypto2.randomBytes(IV_LENGTH);
|
|
2825
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
|
|
2728
2826
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
2729
2827
|
const tag = cipher.getAuthTag();
|
|
2730
2828
|
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
@@ -2738,7 +2836,7 @@ function decryptSyncBlob(ciphertext) {
|
|
|
2738
2836
|
const iv = combined.subarray(0, IV_LENGTH);
|
|
2739
2837
|
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
2740
2838
|
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
2741
|
-
const decipher =
|
|
2839
|
+
const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
|
|
2742
2840
|
decipher.setAuthTag(tag);
|
|
2743
2841
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
2744
2842
|
}
|
|
@@ -2760,32 +2858,35 @@ function decompress(input) {
|
|
|
2760
2858
|
|
|
2761
2859
|
// src/lib/license.ts
|
|
2762
2860
|
init_config();
|
|
2763
|
-
import { readFileSync as
|
|
2861
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
2764
2862
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2765
|
-
import
|
|
2863
|
+
import { createRequire as createRequire2 } from "module";
|
|
2864
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2865
|
+
import os5 from "os";
|
|
2866
|
+
import path6 from "path";
|
|
2766
2867
|
import { jwtVerify, importSPKI } from "jose";
|
|
2767
|
-
var LICENSE_PATH =
|
|
2768
|
-
var CACHE_PATH =
|
|
2769
|
-
var DEVICE_ID_PATH =
|
|
2868
|
+
var LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
2869
|
+
var CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
2870
|
+
var DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
2770
2871
|
function loadDeviceId() {
|
|
2771
|
-
const deviceJsonPath =
|
|
2872
|
+
const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
|
|
2772
2873
|
try {
|
|
2773
|
-
if (
|
|
2774
|
-
const data = JSON.parse(
|
|
2874
|
+
if (existsSync6(deviceJsonPath)) {
|
|
2875
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
2775
2876
|
if (data.deviceId) return data.deviceId;
|
|
2776
2877
|
}
|
|
2777
2878
|
} catch {
|
|
2778
2879
|
}
|
|
2779
2880
|
try {
|
|
2780
|
-
if (
|
|
2781
|
-
const id2 =
|
|
2881
|
+
if (existsSync6(DEVICE_ID_PATH)) {
|
|
2882
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
2782
2883
|
if (id2) return id2;
|
|
2783
2884
|
}
|
|
2784
2885
|
} catch {
|
|
2785
2886
|
}
|
|
2786
2887
|
const id = randomUUID2();
|
|
2787
|
-
|
|
2788
|
-
|
|
2888
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2889
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
2789
2890
|
return id;
|
|
2790
2891
|
}
|
|
2791
2892
|
|
|
@@ -2793,12 +2894,13 @@ function loadDeviceId() {
|
|
|
2793
2894
|
init_config();
|
|
2794
2895
|
init_crdt_sync();
|
|
2795
2896
|
init_employees();
|
|
2897
|
+
init_secure_files();
|
|
2796
2898
|
function sqlSafe(v) {
|
|
2797
2899
|
return v === void 0 ? null : v;
|
|
2798
2900
|
}
|
|
2799
2901
|
function logError(msg) {
|
|
2800
2902
|
try {
|
|
2801
|
-
const logPath =
|
|
2903
|
+
const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
|
|
2802
2904
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2803
2905
|
`);
|
|
2804
2906
|
} catch {
|
|
@@ -2807,24 +2909,93 @@ function logError(msg) {
|
|
|
2807
2909
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
2808
2910
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
2809
2911
|
var PUSH_BATCH_SIZE = 5e3;
|
|
2810
|
-
var ROSTER_LOCK_PATH =
|
|
2912
|
+
var ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
|
|
2811
2913
|
var LOCK_STALE_MS = 3e4;
|
|
2914
|
+
var _pgPromise = null;
|
|
2915
|
+
var _pgFailed = false;
|
|
2916
|
+
function loadPgClient() {
|
|
2917
|
+
if (_pgFailed) return null;
|
|
2918
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
2919
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
2920
|
+
let cloudPostgresUrl;
|
|
2921
|
+
try {
|
|
2922
|
+
if (existsSync9(configPath)) {
|
|
2923
|
+
const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2924
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
2925
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
2926
|
+
_pgFailed = true;
|
|
2927
|
+
return null;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
} catch {
|
|
2931
|
+
}
|
|
2932
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
2933
|
+
if (!url) {
|
|
2934
|
+
_pgFailed = true;
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
if (!_pgPromise) {
|
|
2938
|
+
_pgPromise = (async () => {
|
|
2939
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
2940
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
2941
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(homedir2(), "exe-db");
|
|
2942
|
+
const req = createRequire3(path9.join(exeDbRoot, "package.json"));
|
|
2943
|
+
const entry = req.resolve("@prisma/client");
|
|
2944
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
2945
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
2946
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
2947
|
+
return new Ctor();
|
|
2948
|
+
})().catch(() => {
|
|
2949
|
+
_pgFailed = true;
|
|
2950
|
+
_pgPromise = null;
|
|
2951
|
+
throw new Error("pg_unavailable");
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
return _pgPromise;
|
|
2955
|
+
}
|
|
2956
|
+
async function pushToPostgres(records) {
|
|
2957
|
+
const loader = loadPgClient();
|
|
2958
|
+
if (!loader) return 0;
|
|
2959
|
+
let prisma;
|
|
2960
|
+
try {
|
|
2961
|
+
prisma = await loader;
|
|
2962
|
+
} catch {
|
|
2963
|
+
return 0;
|
|
2964
|
+
}
|
|
2965
|
+
let inserted = 0;
|
|
2966
|
+
for (const rec of records) {
|
|
2967
|
+
try {
|
|
2968
|
+
await prisma.$executeRawUnsafe(
|
|
2969
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
2970
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
2971
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
2972
|
+
String(rec.id ?? ""),
|
|
2973
|
+
JSON.stringify(rec),
|
|
2974
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
2975
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
2976
|
+
);
|
|
2977
|
+
inserted++;
|
|
2978
|
+
} catch {
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
return inserted;
|
|
2982
|
+
}
|
|
2812
2983
|
async function withRosterLock(fn) {
|
|
2813
2984
|
try {
|
|
2814
2985
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2815
2986
|
closeSync2(fd);
|
|
2816
|
-
|
|
2987
|
+
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2817
2988
|
} catch (err) {
|
|
2818
2989
|
if (err.code === "EEXIST") {
|
|
2819
2990
|
try {
|
|
2820
|
-
const ts = parseInt(
|
|
2991
|
+
const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
2821
2992
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
2822
2993
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
2823
2994
|
}
|
|
2824
2995
|
unlinkSync4(ROSTER_LOCK_PATH);
|
|
2825
2996
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
2826
2997
|
closeSync2(fd);
|
|
2827
|
-
|
|
2998
|
+
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2828
2999
|
} catch (retryErr) {
|
|
2829
3000
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
2830
3001
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3094,6 +3265,10 @@ async function cloudSync(config) {
|
|
|
3094
3265
|
const maxVersion = Number(records[records.length - 1].version);
|
|
3095
3266
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
3096
3267
|
if (!pushOk) break;
|
|
3268
|
+
try {
|
|
3269
|
+
await pushToPostgres(records);
|
|
3270
|
+
} catch {
|
|
3271
|
+
}
|
|
3097
3272
|
await client.execute({
|
|
3098
3273
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
3099
3274
|
args: [String(maxVersion)]
|
|
@@ -3198,8 +3373,8 @@ async function cloudSync(config) {
|
|
|
3198
3373
|
try {
|
|
3199
3374
|
const employees = await loadEmployees();
|
|
3200
3375
|
rosterResult.employees = employees.length;
|
|
3201
|
-
const idDir =
|
|
3202
|
-
if (
|
|
3376
|
+
const idDir = path9.join(EXE_AI_DIR, "identity");
|
|
3377
|
+
if (existsSync9(idDir)) {
|
|
3203
3378
|
rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3204
3379
|
}
|
|
3205
3380
|
} catch {
|
|
@@ -3217,66 +3392,66 @@ async function cloudSync(config) {
|
|
|
3217
3392
|
roster: rosterResult
|
|
3218
3393
|
};
|
|
3219
3394
|
}
|
|
3220
|
-
var ROSTER_DELETIONS_PATH =
|
|
3395
|
+
var ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
|
|
3221
3396
|
function recordRosterDeletion(name) {
|
|
3222
3397
|
let deletions = [];
|
|
3223
3398
|
try {
|
|
3224
|
-
if (
|
|
3225
|
-
deletions = JSON.parse(
|
|
3399
|
+
if (existsSync9(ROSTER_DELETIONS_PATH)) {
|
|
3400
|
+
deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3226
3401
|
}
|
|
3227
3402
|
} catch {
|
|
3228
3403
|
}
|
|
3229
3404
|
if (!deletions.includes(name)) deletions.push(name);
|
|
3230
|
-
|
|
3405
|
+
writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
3231
3406
|
}
|
|
3232
3407
|
function consumeRosterDeletions() {
|
|
3233
3408
|
try {
|
|
3234
|
-
if (!
|
|
3235
|
-
const deletions = JSON.parse(
|
|
3236
|
-
|
|
3409
|
+
if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
|
|
3410
|
+
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3411
|
+
writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
|
|
3237
3412
|
return deletions;
|
|
3238
3413
|
} catch {
|
|
3239
3414
|
return [];
|
|
3240
3415
|
}
|
|
3241
3416
|
}
|
|
3242
3417
|
function buildRosterBlob(paths) {
|
|
3243
|
-
const rosterPath = paths?.rosterPath ??
|
|
3244
|
-
const identityDir = paths?.identityDir ??
|
|
3245
|
-
const configPath = paths?.configPath ??
|
|
3418
|
+
const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
|
|
3419
|
+
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
3420
|
+
const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
|
|
3246
3421
|
let roster = [];
|
|
3247
|
-
if (
|
|
3422
|
+
if (existsSync9(rosterPath)) {
|
|
3248
3423
|
try {
|
|
3249
|
-
roster = JSON.parse(
|
|
3424
|
+
roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
|
|
3250
3425
|
} catch {
|
|
3251
3426
|
}
|
|
3252
3427
|
}
|
|
3253
3428
|
const identities = {};
|
|
3254
|
-
if (
|
|
3429
|
+
if (existsSync9(identityDir)) {
|
|
3255
3430
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3256
3431
|
try {
|
|
3257
|
-
identities[file] =
|
|
3432
|
+
identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
|
|
3258
3433
|
} catch {
|
|
3259
3434
|
}
|
|
3260
3435
|
}
|
|
3261
3436
|
}
|
|
3262
3437
|
let config;
|
|
3263
|
-
if (
|
|
3438
|
+
if (existsSync9(configPath)) {
|
|
3264
3439
|
try {
|
|
3265
|
-
config = JSON.parse(
|
|
3440
|
+
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
3266
3441
|
} catch {
|
|
3267
3442
|
}
|
|
3268
3443
|
}
|
|
3269
3444
|
let agentConfig;
|
|
3270
|
-
const agentConfigPath =
|
|
3271
|
-
if (
|
|
3445
|
+
const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
3446
|
+
if (existsSync9(agentConfigPath)) {
|
|
3272
3447
|
try {
|
|
3273
|
-
agentConfig = JSON.parse(
|
|
3448
|
+
agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3274
3449
|
} catch {
|
|
3275
3450
|
}
|
|
3276
3451
|
}
|
|
3277
3452
|
const deletedNames = consumeRosterDeletions();
|
|
3278
3453
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
3279
|
-
const hash =
|
|
3454
|
+
const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3280
3455
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
3281
3456
|
}
|
|
3282
3457
|
async function cloudPushRoster(config) {
|
|
@@ -3346,23 +3521,24 @@ async function cloudPullRoster(config) {
|
|
|
3346
3521
|
}
|
|
3347
3522
|
}
|
|
3348
3523
|
function mergeConfig(remoteConfig, configPath) {
|
|
3349
|
-
const cfgPath = configPath ??
|
|
3524
|
+
const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
|
|
3350
3525
|
let local = {};
|
|
3351
|
-
if (
|
|
3526
|
+
if (existsSync9(cfgPath)) {
|
|
3352
3527
|
try {
|
|
3353
|
-
local = JSON.parse(
|
|
3528
|
+
local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
|
|
3354
3529
|
} catch {
|
|
3355
3530
|
}
|
|
3356
3531
|
}
|
|
3357
3532
|
const merged = { ...remoteConfig, ...local };
|
|
3358
|
-
const dir =
|
|
3359
|
-
|
|
3360
|
-
|
|
3533
|
+
const dir = path9.dirname(cfgPath);
|
|
3534
|
+
ensurePrivateDirSync(dir);
|
|
3535
|
+
writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3536
|
+
enforcePrivateFileSync(cfgPath);
|
|
3361
3537
|
}
|
|
3362
3538
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3363
3539
|
return withRosterLock(async () => {
|
|
3364
3540
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3365
|
-
const identityDir = paths?.identityDir ??
|
|
3541
|
+
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
3366
3542
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3367
3543
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3368
3544
|
let added = 0;
|
|
@@ -3383,15 +3559,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3383
3559
|
) ?? lookupKey;
|
|
3384
3560
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3385
3561
|
if (remoteIdentity) {
|
|
3386
|
-
if (!
|
|
3387
|
-
const idPath =
|
|
3562
|
+
if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
|
|
3563
|
+
const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
|
|
3388
3564
|
let localIdentity = null;
|
|
3389
3565
|
try {
|
|
3390
|
-
localIdentity =
|
|
3566
|
+
localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
|
|
3391
3567
|
} catch {
|
|
3392
3568
|
}
|
|
3393
3569
|
if (localIdentity !== remoteIdentity) {
|
|
3394
|
-
|
|
3570
|
+
writeFileSync5(idPath, remoteIdentity, "utf-8");
|
|
3395
3571
|
identitiesUpdated++;
|
|
3396
3572
|
}
|
|
3397
3573
|
}
|
|
@@ -3417,16 +3593,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3417
3593
|
}
|
|
3418
3594
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
3419
3595
|
try {
|
|
3420
|
-
const agentConfigPath =
|
|
3596
|
+
const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
3421
3597
|
let local = {};
|
|
3422
|
-
if (
|
|
3598
|
+
if (existsSync9(agentConfigPath)) {
|
|
3423
3599
|
try {
|
|
3424
|
-
local = JSON.parse(
|
|
3600
|
+
local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3425
3601
|
} catch {
|
|
3426
3602
|
}
|
|
3427
3603
|
}
|
|
3428
3604
|
const merged = { ...remote.agentConfig, ...local };
|
|
3429
|
-
|
|
3605
|
+
ensurePrivateDirSync(path9.dirname(agentConfigPath));
|
|
3606
|
+
writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3607
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
3430
3608
|
} catch {
|
|
3431
3609
|
}
|
|
3432
3610
|
}
|
|
@@ -3874,5 +4052,6 @@ export {
|
|
|
3874
4052
|
cloudSync,
|
|
3875
4053
|
mergeConfig,
|
|
3876
4054
|
mergeRosterFromRemote,
|
|
4055
|
+
pushToPostgres,
|
|
3877
4056
|
recordRosterDeletion
|
|
3878
4057
|
};
|