@askexenow/exe-os 0.9.7 → 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 +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -25,6 +25,44 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/secure-files.ts
|
|
29
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
30
|
+
import { chmod, mkdir } from "fs/promises";
|
|
31
|
+
async function ensurePrivateDir(dirPath) {
|
|
32
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
33
|
+
try {
|
|
34
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function ensurePrivateDirSync(dirPath) {
|
|
39
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function enforcePrivateFileSync(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
58
|
+
var init_secure_files = __esm({
|
|
59
|
+
"src/lib/secure-files.ts"() {
|
|
60
|
+
"use strict";
|
|
61
|
+
PRIVATE_DIR_MODE = 448;
|
|
62
|
+
PRIVATE_FILE_MODE = 384;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
28
66
|
// src/lib/config.ts
|
|
29
67
|
var config_exports = {};
|
|
30
68
|
__export(config_exports, {
|
|
@@ -41,8 +79,8 @@ __export(config_exports, {
|
|
|
41
79
|
migrateConfig: () => migrateConfig,
|
|
42
80
|
saveConfig: () => saveConfig
|
|
43
81
|
});
|
|
44
|
-
import { readFile, writeFile
|
|
45
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
82
|
+
import { readFile, writeFile } from "fs/promises";
|
|
83
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
46
84
|
import path from "path";
|
|
47
85
|
import os from "os";
|
|
48
86
|
function resolveDataDir() {
|
|
@@ -50,7 +88,7 @@ function resolveDataDir() {
|
|
|
50
88
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
51
89
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
52
90
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
53
|
-
if (!
|
|
91
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
54
92
|
try {
|
|
55
93
|
renameSync(legacyDir, newDir);
|
|
56
94
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -113,9 +151,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
113
151
|
}
|
|
114
152
|
async function loadConfig() {
|
|
115
153
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
116
|
-
await
|
|
154
|
+
await ensurePrivateDir(dir);
|
|
117
155
|
const configPath = path.join(dir, "config.json");
|
|
118
|
-
if (!
|
|
156
|
+
if (!existsSync2(configPath)) {
|
|
119
157
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
120
158
|
}
|
|
121
159
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -128,6 +166,7 @@ async function loadConfig() {
|
|
|
128
166
|
`);
|
|
129
167
|
try {
|
|
130
168
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
169
|
+
await enforcePrivateFile(configPath);
|
|
131
170
|
} catch {
|
|
132
171
|
}
|
|
133
172
|
}
|
|
@@ -146,7 +185,7 @@ async function loadConfig() {
|
|
|
146
185
|
function loadConfigSync() {
|
|
147
186
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
148
187
|
const configPath = path.join(dir, "config.json");
|
|
149
|
-
if (!
|
|
188
|
+
if (!existsSync2(configPath)) {
|
|
150
189
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
151
190
|
}
|
|
152
191
|
try {
|
|
@@ -164,12 +203,10 @@ function loadConfigSync() {
|
|
|
164
203
|
}
|
|
165
204
|
async function saveConfig(config) {
|
|
166
205
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
167
|
-
await
|
|
206
|
+
await ensurePrivateDir(dir);
|
|
168
207
|
const configPath = path.join(dir, "config.json");
|
|
169
208
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
170
|
-
|
|
171
|
-
await chmod(configPath, 384);
|
|
172
|
-
}
|
|
209
|
+
await enforcePrivateFile(configPath);
|
|
173
210
|
}
|
|
174
211
|
async function loadConfigFrom(configPath) {
|
|
175
212
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -189,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
189
226
|
var init_config = __esm({
|
|
190
227
|
"src/lib/config.ts"() {
|
|
191
228
|
"use strict";
|
|
229
|
+
init_secure_files();
|
|
192
230
|
EXE_AI_DIR = resolveDataDir();
|
|
193
231
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
194
232
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -361,6 +399,43 @@ var init_daemon_protocol = __esm({
|
|
|
361
399
|
}
|
|
362
400
|
});
|
|
363
401
|
|
|
402
|
+
// src/lib/daemon-auth.ts
|
|
403
|
+
import crypto from "crypto";
|
|
404
|
+
import path2 from "path";
|
|
405
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
406
|
+
function normalizeToken(token) {
|
|
407
|
+
if (!token) return null;
|
|
408
|
+
const trimmed = token.trim();
|
|
409
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
410
|
+
}
|
|
411
|
+
function readDaemonToken() {
|
|
412
|
+
try {
|
|
413
|
+
if (!existsSync3(DAEMON_TOKEN_PATH)) return null;
|
|
414
|
+
return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
|
|
415
|
+
} catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function ensureDaemonToken(seed) {
|
|
420
|
+
const existing = readDaemonToken();
|
|
421
|
+
if (existing) return existing;
|
|
422
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
423
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
424
|
+
writeFileSync(DAEMON_TOKEN_PATH, `${token}
|
|
425
|
+
`, "utf8");
|
|
426
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
427
|
+
return token;
|
|
428
|
+
}
|
|
429
|
+
var DAEMON_TOKEN_PATH;
|
|
430
|
+
var init_daemon_auth = __esm({
|
|
431
|
+
"src/lib/daemon-auth.ts"() {
|
|
432
|
+
"use strict";
|
|
433
|
+
init_config();
|
|
434
|
+
init_secure_files();
|
|
435
|
+
DAEMON_TOKEN_PATH = path2.join(EXE_AI_DIR, "exed.token");
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
364
439
|
// src/lib/session-registry.ts
|
|
365
440
|
var session_registry_exports = {};
|
|
366
441
|
__export(session_registry_exports, {
|
|
@@ -368,14 +443,14 @@ __export(session_registry_exports, {
|
|
|
368
443
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
369
444
|
registerSession: () => registerSession
|
|
370
445
|
});
|
|
371
|
-
import { readFileSync as
|
|
446
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
372
447
|
import { execSync } from "child_process";
|
|
373
|
-
import
|
|
448
|
+
import path3 from "path";
|
|
374
449
|
import os2 from "os";
|
|
375
450
|
function registerSession(entry) {
|
|
376
|
-
const dir =
|
|
377
|
-
if (!
|
|
378
|
-
|
|
451
|
+
const dir = path3.dirname(REGISTRY_PATH);
|
|
452
|
+
if (!existsSync4(dir)) {
|
|
453
|
+
mkdirSync2(dir, { recursive: true });
|
|
379
454
|
}
|
|
380
455
|
const sessions = listSessions();
|
|
381
456
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -384,11 +459,11 @@ function registerSession(entry) {
|
|
|
384
459
|
} else {
|
|
385
460
|
sessions.push(entry);
|
|
386
461
|
}
|
|
387
|
-
|
|
462
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
388
463
|
}
|
|
389
464
|
function listSessions() {
|
|
390
465
|
try {
|
|
391
|
-
const raw =
|
|
466
|
+
const raw = readFileSync3(REGISTRY_PATH, "utf8");
|
|
392
467
|
return JSON.parse(raw);
|
|
393
468
|
} catch {
|
|
394
469
|
return [];
|
|
@@ -409,7 +484,7 @@ function pruneStaleSessions() {
|
|
|
409
484
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
410
485
|
const pruned = sessions.length - alive.length;
|
|
411
486
|
if (pruned > 0) {
|
|
412
|
-
|
|
487
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
413
488
|
}
|
|
414
489
|
return pruned;
|
|
415
490
|
}
|
|
@@ -417,7 +492,7 @@ var REGISTRY_PATH;
|
|
|
417
492
|
var init_session_registry = __esm({
|
|
418
493
|
"src/lib/session-registry.ts"() {
|
|
419
494
|
"use strict";
|
|
420
|
-
REGISTRY_PATH =
|
|
495
|
+
REGISTRY_PATH = path3.join(os2.homedir(), ".exe-os", "session-registry.json");
|
|
421
496
|
}
|
|
422
497
|
});
|
|
423
498
|
|
|
@@ -717,20 +792,21 @@ __export(agent_config_exports, {
|
|
|
717
792
|
saveAgentConfig: () => saveAgentConfig,
|
|
718
793
|
setAgentRuntime: () => setAgentRuntime
|
|
719
794
|
});
|
|
720
|
-
import { readFileSync as
|
|
721
|
-
import
|
|
795
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
796
|
+
import path4 from "path";
|
|
722
797
|
function loadAgentConfig() {
|
|
723
|
-
if (!
|
|
798
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
724
799
|
try {
|
|
725
|
-
return JSON.parse(
|
|
800
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
726
801
|
} catch {
|
|
727
802
|
return {};
|
|
728
803
|
}
|
|
729
804
|
}
|
|
730
805
|
function saveAgentConfig(config) {
|
|
731
|
-
const dir =
|
|
732
|
-
|
|
733
|
-
|
|
806
|
+
const dir = path4.dirname(AGENT_CONFIG_PATH);
|
|
807
|
+
ensurePrivateDirSync(dir);
|
|
808
|
+
writeFileSync3(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
809
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
734
810
|
}
|
|
735
811
|
function getAgentRuntime(agentId) {
|
|
736
812
|
const config = loadAgentConfig();
|
|
@@ -770,7 +846,8 @@ var init_agent_config = __esm({
|
|
|
770
846
|
"use strict";
|
|
771
847
|
init_config();
|
|
772
848
|
init_runtime_table();
|
|
773
|
-
|
|
849
|
+
init_secure_files();
|
|
850
|
+
AGENT_CONFIG_PATH = path4.join(EXE_AI_DIR, "agent-config.json");
|
|
774
851
|
KNOWN_RUNTIMES = {
|
|
775
852
|
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
776
853
|
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
@@ -798,17 +875,17 @@ __export(intercom_queue_exports, {
|
|
|
798
875
|
queueIntercom: () => queueIntercom,
|
|
799
876
|
readQueue: () => readQueue
|
|
800
877
|
});
|
|
801
|
-
import { readFileSync as
|
|
802
|
-
import
|
|
878
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
879
|
+
import path5 from "path";
|
|
803
880
|
import os3 from "os";
|
|
804
881
|
function ensureDir() {
|
|
805
|
-
const dir =
|
|
806
|
-
if (!
|
|
882
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
883
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
807
884
|
}
|
|
808
885
|
function readQueue() {
|
|
809
886
|
try {
|
|
810
|
-
if (!
|
|
811
|
-
return JSON.parse(
|
|
887
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
888
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
812
889
|
} catch {
|
|
813
890
|
return [];
|
|
814
891
|
}
|
|
@@ -816,7 +893,7 @@ function readQueue() {
|
|
|
816
893
|
function writeQueue(queue) {
|
|
817
894
|
ensureDir();
|
|
818
895
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
819
|
-
|
|
896
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
820
897
|
renameSync2(tmp, QUEUE_PATH);
|
|
821
898
|
}
|
|
822
899
|
function queueIntercom(targetSession, reason) {
|
|
@@ -908,10 +985,10 @@ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
|
908
985
|
var init_intercom_queue = __esm({
|
|
909
986
|
"src/lib/intercom-queue.ts"() {
|
|
910
987
|
"use strict";
|
|
911
|
-
QUEUE_PATH =
|
|
988
|
+
QUEUE_PATH = path5.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
912
989
|
MAX_RETRIES = 5;
|
|
913
990
|
TTL_MS = 60 * 60 * 1e3;
|
|
914
|
-
INTERCOM_LOG =
|
|
991
|
+
INTERCOM_LOG = path5.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
915
992
|
}
|
|
916
993
|
});
|
|
917
994
|
|
|
@@ -985,6 +1062,7 @@ __export(employees_exports, {
|
|
|
985
1062
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
986
1063
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
987
1064
|
hasRole: () => hasRole,
|
|
1065
|
+
hireEmployee: () => hireEmployee,
|
|
988
1066
|
isCoordinatorName: () => isCoordinatorName,
|
|
989
1067
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
990
1068
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -997,9 +1075,9 @@ __export(employees_exports, {
|
|
|
997
1075
|
validateEmployeeName: () => validateEmployeeName
|
|
998
1076
|
});
|
|
999
1077
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1000
|
-
import { existsSync as
|
|
1078
|
+
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1001
1079
|
import { execSync as execSync4 } from "child_process";
|
|
1002
|
-
import
|
|
1080
|
+
import path6 from "path";
|
|
1003
1081
|
import os4 from "os";
|
|
1004
1082
|
function normalizeRole(role) {
|
|
1005
1083
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -1036,7 +1114,7 @@ function validateEmployeeName(name) {
|
|
|
1036
1114
|
return { valid: true };
|
|
1037
1115
|
}
|
|
1038
1116
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1039
|
-
if (!
|
|
1117
|
+
if (!existsSync7(employeesPath)) {
|
|
1040
1118
|
return [];
|
|
1041
1119
|
}
|
|
1042
1120
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -1047,13 +1125,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
1047
1125
|
}
|
|
1048
1126
|
}
|
|
1049
1127
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1050
|
-
await mkdir2(
|
|
1128
|
+
await mkdir2(path6.dirname(employeesPath), { recursive: true });
|
|
1051
1129
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1052
1130
|
}
|
|
1053
1131
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1054
|
-
if (!
|
|
1132
|
+
if (!existsSync7(employeesPath)) return [];
|
|
1055
1133
|
try {
|
|
1056
|
-
return JSON.parse(
|
|
1134
|
+
return JSON.parse(readFileSync6(employeesPath, "utf-8"));
|
|
1057
1135
|
} catch {
|
|
1058
1136
|
return [];
|
|
1059
1137
|
}
|
|
@@ -1095,6 +1173,52 @@ function addEmployee(employees, employee) {
|
|
|
1095
1173
|
}
|
|
1096
1174
|
return [...employees, normalized];
|
|
1097
1175
|
}
|
|
1176
|
+
function appendToCoordinatorTeam(employee) {
|
|
1177
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1178
|
+
if (!coordinator) return;
|
|
1179
|
+
const idPath = path6.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1180
|
+
if (!existsSync7(idPath)) return;
|
|
1181
|
+
const content = readFileSync6(idPath, "utf-8");
|
|
1182
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1183
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1184
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1185
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1186
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1187
|
+
const entry = `
|
|
1188
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1189
|
+
`;
|
|
1190
|
+
let updated;
|
|
1191
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1192
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1193
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1194
|
+
} else {
|
|
1195
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1196
|
+
}
|
|
1197
|
+
writeFileSync5(idPath, updated, "utf-8");
|
|
1198
|
+
}
|
|
1199
|
+
function capitalize(s) {
|
|
1200
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1201
|
+
}
|
|
1202
|
+
async function hireEmployee(employee) {
|
|
1203
|
+
const employees = await loadEmployees();
|
|
1204
|
+
const updated = addEmployee(employees, employee);
|
|
1205
|
+
await saveEmployees(updated);
|
|
1206
|
+
try {
|
|
1207
|
+
appendToCoordinatorTeam(employee);
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1210
|
+
try {
|
|
1211
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1212
|
+
const config = loadAgentConfig2();
|
|
1213
|
+
const name = employee.name.toLowerCase();
|
|
1214
|
+
if (!config[name] && config["default"]) {
|
|
1215
|
+
config[name] = { ...config["default"] };
|
|
1216
|
+
saveAgentConfig2(config);
|
|
1217
|
+
}
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
return updated;
|
|
1221
|
+
}
|
|
1098
1222
|
async function normalizeRosterCase(rosterPath) {
|
|
1099
1223
|
const employees = await loadEmployees(rosterPath);
|
|
1100
1224
|
let changed = false;
|
|
@@ -1104,14 +1228,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
1104
1228
|
emp.name = emp.name.toLowerCase();
|
|
1105
1229
|
changed = true;
|
|
1106
1230
|
try {
|
|
1107
|
-
const identityDir =
|
|
1108
|
-
const oldPath =
|
|
1109
|
-
const newPath =
|
|
1110
|
-
if (
|
|
1231
|
+
const identityDir = path6.join(os4.homedir(), ".exe-os", "identity");
|
|
1232
|
+
const oldPath = path6.join(identityDir, `${oldName}.md`);
|
|
1233
|
+
const newPath = path6.join(identityDir, `${emp.name}.md`);
|
|
1234
|
+
if (existsSync7(oldPath) && !existsSync7(newPath)) {
|
|
1111
1235
|
renameSync3(oldPath, newPath);
|
|
1112
|
-
} else if (
|
|
1113
|
-
const content =
|
|
1114
|
-
|
|
1236
|
+
} else if (existsSync7(oldPath) && oldPath !== newPath) {
|
|
1237
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
1238
|
+
writeFileSync5(newPath, content, "utf-8");
|
|
1115
1239
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1116
1240
|
unlinkSync(oldPath);
|
|
1117
1241
|
}
|
|
@@ -1141,7 +1265,7 @@ function registerBinSymlinks(name) {
|
|
|
1141
1265
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1142
1266
|
return { created, skipped, errors };
|
|
1143
1267
|
}
|
|
1144
|
-
const binDir =
|
|
1268
|
+
const binDir = path6.dirname(exeBinPath);
|
|
1145
1269
|
let target;
|
|
1146
1270
|
try {
|
|
1147
1271
|
target = readlinkSync(exeBinPath);
|
|
@@ -1151,8 +1275,8 @@ function registerBinSymlinks(name) {
|
|
|
1151
1275
|
}
|
|
1152
1276
|
for (const suffix of ["", "-opencode"]) {
|
|
1153
1277
|
const linkName = `${name}${suffix}`;
|
|
1154
|
-
const linkPath =
|
|
1155
|
-
if (
|
|
1278
|
+
const linkPath = path6.join(binDir, linkName);
|
|
1279
|
+
if (existsSync7(linkPath)) {
|
|
1156
1280
|
skipped.push(linkName);
|
|
1157
1281
|
continue;
|
|
1158
1282
|
}
|
|
@@ -1165,25 +1289,611 @@ function registerBinSymlinks(name) {
|
|
|
1165
1289
|
}
|
|
1166
1290
|
return { created, skipped, errors };
|
|
1167
1291
|
}
|
|
1168
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
1292
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
1169
1293
|
var init_employees = __esm({
|
|
1170
1294
|
"src/lib/employees.ts"() {
|
|
1171
1295
|
"use strict";
|
|
1172
1296
|
init_config();
|
|
1173
|
-
EMPLOYEES_PATH =
|
|
1297
|
+
EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
|
|
1174
1298
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1175
1299
|
COORDINATOR_ROLE = "COO";
|
|
1176
1300
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1301
|
+
IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
|
|
1302
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// src/lib/database-adapter.ts
|
|
1307
|
+
import os5 from "os";
|
|
1308
|
+
import path7 from "path";
|
|
1309
|
+
import { createRequire } from "module";
|
|
1310
|
+
import { pathToFileURL } from "url";
|
|
1311
|
+
function quotedIdentifier(identifier) {
|
|
1312
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1313
|
+
}
|
|
1314
|
+
function unqualifiedTableName(name) {
|
|
1315
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1316
|
+
const parts = raw.split(".");
|
|
1317
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1318
|
+
}
|
|
1319
|
+
function stripTrailingSemicolon(sql) {
|
|
1320
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1321
|
+
}
|
|
1322
|
+
function appendClause(sql, clause) {
|
|
1323
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
1324
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1325
|
+
if (!returningMatch) {
|
|
1326
|
+
return `${trimmed}${clause}`;
|
|
1327
|
+
}
|
|
1328
|
+
const idx = returningMatch.index;
|
|
1329
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1330
|
+
}
|
|
1331
|
+
function normalizeStatement(stmt) {
|
|
1332
|
+
if (typeof stmt === "string") {
|
|
1333
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1334
|
+
}
|
|
1335
|
+
const sql = stmt.sql;
|
|
1336
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1337
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1338
|
+
}
|
|
1339
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1340
|
+
}
|
|
1341
|
+
function rewriteBooleanLiterals(sql) {
|
|
1342
|
+
let out = sql;
|
|
1343
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1344
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1345
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1346
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1347
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1348
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1349
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1350
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1351
|
+
}
|
|
1352
|
+
return out;
|
|
1353
|
+
}
|
|
1354
|
+
function rewriteInsertOrIgnore(sql) {
|
|
1355
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1356
|
+
return sql;
|
|
1357
|
+
}
|
|
1358
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1359
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1360
|
+
}
|
|
1361
|
+
function rewriteInsertOrReplace(sql) {
|
|
1362
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1363
|
+
if (!match) {
|
|
1364
|
+
return sql;
|
|
1365
|
+
}
|
|
1366
|
+
const rawTable = match[1];
|
|
1367
|
+
const rawColumns = match[2];
|
|
1368
|
+
const remainder = match[3];
|
|
1369
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
1370
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1371
|
+
if (!conflictKeys?.length) {
|
|
1372
|
+
return sql;
|
|
1373
|
+
}
|
|
1374
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1375
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1376
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1377
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1378
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1379
|
+
}
|
|
1380
|
+
function rewriteSql(sql) {
|
|
1381
|
+
let out = sql;
|
|
1382
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1383
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1384
|
+
out = rewriteBooleanLiterals(out);
|
|
1385
|
+
out = rewriteInsertOrReplace(out);
|
|
1386
|
+
out = rewriteInsertOrIgnore(out);
|
|
1387
|
+
return stripTrailingSemicolon(out);
|
|
1388
|
+
}
|
|
1389
|
+
function toBoolean(value) {
|
|
1390
|
+
if (value === null || value === void 0) return value;
|
|
1391
|
+
if (typeof value === "boolean") return value;
|
|
1392
|
+
if (typeof value === "number") return value !== 0;
|
|
1393
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
1394
|
+
if (typeof value === "string") {
|
|
1395
|
+
const normalized = value.trim().toLowerCase();
|
|
1396
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
1397
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1398
|
+
}
|
|
1399
|
+
return Boolean(value);
|
|
1400
|
+
}
|
|
1401
|
+
function countQuestionMarks(sql, end) {
|
|
1402
|
+
let count = 0;
|
|
1403
|
+
let inSingle = false;
|
|
1404
|
+
let inDouble = false;
|
|
1405
|
+
let inLineComment = false;
|
|
1406
|
+
let inBlockComment = false;
|
|
1407
|
+
for (let i = 0; i < end; i++) {
|
|
1408
|
+
const ch = sql[i];
|
|
1409
|
+
const next = sql[i + 1];
|
|
1410
|
+
if (inLineComment) {
|
|
1411
|
+
if (ch === "\n") inLineComment = false;
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (inBlockComment) {
|
|
1415
|
+
if (ch === "*" && next === "/") {
|
|
1416
|
+
inBlockComment = false;
|
|
1417
|
+
i += 1;
|
|
1418
|
+
}
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1422
|
+
inLineComment = true;
|
|
1423
|
+
i += 1;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1427
|
+
inBlockComment = true;
|
|
1428
|
+
i += 1;
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1432
|
+
inSingle = !inSingle;
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1436
|
+
inDouble = !inDouble;
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1440
|
+
count += 1;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return count;
|
|
1444
|
+
}
|
|
1445
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1446
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1447
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1448
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1449
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1450
|
+
const matchText = match[0];
|
|
1451
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1452
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return indexes;
|
|
1456
|
+
}
|
|
1457
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1458
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1459
|
+
if (!match) return;
|
|
1460
|
+
const rawTable = match[1];
|
|
1461
|
+
const rawColumns = match[2];
|
|
1462
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1463
|
+
if (!boolColumns?.size) return;
|
|
1464
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1465
|
+
for (const [index, column] of columns.entries()) {
|
|
1466
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1467
|
+
args[index] = toBoolean(args[index]);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1472
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1473
|
+
if (!match) return;
|
|
1474
|
+
const rawTable = match[1];
|
|
1475
|
+
const setClause = match[2];
|
|
1476
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1477
|
+
if (!boolColumns?.size) return;
|
|
1478
|
+
const assignments = setClause.split(",");
|
|
1479
|
+
let placeholderIndex = 0;
|
|
1480
|
+
for (const assignment of assignments) {
|
|
1481
|
+
if (!assignment.includes("?")) continue;
|
|
1482
|
+
placeholderIndex += 1;
|
|
1483
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1484
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1485
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function coerceBooleanArgs(sql, args) {
|
|
1490
|
+
const nextArgs = [...args];
|
|
1491
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1492
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1493
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1494
|
+
for (const index of placeholderIndexes) {
|
|
1495
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1496
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return nextArgs;
|
|
1500
|
+
}
|
|
1501
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1502
|
+
let out = "";
|
|
1503
|
+
let placeholder = 0;
|
|
1504
|
+
let inSingle = false;
|
|
1505
|
+
let inDouble = false;
|
|
1506
|
+
let inLineComment = false;
|
|
1507
|
+
let inBlockComment = false;
|
|
1508
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1509
|
+
const ch = sql[i];
|
|
1510
|
+
const next = sql[i + 1];
|
|
1511
|
+
if (inLineComment) {
|
|
1512
|
+
out += ch;
|
|
1513
|
+
if (ch === "\n") inLineComment = false;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (inBlockComment) {
|
|
1517
|
+
out += ch;
|
|
1518
|
+
if (ch === "*" && next === "/") {
|
|
1519
|
+
out += next;
|
|
1520
|
+
inBlockComment = false;
|
|
1521
|
+
i += 1;
|
|
1522
|
+
}
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1526
|
+
out += ch + next;
|
|
1527
|
+
inLineComment = true;
|
|
1528
|
+
i += 1;
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1532
|
+
out += ch + next;
|
|
1533
|
+
inBlockComment = true;
|
|
1534
|
+
i += 1;
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1538
|
+
inSingle = !inSingle;
|
|
1539
|
+
out += ch;
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1543
|
+
inDouble = !inDouble;
|
|
1544
|
+
out += ch;
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1548
|
+
placeholder += 1;
|
|
1549
|
+
out += `$${placeholder}`;
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
out += ch;
|
|
1553
|
+
}
|
|
1554
|
+
return out;
|
|
1555
|
+
}
|
|
1556
|
+
function translateStatementForPostgres(stmt) {
|
|
1557
|
+
const normalized = normalizeStatement(stmt);
|
|
1558
|
+
if (normalized.kind === "named") {
|
|
1559
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1560
|
+
}
|
|
1561
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1562
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1563
|
+
return {
|
|
1564
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1565
|
+
args: coercedArgs
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
function shouldBypassPostgres(stmt) {
|
|
1569
|
+
const normalized = normalizeStatement(stmt);
|
|
1570
|
+
if (normalized.kind === "named") {
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1574
|
+
}
|
|
1575
|
+
function shouldFallbackOnError(error) {
|
|
1576
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1577
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1578
|
+
}
|
|
1579
|
+
function isReadQuery(sql) {
|
|
1580
|
+
const trimmed = sql.trimStart();
|
|
1581
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1582
|
+
}
|
|
1583
|
+
function buildRow(row, columns) {
|
|
1584
|
+
const values = columns.map((column) => row[column]);
|
|
1585
|
+
return Object.assign(values, row);
|
|
1586
|
+
}
|
|
1587
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1588
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1589
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1590
|
+
return {
|
|
1591
|
+
columns,
|
|
1592
|
+
columnTypes: columns.map(() => ""),
|
|
1593
|
+
rows: resultRows,
|
|
1594
|
+
rowsAffected,
|
|
1595
|
+
lastInsertRowid: void 0,
|
|
1596
|
+
toJSON() {
|
|
1597
|
+
return {
|
|
1598
|
+
columns,
|
|
1599
|
+
columnTypes: columns.map(() => ""),
|
|
1600
|
+
rows,
|
|
1601
|
+
rowsAffected,
|
|
1602
|
+
lastInsertRowid: void 0
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
async function loadPrismaClient() {
|
|
1608
|
+
if (!prismaClientPromise) {
|
|
1609
|
+
prismaClientPromise = (async () => {
|
|
1610
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1611
|
+
if (explicitPath) {
|
|
1612
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1613
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1614
|
+
if (!PrismaClient2) {
|
|
1615
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1616
|
+
}
|
|
1617
|
+
return new PrismaClient2();
|
|
1618
|
+
}
|
|
1619
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1620
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1621
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1622
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1623
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1624
|
+
if (!PrismaClient) {
|
|
1625
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1626
|
+
}
|
|
1627
|
+
return new PrismaClient();
|
|
1628
|
+
})();
|
|
1629
|
+
}
|
|
1630
|
+
return prismaClientPromise;
|
|
1631
|
+
}
|
|
1632
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1633
|
+
if (!compatibilityBootstrapPromise) {
|
|
1634
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1635
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1636
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1637
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1638
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1639
|
+
relation
|
|
1640
|
+
);
|
|
1641
|
+
if (!rows[0]?.regclass) {
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
await prisma.$executeRawUnsafe(
|
|
1645
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
})();
|
|
1649
|
+
}
|
|
1650
|
+
return compatibilityBootstrapPromise;
|
|
1651
|
+
}
|
|
1652
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1653
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1654
|
+
if (isReadQuery(translated.sql)) {
|
|
1655
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1656
|
+
translated.sql,
|
|
1657
|
+
...translated.args
|
|
1658
|
+
);
|
|
1659
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1660
|
+
}
|
|
1661
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1662
|
+
return buildResultSet([], rowsAffected);
|
|
1663
|
+
}
|
|
1664
|
+
function splitSqlStatements(sql) {
|
|
1665
|
+
const parts = [];
|
|
1666
|
+
let current = "";
|
|
1667
|
+
let inSingle = false;
|
|
1668
|
+
let inDouble = false;
|
|
1669
|
+
let inLineComment = false;
|
|
1670
|
+
let inBlockComment = false;
|
|
1671
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1672
|
+
const ch = sql[i];
|
|
1673
|
+
const next = sql[i + 1];
|
|
1674
|
+
if (inLineComment) {
|
|
1675
|
+
current += ch;
|
|
1676
|
+
if (ch === "\n") inLineComment = false;
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (inBlockComment) {
|
|
1680
|
+
current += ch;
|
|
1681
|
+
if (ch === "*" && next === "/") {
|
|
1682
|
+
current += next;
|
|
1683
|
+
inBlockComment = false;
|
|
1684
|
+
i += 1;
|
|
1685
|
+
}
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1689
|
+
current += ch + next;
|
|
1690
|
+
inLineComment = true;
|
|
1691
|
+
i += 1;
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1695
|
+
current += ch + next;
|
|
1696
|
+
inBlockComment = true;
|
|
1697
|
+
i += 1;
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1701
|
+
inSingle = !inSingle;
|
|
1702
|
+
current += ch;
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1706
|
+
inDouble = !inDouble;
|
|
1707
|
+
current += ch;
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1711
|
+
if (current.trim()) {
|
|
1712
|
+
parts.push(current.trim());
|
|
1713
|
+
}
|
|
1714
|
+
current = "";
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
current += ch;
|
|
1718
|
+
}
|
|
1719
|
+
if (current.trim()) {
|
|
1720
|
+
parts.push(current.trim());
|
|
1721
|
+
}
|
|
1722
|
+
return parts;
|
|
1723
|
+
}
|
|
1724
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1725
|
+
const prisma = await loadPrismaClient();
|
|
1726
|
+
await ensureCompatibilityViews(prisma);
|
|
1727
|
+
let closed = false;
|
|
1728
|
+
let adapter;
|
|
1729
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1730
|
+
if (!fallbackClient) {
|
|
1731
|
+
if (error) throw error;
|
|
1732
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1733
|
+
}
|
|
1734
|
+
if (error) {
|
|
1735
|
+
process.stderr.write(
|
|
1736
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1737
|
+
`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
return fallbackClient.execute(stmt);
|
|
1741
|
+
};
|
|
1742
|
+
adapter = {
|
|
1743
|
+
async execute(stmt) {
|
|
1744
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1745
|
+
return fallbackExecute(stmt);
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
if (shouldFallbackOnError(error)) {
|
|
1751
|
+
return fallbackExecute(stmt, error);
|
|
1752
|
+
}
|
|
1753
|
+
throw error;
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
async batch(stmts, mode) {
|
|
1757
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1758
|
+
if (!fallbackClient) {
|
|
1759
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1760
|
+
}
|
|
1761
|
+
return fallbackClient.batch(stmts, mode);
|
|
1762
|
+
}
|
|
1763
|
+
try {
|
|
1764
|
+
if (prisma.$transaction) {
|
|
1765
|
+
return await prisma.$transaction(async (tx) => {
|
|
1766
|
+
const results2 = [];
|
|
1767
|
+
for (const stmt of stmts) {
|
|
1768
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1769
|
+
}
|
|
1770
|
+
return results2;
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
const results = [];
|
|
1774
|
+
for (const stmt of stmts) {
|
|
1775
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1776
|
+
}
|
|
1777
|
+
return results;
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1780
|
+
process.stderr.write(
|
|
1781
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1782
|
+
`
|
|
1783
|
+
);
|
|
1784
|
+
return fallbackClient.batch(stmts, mode);
|
|
1785
|
+
}
|
|
1786
|
+
throw error;
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
async migrate(stmts) {
|
|
1790
|
+
if (fallbackClient) {
|
|
1791
|
+
return fallbackClient.migrate(stmts);
|
|
1792
|
+
}
|
|
1793
|
+
return adapter.batch(stmts, "deferred");
|
|
1794
|
+
},
|
|
1795
|
+
async transaction(mode) {
|
|
1796
|
+
if (!fallbackClient) {
|
|
1797
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1798
|
+
}
|
|
1799
|
+
return fallbackClient.transaction(mode);
|
|
1800
|
+
},
|
|
1801
|
+
async executeMultiple(sql) {
|
|
1802
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1803
|
+
return fallbackClient.executeMultiple(sql);
|
|
1804
|
+
}
|
|
1805
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1806
|
+
await adapter.execute(statement);
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
async sync() {
|
|
1810
|
+
if (fallbackClient) {
|
|
1811
|
+
return fallbackClient.sync();
|
|
1812
|
+
}
|
|
1813
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1814
|
+
},
|
|
1815
|
+
close() {
|
|
1816
|
+
closed = true;
|
|
1817
|
+
prismaClientPromise = null;
|
|
1818
|
+
compatibilityBootstrapPromise = null;
|
|
1819
|
+
void prisma.$disconnect?.();
|
|
1820
|
+
},
|
|
1821
|
+
get closed() {
|
|
1822
|
+
return closed;
|
|
1823
|
+
},
|
|
1824
|
+
get protocol() {
|
|
1825
|
+
return "prisma-postgres";
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
return adapter;
|
|
1829
|
+
}
|
|
1830
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1831
|
+
var init_database_adapter = __esm({
|
|
1832
|
+
"src/lib/database-adapter.ts"() {
|
|
1833
|
+
"use strict";
|
|
1834
|
+
VIEW_MAPPINGS = [
|
|
1835
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1836
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1837
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1838
|
+
{ view: "entities", source: "memory.entities" },
|
|
1839
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1840
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1841
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1842
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1843
|
+
{ view: "messages", source: "memory.messages" },
|
|
1844
|
+
{ view: "users", source: "wiki.users" },
|
|
1845
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1846
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1847
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1848
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1849
|
+
];
|
|
1850
|
+
UPSERT_KEYS = {
|
|
1851
|
+
memories: ["id"],
|
|
1852
|
+
tasks: ["id"],
|
|
1853
|
+
behaviors: ["id"],
|
|
1854
|
+
entities: ["id"],
|
|
1855
|
+
relationships: ["id"],
|
|
1856
|
+
entity_aliases: ["alias"],
|
|
1857
|
+
notifications: ["id"],
|
|
1858
|
+
messages: ["id"],
|
|
1859
|
+
users: ["id"],
|
|
1860
|
+
workspaces: ["id"],
|
|
1861
|
+
workspace_users: ["id"],
|
|
1862
|
+
documents: ["id"],
|
|
1863
|
+
chats: ["id"]
|
|
1864
|
+
};
|
|
1865
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1866
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1867
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1868
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1869
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1870
|
+
};
|
|
1871
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1872
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1873
|
+
);
|
|
1874
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1875
|
+
/\bPRAGMA\b/i,
|
|
1876
|
+
/\bsqlite_master\b/i,
|
|
1877
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1878
|
+
/\bMATCH\b/i,
|
|
1879
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1880
|
+
/\bjson_extract\s*\(/i,
|
|
1881
|
+
/\bjulianday\s*\(/i,
|
|
1882
|
+
/\bstrftime\s*\(/i,
|
|
1883
|
+
/\blast_insert_rowid\s*\(/i
|
|
1884
|
+
];
|
|
1885
|
+
prismaClientPromise = null;
|
|
1886
|
+
compatibilityBootstrapPromise = null;
|
|
1177
1887
|
}
|
|
1178
1888
|
});
|
|
1179
1889
|
|
|
1180
1890
|
// src/lib/exe-daemon-client.ts
|
|
1181
1891
|
import net from "net";
|
|
1182
|
-
import
|
|
1892
|
+
import os6 from "os";
|
|
1183
1893
|
import { spawn } from "child_process";
|
|
1184
1894
|
import { randomUUID } from "crypto";
|
|
1185
|
-
import { existsSync as
|
|
1186
|
-
import
|
|
1895
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1896
|
+
import path8 from "path";
|
|
1187
1897
|
import { fileURLToPath } from "url";
|
|
1188
1898
|
function handleData(chunk) {
|
|
1189
1899
|
_buffer += chunk.toString();
|
|
@@ -1211,9 +1921,9 @@ function handleData(chunk) {
|
|
|
1211
1921
|
}
|
|
1212
1922
|
}
|
|
1213
1923
|
function cleanupStaleFiles() {
|
|
1214
|
-
if (
|
|
1924
|
+
if (existsSync8(PID_PATH)) {
|
|
1215
1925
|
try {
|
|
1216
|
-
const pid = parseInt(
|
|
1926
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1217
1927
|
if (pid > 0) {
|
|
1218
1928
|
try {
|
|
1219
1929
|
process.kill(pid, 0);
|
|
@@ -1234,17 +1944,17 @@ function cleanupStaleFiles() {
|
|
|
1234
1944
|
}
|
|
1235
1945
|
}
|
|
1236
1946
|
function findPackageRoot() {
|
|
1237
|
-
let dir =
|
|
1238
|
-
const { root } =
|
|
1947
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1948
|
+
const { root } = path8.parse(dir);
|
|
1239
1949
|
while (dir !== root) {
|
|
1240
|
-
if (
|
|
1241
|
-
dir =
|
|
1950
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1951
|
+
dir = path8.dirname(dir);
|
|
1242
1952
|
}
|
|
1243
1953
|
return null;
|
|
1244
1954
|
}
|
|
1245
1955
|
function spawnDaemon() {
|
|
1246
|
-
const freeGB =
|
|
1247
|
-
const totalGB =
|
|
1956
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1957
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
1248
1958
|
if (totalGB <= 8) {
|
|
1249
1959
|
process.stderr.write(
|
|
1250
1960
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -1264,16 +1974,17 @@ function spawnDaemon() {
|
|
|
1264
1974
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1265
1975
|
return;
|
|
1266
1976
|
}
|
|
1267
|
-
const daemonPath =
|
|
1268
|
-
if (!
|
|
1977
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1978
|
+
if (!existsSync8(daemonPath)) {
|
|
1269
1979
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1270
1980
|
`);
|
|
1271
1981
|
return;
|
|
1272
1982
|
}
|
|
1273
1983
|
const resolvedPath = daemonPath;
|
|
1984
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1274
1985
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1275
1986
|
`);
|
|
1276
|
-
const logPath =
|
|
1987
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1277
1988
|
let stderrFd = "ignore";
|
|
1278
1989
|
try {
|
|
1279
1990
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1291,7 +2002,8 @@ function spawnDaemon() {
|
|
|
1291
2002
|
TMUX_PANE: void 0,
|
|
1292
2003
|
// Prevents resolveExeSession() from scoping to one session
|
|
1293
2004
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1294
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2005
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2006
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1295
2007
|
}
|
|
1296
2008
|
});
|
|
1297
2009
|
child.unref();
|
|
@@ -1401,13 +2113,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1401
2113
|
return;
|
|
1402
2114
|
}
|
|
1403
2115
|
const id = randomUUID();
|
|
2116
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1404
2117
|
const timer = setTimeout(() => {
|
|
1405
2118
|
_pending.delete(id);
|
|
1406
2119
|
resolve({ error: "Request timeout" });
|
|
1407
2120
|
}, timeoutMs);
|
|
1408
2121
|
_pending.set(id, { resolve, timer });
|
|
1409
2122
|
try {
|
|
1410
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2123
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1411
2124
|
} catch {
|
|
1412
2125
|
clearTimeout(timer);
|
|
1413
2126
|
_pending.delete(id);
|
|
@@ -1424,74 +2137,123 @@ async function pingDaemon() {
|
|
|
1424
2137
|
return null;
|
|
1425
2138
|
}
|
|
1426
2139
|
function killAndRespawnDaemon() {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2140
|
+
if (!acquireSpawnLock()) {
|
|
2141
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
2142
|
+
if (_socket) {
|
|
2143
|
+
_socket.destroy();
|
|
2144
|
+
_socket = null;
|
|
2145
|
+
}
|
|
2146
|
+
_connected = false;
|
|
2147
|
+
_buffer = "";
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
try {
|
|
2151
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2152
|
+
if (existsSync8(PID_PATH)) {
|
|
2153
|
+
try {
|
|
2154
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
2155
|
+
if (pid > 0) {
|
|
2156
|
+
try {
|
|
2157
|
+
process.kill(pid, "SIGKILL");
|
|
2158
|
+
} catch {
|
|
2159
|
+
}
|
|
1435
2160
|
}
|
|
2161
|
+
} catch {
|
|
1436
2162
|
}
|
|
2163
|
+
}
|
|
2164
|
+
if (_socket) {
|
|
2165
|
+
_socket.destroy();
|
|
2166
|
+
_socket = null;
|
|
2167
|
+
}
|
|
2168
|
+
_connected = false;
|
|
2169
|
+
_buffer = "";
|
|
2170
|
+
try {
|
|
2171
|
+
unlinkSync2(PID_PATH);
|
|
1437
2172
|
} catch {
|
|
1438
2173
|
}
|
|
2174
|
+
try {
|
|
2175
|
+
unlinkSync2(SOCKET_PATH);
|
|
2176
|
+
} catch {
|
|
2177
|
+
}
|
|
2178
|
+
spawnDaemon();
|
|
2179
|
+
} finally {
|
|
2180
|
+
releaseSpawnLock();
|
|
1439
2181
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
_socket = null;
|
|
1443
|
-
}
|
|
1444
|
-
_connected = false;
|
|
1445
|
-
_buffer = "";
|
|
2182
|
+
}
|
|
2183
|
+
function isDaemonTooYoung() {
|
|
1446
2184
|
try {
|
|
1447
|
-
|
|
2185
|
+
const stat = statSync(PID_PATH);
|
|
2186
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
1448
2187
|
} catch {
|
|
2188
|
+
return false;
|
|
1449
2189
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2190
|
+
}
|
|
2191
|
+
async function retryThenRestart(doRequest, label) {
|
|
2192
|
+
const result = await doRequest();
|
|
2193
|
+
if (!result.error) {
|
|
2194
|
+
_consecutiveFailures = 0;
|
|
2195
|
+
return result;
|
|
1453
2196
|
}
|
|
1454
|
-
|
|
2197
|
+
_consecutiveFailures++;
|
|
2198
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
2199
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
2200
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
2201
|
+
`);
|
|
2202
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2203
|
+
if (!_connected) {
|
|
2204
|
+
if (!await connectToSocket()) continue;
|
|
2205
|
+
}
|
|
2206
|
+
const retry = await doRequest();
|
|
2207
|
+
if (!retry.error) {
|
|
2208
|
+
_consecutiveFailures = 0;
|
|
2209
|
+
return retry;
|
|
2210
|
+
}
|
|
2211
|
+
_consecutiveFailures++;
|
|
2212
|
+
}
|
|
2213
|
+
if (isDaemonTooYoung()) {
|
|
2214
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
2215
|
+
`);
|
|
2216
|
+
return { error: result.error };
|
|
2217
|
+
}
|
|
2218
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
2219
|
+
`);
|
|
2220
|
+
killAndRespawnDaemon();
|
|
2221
|
+
const start = Date.now();
|
|
2222
|
+
let delay2 = 200;
|
|
2223
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2224
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2225
|
+
if (await connectToSocket()) break;
|
|
2226
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2227
|
+
}
|
|
2228
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
2229
|
+
const final = await doRequest();
|
|
2230
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
2231
|
+
return final;
|
|
1455
2232
|
}
|
|
1456
2233
|
async function embedViaClient(text, priority = "high") {
|
|
1457
2234
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1458
2235
|
_requestCount++;
|
|
1459
2236
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1460
2237
|
const health = await pingDaemon();
|
|
1461
|
-
if (!health) {
|
|
2238
|
+
if (!health && !isDaemonTooYoung()) {
|
|
1462
2239
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1463
2240
|
`);
|
|
1464
2241
|
killAndRespawnDaemon();
|
|
1465
2242
|
const start = Date.now();
|
|
1466
|
-
let
|
|
2243
|
+
let d = 200;
|
|
1467
2244
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1468
|
-
await new Promise((r) => setTimeout(r,
|
|
2245
|
+
await new Promise((r) => setTimeout(r, d));
|
|
1469
2246
|
if (await connectToSocket()) break;
|
|
1470
|
-
|
|
2247
|
+
d = Math.min(d * 2, 3e3);
|
|
1471
2248
|
}
|
|
1472
2249
|
if (!_connected) return null;
|
|
1473
2250
|
}
|
|
1474
2251
|
}
|
|
1475
|
-
const result = await
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
killAndRespawnDaemon();
|
|
1481
|
-
const start = Date.now();
|
|
1482
|
-
let delay2 = 200;
|
|
1483
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1484
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
1485
|
-
if (await connectToSocket()) break;
|
|
1486
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1487
|
-
}
|
|
1488
|
-
if (!_connected) return null;
|
|
1489
|
-
const retry = await sendRequest([text], priority);
|
|
1490
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
1491
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
1492
|
-
`);
|
|
1493
|
-
}
|
|
1494
|
-
return null;
|
|
2252
|
+
const result = await retryThenRestart(
|
|
2253
|
+
() => sendRequest([text], priority),
|
|
2254
|
+
"Embed"
|
|
2255
|
+
);
|
|
2256
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
1495
2257
|
}
|
|
1496
2258
|
function disconnectClient() {
|
|
1497
2259
|
if (_socket) {
|
|
@@ -1509,22 +2271,28 @@ function disconnectClient() {
|
|
|
1509
2271
|
function isClientConnected() {
|
|
1510
2272
|
return _connected;
|
|
1511
2273
|
}
|
|
1512
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
2274
|
+
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;
|
|
1513
2275
|
var init_exe_daemon_client = __esm({
|
|
1514
2276
|
"src/lib/exe-daemon-client.ts"() {
|
|
1515
2277
|
"use strict";
|
|
1516
2278
|
init_config();
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2279
|
+
init_daemon_auth();
|
|
2280
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
2281
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
2282
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1520
2283
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1521
2284
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1522
2285
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2286
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1523
2287
|
_socket = null;
|
|
1524
2288
|
_connected = false;
|
|
1525
2289
|
_buffer = "";
|
|
1526
2290
|
_requestCount = 0;
|
|
2291
|
+
_consecutiveFailures = 0;
|
|
1527
2292
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2293
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
2294
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
2295
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
1528
2296
|
_pending = /* @__PURE__ */ new Map();
|
|
1529
2297
|
MAX_BUFFER = 1e7;
|
|
1530
2298
|
}
|
|
@@ -1536,7 +2304,7 @@ __export(db_daemon_client_exports, {
|
|
|
1536
2304
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1537
2305
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1538
2306
|
});
|
|
1539
|
-
function
|
|
2307
|
+
function normalizeStatement2(stmt) {
|
|
1540
2308
|
if (typeof stmt === "string") {
|
|
1541
2309
|
return { sql: stmt, args: [] };
|
|
1542
2310
|
}
|
|
@@ -1560,7 +2328,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1560
2328
|
if (!_useDaemon || !isClientConnected()) {
|
|
1561
2329
|
return fallbackClient.execute(stmt);
|
|
1562
2330
|
}
|
|
1563
|
-
const { sql, args } =
|
|
2331
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1564
2332
|
const response = await sendDaemonRequest({
|
|
1565
2333
|
type: "db-execute",
|
|
1566
2334
|
sql,
|
|
@@ -1585,7 +2353,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1585
2353
|
if (!_useDaemon || !isClientConnected()) {
|
|
1586
2354
|
return fallbackClient.batch(stmts, mode);
|
|
1587
2355
|
}
|
|
1588
|
-
const statements = stmts.map(
|
|
2356
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1589
2357
|
const response = await sendDaemonRequest({
|
|
1590
2358
|
type: "db-batch",
|
|
1591
2359
|
statements,
|
|
@@ -1680,6 +2448,18 @@ __export(database_exports, {
|
|
|
1680
2448
|
});
|
|
1681
2449
|
import { createClient } from "@libsql/client";
|
|
1682
2450
|
async function initDatabase(config) {
|
|
2451
|
+
if (_walCheckpointTimer) {
|
|
2452
|
+
clearInterval(_walCheckpointTimer);
|
|
2453
|
+
_walCheckpointTimer = null;
|
|
2454
|
+
}
|
|
2455
|
+
if (_daemonClient) {
|
|
2456
|
+
_daemonClient.close();
|
|
2457
|
+
_daemonClient = null;
|
|
2458
|
+
}
|
|
2459
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2460
|
+
_adapterClient.close();
|
|
2461
|
+
}
|
|
2462
|
+
_adapterClient = null;
|
|
1683
2463
|
if (_client) {
|
|
1684
2464
|
_client.close();
|
|
1685
2465
|
_client = null;
|
|
@@ -1693,6 +2473,7 @@ async function initDatabase(config) {
|
|
|
1693
2473
|
}
|
|
1694
2474
|
_client = createClient(opts);
|
|
1695
2475
|
_resilientClient = wrapWithRetry(_client);
|
|
2476
|
+
_adapterClient = _resilientClient;
|
|
1696
2477
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1697
2478
|
});
|
|
1698
2479
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1703,14 +2484,20 @@ async function initDatabase(config) {
|
|
|
1703
2484
|
});
|
|
1704
2485
|
}, 3e4);
|
|
1705
2486
|
_walCheckpointTimer.unref();
|
|
2487
|
+
if (process.env.DATABASE_URL) {
|
|
2488
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2489
|
+
}
|
|
1706
2490
|
}
|
|
1707
2491
|
function isInitialized() {
|
|
1708
|
-
return _client !== null;
|
|
2492
|
+
return _adapterClient !== null || _client !== null;
|
|
1709
2493
|
}
|
|
1710
2494
|
function getClient() {
|
|
1711
|
-
if (!
|
|
2495
|
+
if (!_adapterClient) {
|
|
1712
2496
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1713
2497
|
}
|
|
2498
|
+
if (process.env.DATABASE_URL) {
|
|
2499
|
+
return _adapterClient;
|
|
2500
|
+
}
|
|
1714
2501
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1715
2502
|
return _resilientClient;
|
|
1716
2503
|
}
|
|
@@ -1720,6 +2507,7 @@ function getClient() {
|
|
|
1720
2507
|
return _resilientClient;
|
|
1721
2508
|
}
|
|
1722
2509
|
async function initDaemonClient() {
|
|
2510
|
+
if (process.env.DATABASE_URL) return;
|
|
1723
2511
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1724
2512
|
if (!_resilientClient) return;
|
|
1725
2513
|
try {
|
|
@@ -2016,6 +2804,7 @@ async function ensureSchema() {
|
|
|
2016
2804
|
project TEXT NOT NULL,
|
|
2017
2805
|
summary TEXT NOT NULL,
|
|
2018
2806
|
task_file TEXT,
|
|
2807
|
+
session_scope TEXT,
|
|
2019
2808
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2020
2809
|
created_at TEXT NOT NULL
|
|
2021
2810
|
);
|
|
@@ -2024,7 +2813,7 @@ async function ensureSchema() {
|
|
|
2024
2813
|
ON notifications(read);
|
|
2025
2814
|
|
|
2026
2815
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2027
|
-
ON notifications(agent_id);
|
|
2816
|
+
ON notifications(agent_id, session_scope);
|
|
2028
2817
|
|
|
2029
2818
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2030
2819
|
ON notifications(task_file);
|
|
@@ -2062,6 +2851,7 @@ async function ensureSchema() {
|
|
|
2062
2851
|
target_agent TEXT NOT NULL,
|
|
2063
2852
|
target_project TEXT,
|
|
2064
2853
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2854
|
+
session_scope TEXT,
|
|
2065
2855
|
content TEXT NOT NULL,
|
|
2066
2856
|
priority TEXT DEFAULT 'normal',
|
|
2067
2857
|
status TEXT DEFAULT 'pending',
|
|
@@ -2075,10 +2865,31 @@ async function ensureSchema() {
|
|
|
2075
2865
|
);
|
|
2076
2866
|
|
|
2077
2867
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2078
|
-
ON messages(target_agent, status);
|
|
2868
|
+
ON messages(target_agent, session_scope, status);
|
|
2079
2869
|
|
|
2080
2870
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2081
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2871
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2872
|
+
`);
|
|
2873
|
+
try {
|
|
2874
|
+
await client.execute({
|
|
2875
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2876
|
+
args: []
|
|
2877
|
+
});
|
|
2878
|
+
} catch {
|
|
2879
|
+
}
|
|
2880
|
+
try {
|
|
2881
|
+
await client.execute({
|
|
2882
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2883
|
+
args: []
|
|
2884
|
+
});
|
|
2885
|
+
} catch {
|
|
2886
|
+
}
|
|
2887
|
+
await client.executeMultiple(`
|
|
2888
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2889
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2890
|
+
|
|
2891
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2892
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2082
2893
|
`);
|
|
2083
2894
|
try {
|
|
2084
2895
|
await client.execute({
|
|
@@ -2662,46 +3473,66 @@ async function ensureSchema() {
|
|
|
2662
3473
|
} catch {
|
|
2663
3474
|
}
|
|
2664
3475
|
}
|
|
3476
|
+
try {
|
|
3477
|
+
await client.execute({
|
|
3478
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3479
|
+
args: []
|
|
3480
|
+
});
|
|
3481
|
+
} catch {
|
|
3482
|
+
}
|
|
2665
3483
|
}
|
|
2666
3484
|
async function disposeDatabase() {
|
|
3485
|
+
if (_walCheckpointTimer) {
|
|
3486
|
+
clearInterval(_walCheckpointTimer);
|
|
3487
|
+
_walCheckpointTimer = null;
|
|
3488
|
+
}
|
|
2667
3489
|
if (_daemonClient) {
|
|
2668
3490
|
_daemonClient.close();
|
|
2669
3491
|
_daemonClient = null;
|
|
2670
3492
|
}
|
|
3493
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3494
|
+
_adapterClient.close();
|
|
3495
|
+
}
|
|
3496
|
+
_adapterClient = null;
|
|
2671
3497
|
if (_client) {
|
|
2672
3498
|
_client.close();
|
|
2673
3499
|
_client = null;
|
|
2674
3500
|
_resilientClient = null;
|
|
2675
3501
|
}
|
|
2676
3502
|
}
|
|
2677
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3503
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2678
3504
|
var init_database = __esm({
|
|
2679
3505
|
"src/lib/database.ts"() {
|
|
2680
3506
|
"use strict";
|
|
2681
3507
|
init_db_retry();
|
|
2682
3508
|
init_employees();
|
|
3509
|
+
init_database_adapter();
|
|
2683
3510
|
_client = null;
|
|
2684
3511
|
_resilientClient = null;
|
|
2685
3512
|
_walCheckpointTimer = null;
|
|
2686
3513
|
_daemonClient = null;
|
|
3514
|
+
_adapterClient = null;
|
|
2687
3515
|
initTurso = initDatabase;
|
|
2688
3516
|
disposeTurso = disposeDatabase;
|
|
2689
3517
|
}
|
|
2690
3518
|
});
|
|
2691
3519
|
|
|
2692
3520
|
// src/lib/license.ts
|
|
2693
|
-
import { readFileSync as
|
|
3521
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2694
3522
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2695
|
-
import
|
|
3523
|
+
import { createRequire as createRequire2 } from "module";
|
|
3524
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3525
|
+
import os7 from "os";
|
|
3526
|
+
import path9 from "path";
|
|
2696
3527
|
import { jwtVerify, importSPKI } from "jose";
|
|
2697
3528
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2698
3529
|
var init_license = __esm({
|
|
2699
3530
|
"src/lib/license.ts"() {
|
|
2700
3531
|
"use strict";
|
|
2701
3532
|
init_config();
|
|
2702
|
-
LICENSE_PATH =
|
|
2703
|
-
CACHE_PATH =
|
|
2704
|
-
DEVICE_ID_PATH =
|
|
3533
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3534
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3535
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2705
3536
|
PLAN_LIMITS = {
|
|
2706
3537
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2707
3538
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2713,12 +3544,12 @@ var init_license = __esm({
|
|
|
2713
3544
|
});
|
|
2714
3545
|
|
|
2715
3546
|
// src/lib/plan-limits.ts
|
|
2716
|
-
import { readFileSync as
|
|
2717
|
-
import
|
|
3547
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3548
|
+
import path10 from "path";
|
|
2718
3549
|
function getLicenseSync() {
|
|
2719
3550
|
try {
|
|
2720
|
-
if (!
|
|
2721
|
-
const raw = JSON.parse(
|
|
3551
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3552
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2722
3553
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2723
3554
|
const parts = raw.token.split(".");
|
|
2724
3555
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2756,8 +3587,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2756
3587
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2757
3588
|
let count = 0;
|
|
2758
3589
|
try {
|
|
2759
|
-
if (
|
|
2760
|
-
const raw =
|
|
3590
|
+
if (existsSync10(filePath)) {
|
|
3591
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2761
3592
|
const employees = JSON.parse(raw);
|
|
2762
3593
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2763
3594
|
}
|
|
@@ -2786,29 +3617,63 @@ var init_plan_limits = __esm({
|
|
|
2786
3617
|
this.name = "PlanLimitError";
|
|
2787
3618
|
}
|
|
2788
3619
|
};
|
|
2789
|
-
CACHE_PATH2 =
|
|
3620
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
|
|
3624
|
+
// src/lib/task-scope.ts
|
|
3625
|
+
function getCurrentSessionScope() {
|
|
3626
|
+
try {
|
|
3627
|
+
return resolveExeSession();
|
|
3628
|
+
} catch {
|
|
3629
|
+
return null;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3633
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3634
|
+
if (!scope) return { sql: "", args: [] };
|
|
3635
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3636
|
+
return {
|
|
3637
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3638
|
+
args: [scope]
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
3642
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3643
|
+
if (!scope) return { sql: "", args: [] };
|
|
3644
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3645
|
+
return {
|
|
3646
|
+
sql: ` AND ${col} = ?`,
|
|
3647
|
+
args: [scope]
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
var init_task_scope = __esm({
|
|
3651
|
+
"src/lib/task-scope.ts"() {
|
|
3652
|
+
"use strict";
|
|
3653
|
+
init_tmux_routing();
|
|
2790
3654
|
}
|
|
2791
3655
|
});
|
|
2792
3656
|
|
|
2793
3657
|
// src/lib/notifications.ts
|
|
2794
|
-
import
|
|
2795
|
-
import
|
|
2796
|
-
import
|
|
3658
|
+
import crypto2 from "crypto";
|
|
3659
|
+
import path11 from "path";
|
|
3660
|
+
import os8 from "os";
|
|
2797
3661
|
import {
|
|
2798
|
-
readFileSync as
|
|
3662
|
+
readFileSync as readFileSync10,
|
|
2799
3663
|
readdirSync,
|
|
2800
3664
|
unlinkSync as unlinkSync3,
|
|
2801
|
-
existsSync as
|
|
3665
|
+
existsSync as existsSync11,
|
|
2802
3666
|
rmdirSync
|
|
2803
3667
|
} from "fs";
|
|
2804
3668
|
async function writeNotification(notification) {
|
|
2805
3669
|
try {
|
|
2806
3670
|
const client = getClient();
|
|
2807
|
-
const id =
|
|
3671
|
+
const id = crypto2.randomUUID();
|
|
2808
3672
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3673
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2809
3674
|
await client.execute({
|
|
2810
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2811
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3675
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3676
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2812
3677
|
args: [
|
|
2813
3678
|
id,
|
|
2814
3679
|
notification.agentId,
|
|
@@ -2817,6 +3682,7 @@ async function writeNotification(notification) {
|
|
|
2817
3682
|
notification.project,
|
|
2818
3683
|
notification.summary,
|
|
2819
3684
|
notification.taskFile ?? null,
|
|
3685
|
+
sessionScope,
|
|
2820
3686
|
now
|
|
2821
3687
|
]
|
|
2822
3688
|
});
|
|
@@ -2825,12 +3691,14 @@ async function writeNotification(notification) {
|
|
|
2825
3691
|
`);
|
|
2826
3692
|
}
|
|
2827
3693
|
}
|
|
2828
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3694
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2829
3695
|
try {
|
|
2830
3696
|
const client = getClient();
|
|
3697
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2831
3698
|
await client.execute({
|
|
2832
|
-
sql:
|
|
2833
|
-
|
|
3699
|
+
sql: `UPDATE notifications SET read = 1
|
|
3700
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3701
|
+
args: [taskFile, ...scope.args]
|
|
2834
3702
|
});
|
|
2835
3703
|
} catch {
|
|
2836
3704
|
}
|
|
@@ -2839,6 +3707,7 @@ var init_notifications = __esm({
|
|
|
2839
3707
|
"src/lib/notifications.ts"() {
|
|
2840
3708
|
"use strict";
|
|
2841
3709
|
init_database();
|
|
3710
|
+
init_task_scope();
|
|
2842
3711
|
}
|
|
2843
3712
|
});
|
|
2844
3713
|
|
|
@@ -2855,7 +3724,7 @@ __export(session_kill_telemetry_exports, {
|
|
|
2855
3724
|
recordSessionKill: () => recordSessionKill,
|
|
2856
3725
|
sumTokensSavedSince: () => sumTokensSavedSince
|
|
2857
3726
|
});
|
|
2858
|
-
import
|
|
3727
|
+
import crypto3 from "crypto";
|
|
2859
3728
|
async function recordSessionKill(input) {
|
|
2860
3729
|
try {
|
|
2861
3730
|
const client = getClient();
|
|
@@ -2865,7 +3734,7 @@ async function recordSessionKill(input) {
|
|
|
2865
3734
|
ticks_idle, estimated_tokens_saved)
|
|
2866
3735
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2867
3736
|
args: [
|
|
2868
|
-
|
|
3737
|
+
crypto3.randomUUID(),
|
|
2869
3738
|
input.sessionName,
|
|
2870
3739
|
input.agentId,
|
|
2871
3740
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2946,30 +3815,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
2946
3815
|
}
|
|
2947
3816
|
});
|
|
2948
3817
|
|
|
2949
|
-
// src/lib/task-scope.ts
|
|
2950
|
-
function getCurrentSessionScope() {
|
|
2951
|
-
try {
|
|
2952
|
-
return resolveExeSession();
|
|
2953
|
-
} catch {
|
|
2954
|
-
return null;
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2958
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2959
|
-
if (!scope) return { sql: "", args: [] };
|
|
2960
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2961
|
-
return {
|
|
2962
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2963
|
-
args: [scope]
|
|
2964
|
-
};
|
|
2965
|
-
}
|
|
2966
|
-
var init_task_scope = __esm({
|
|
2967
|
-
"src/lib/task-scope.ts"() {
|
|
2968
|
-
"use strict";
|
|
2969
|
-
init_tmux_routing();
|
|
2970
|
-
}
|
|
2971
|
-
});
|
|
2972
|
-
|
|
2973
3818
|
// src/lib/state-bus.ts
|
|
2974
3819
|
var StateBus, orgBus;
|
|
2975
3820
|
var init_state_bus = __esm({
|
|
@@ -3026,12 +3871,12 @@ var init_state_bus = __esm({
|
|
|
3026
3871
|
});
|
|
3027
3872
|
|
|
3028
3873
|
// src/lib/tasks-crud.ts
|
|
3029
|
-
import
|
|
3030
|
-
import
|
|
3031
|
-
import
|
|
3874
|
+
import crypto4 from "crypto";
|
|
3875
|
+
import path12 from "path";
|
|
3876
|
+
import os9 from "os";
|
|
3032
3877
|
import { execSync as execSync5 } from "child_process";
|
|
3033
3878
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3034
|
-
import { existsSync as
|
|
3879
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3035
3880
|
async function writeCheckpoint(input) {
|
|
3036
3881
|
const client = getClient();
|
|
3037
3882
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3147,7 +3992,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3147
3992
|
}
|
|
3148
3993
|
async function createTaskCore(input) {
|
|
3149
3994
|
const client = getClient();
|
|
3150
|
-
const id =
|
|
3995
|
+
const id = crypto4.randomUUID();
|
|
3151
3996
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3152
3997
|
const slug = slugify(input.title);
|
|
3153
3998
|
let earlySessionScope = null;
|
|
@@ -3206,8 +4051,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3206
4051
|
}
|
|
3207
4052
|
if (input.baseDir) {
|
|
3208
4053
|
try {
|
|
3209
|
-
await mkdir3(
|
|
3210
|
-
await mkdir3(
|
|
4054
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4055
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3211
4056
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3212
4057
|
await ensureGitignoreExe(input.baseDir);
|
|
3213
4058
|
} catch {
|
|
@@ -3243,13 +4088,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3243
4088
|
});
|
|
3244
4089
|
if (input.baseDir) {
|
|
3245
4090
|
try {
|
|
3246
|
-
const EXE_OS_DIR =
|
|
3247
|
-
const mdPath =
|
|
3248
|
-
const mdDir =
|
|
3249
|
-
if (!
|
|
4091
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
4092
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
4093
|
+
const mdDir = path12.dirname(mdPath);
|
|
4094
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3250
4095
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3251
4096
|
const mdContent = `# ${input.title}
|
|
3252
4097
|
|
|
4098
|
+
## MANDATORY: When done
|
|
4099
|
+
|
|
4100
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4101
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4102
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4103
|
+
|
|
3253
4104
|
**ID:** ${id}
|
|
3254
4105
|
**Status:** ${initialStatus}
|
|
3255
4106
|
**Priority:** ${input.priority}
|
|
@@ -3263,12 +4114,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3263
4114
|
## Context
|
|
3264
4115
|
|
|
3265
4116
|
${input.context}
|
|
3266
|
-
|
|
3267
|
-
## MANDATORY: When done
|
|
3268
|
-
|
|
3269
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3270
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3271
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3272
4117
|
`;
|
|
3273
4118
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3274
4119
|
} catch (err) {
|
|
@@ -3517,7 +4362,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3517
4362
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3518
4363
|
} catch {
|
|
3519
4364
|
}
|
|
3520
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4365
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3521
4366
|
try {
|
|
3522
4367
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3523
4368
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3546,9 +4391,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3546
4391
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3547
4392
|
}
|
|
3548
4393
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3549
|
-
const archPath =
|
|
4394
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3550
4395
|
try {
|
|
3551
|
-
if (
|
|
4396
|
+
if (existsSync12(archPath)) return;
|
|
3552
4397
|
const template = [
|
|
3553
4398
|
`# ${projectName} \u2014 System Architecture`,
|
|
3554
4399
|
"",
|
|
@@ -3581,10 +4426,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3581
4426
|
}
|
|
3582
4427
|
}
|
|
3583
4428
|
async function ensureGitignoreExe(baseDir) {
|
|
3584
|
-
const gitignorePath =
|
|
4429
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3585
4430
|
try {
|
|
3586
|
-
if (
|
|
3587
|
-
const content =
|
|
4431
|
+
if (existsSync12(gitignorePath)) {
|
|
4432
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3588
4433
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3589
4434
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3590
4435
|
} else {
|
|
@@ -3627,8 +4472,8 @@ __export(tasks_review_exports, {
|
|
|
3627
4472
|
isStale: () => isStale,
|
|
3628
4473
|
listPendingReviews: () => listPendingReviews
|
|
3629
4474
|
});
|
|
3630
|
-
import
|
|
3631
|
-
import { existsSync as
|
|
4475
|
+
import path13 from "path";
|
|
4476
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3632
4477
|
function formatAge(isoTimestamp) {
|
|
3633
4478
|
if (!isoTimestamp) return "";
|
|
3634
4479
|
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
@@ -3646,54 +4491,38 @@ function isStale(isoTimestamp) {
|
|
|
3646
4491
|
}
|
|
3647
4492
|
async function countPendingReviews(sessionScope) {
|
|
3648
4493
|
const client = getClient();
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
args: [sessionScope]
|
|
3653
|
-
});
|
|
3654
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3655
|
-
}
|
|
4494
|
+
const scope = strictSessionScopeFilter(
|
|
4495
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4496
|
+
);
|
|
3656
4497
|
const result = await client.execute({
|
|
3657
|
-
sql:
|
|
3658
|
-
|
|
4498
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4499
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4500
|
+
args: [...scope.args]
|
|
3659
4501
|
});
|
|
3660
4502
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3661
4503
|
}
|
|
3662
4504
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3663
4505
|
const client = getClient();
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3668
|
-
AND session_scope = ?`,
|
|
3669
|
-
args: [sinceIso, sessionScope]
|
|
3670
|
-
});
|
|
3671
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3672
|
-
}
|
|
4506
|
+
const scope = strictSessionScopeFilter(
|
|
4507
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4508
|
+
);
|
|
3673
4509
|
const result = await client.execute({
|
|
3674
4510
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3675
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3676
|
-
args: [sinceIso]
|
|
4511
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4512
|
+
args: [sinceIso, ...scope.args]
|
|
3677
4513
|
});
|
|
3678
4514
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3679
4515
|
}
|
|
3680
4516
|
async function listPendingReviews(limit, sessionScope) {
|
|
3681
4517
|
const client = getClient();
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
WHERE status = 'needs_review'
|
|
3686
|
-
AND session_scope = ?
|
|
3687
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3688
|
-
args: [sessionScope, limit]
|
|
3689
|
-
});
|
|
3690
|
-
return result2.rows;
|
|
3691
|
-
}
|
|
4518
|
+
const scope = strictSessionScopeFilter(
|
|
4519
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4520
|
+
);
|
|
3692
4521
|
const result = await client.execute({
|
|
3693
4522
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3694
|
-
WHERE status = 'needs_review'
|
|
4523
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3695
4524
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3696
|
-
args: [limit]
|
|
4525
|
+
args: [...scope.args, limit]
|
|
3697
4526
|
});
|
|
3698
4527
|
return result.rows;
|
|
3699
4528
|
}
|
|
@@ -3705,7 +4534,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3705
4534
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3706
4535
|
AND assigned_by = 'system'
|
|
3707
4536
|
AND title LIKE 'Review:%'
|
|
3708
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4537
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3709
4538
|
args: [now]
|
|
3710
4539
|
});
|
|
3711
4540
|
const r1b = await client.execute({
|
|
@@ -3913,11 +4742,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3913
4742
|
);
|
|
3914
4743
|
}
|
|
3915
4744
|
try {
|
|
3916
|
-
const cacheDir =
|
|
3917
|
-
if (
|
|
4745
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4746
|
+
if (existsSync13(cacheDir)) {
|
|
3918
4747
|
for (const f of readdirSync2(cacheDir)) {
|
|
3919
4748
|
if (f.startsWith("review-notified-")) {
|
|
3920
|
-
unlinkSync4(
|
|
4749
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3921
4750
|
}
|
|
3922
4751
|
}
|
|
3923
4752
|
}
|
|
@@ -3934,11 +4763,12 @@ var init_tasks_review = __esm({
|
|
|
3934
4763
|
init_tmux_routing();
|
|
3935
4764
|
init_session_key();
|
|
3936
4765
|
init_state_bus();
|
|
4766
|
+
init_task_scope();
|
|
3937
4767
|
}
|
|
3938
4768
|
});
|
|
3939
4769
|
|
|
3940
4770
|
// src/lib/tasks-chain.ts
|
|
3941
|
-
import
|
|
4771
|
+
import path14 from "path";
|
|
3942
4772
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3943
4773
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3944
4774
|
const client = getClient();
|
|
@@ -3955,7 +4785,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3955
4785
|
});
|
|
3956
4786
|
for (const ur of unblockedRows.rows) {
|
|
3957
4787
|
try {
|
|
3958
|
-
const ubFile =
|
|
4788
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3959
4789
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3960
4790
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3961
4791
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3990,7 +4820,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3990
4820
|
const scScope = sessionScopeFilter();
|
|
3991
4821
|
const remaining = await client.execute({
|
|
3992
4822
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3993
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4823
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3994
4824
|
args: [parentTaskId, ...scScope.args]
|
|
3995
4825
|
});
|
|
3996
4826
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4024,7 +4854,7 @@ var init_tasks_chain = __esm({
|
|
|
4024
4854
|
|
|
4025
4855
|
// src/lib/project-name.ts
|
|
4026
4856
|
import { execSync as execSync6 } from "child_process";
|
|
4027
|
-
import
|
|
4857
|
+
import path15 from "path";
|
|
4028
4858
|
function getProjectName(cwd) {
|
|
4029
4859
|
const dir = cwd ?? process.cwd();
|
|
4030
4860
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4037,7 +4867,7 @@ function getProjectName(cwd) {
|
|
|
4037
4867
|
timeout: 2e3,
|
|
4038
4868
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4039
4869
|
}).trim();
|
|
4040
|
-
repoRoot =
|
|
4870
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
4041
4871
|
} catch {
|
|
4042
4872
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4043
4873
|
cwd: dir,
|
|
@@ -4046,11 +4876,11 @@ function getProjectName(cwd) {
|
|
|
4046
4876
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4047
4877
|
}).trim();
|
|
4048
4878
|
}
|
|
4049
|
-
_cached2 =
|
|
4879
|
+
_cached2 = path15.basename(repoRoot);
|
|
4050
4880
|
_cachedCwd = dir;
|
|
4051
4881
|
return _cached2;
|
|
4052
4882
|
} catch {
|
|
4053
|
-
_cached2 =
|
|
4883
|
+
_cached2 = path15.basename(dir);
|
|
4054
4884
|
_cachedCwd = dir;
|
|
4055
4885
|
return _cached2;
|
|
4056
4886
|
}
|
|
@@ -4193,10 +5023,10 @@ var init_tasks_notify = __esm({
|
|
|
4193
5023
|
});
|
|
4194
5024
|
|
|
4195
5025
|
// src/lib/behaviors.ts
|
|
4196
|
-
import
|
|
5026
|
+
import crypto5 from "crypto";
|
|
4197
5027
|
async function storeBehavior(opts) {
|
|
4198
5028
|
const client = getClient();
|
|
4199
|
-
const id =
|
|
5029
|
+
const id = crypto5.randomUUID();
|
|
4200
5030
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4201
5031
|
await client.execute({
|
|
4202
5032
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4225,7 +5055,7 @@ __export(skill_learning_exports, {
|
|
|
4225
5055
|
storeTrajectory: () => storeTrajectory,
|
|
4226
5056
|
sweepTrajectories: () => sweepTrajectories
|
|
4227
5057
|
});
|
|
4228
|
-
import
|
|
5058
|
+
import crypto6 from "crypto";
|
|
4229
5059
|
async function extractTrajectory(taskId, agentId) {
|
|
4230
5060
|
const client = getClient();
|
|
4231
5061
|
const result = await client.execute({
|
|
@@ -4254,11 +5084,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4254
5084
|
return signature;
|
|
4255
5085
|
}
|
|
4256
5086
|
function hashSignature(signature) {
|
|
4257
|
-
return
|
|
5087
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4258
5088
|
}
|
|
4259
5089
|
async function storeTrajectory(opts) {
|
|
4260
5090
|
const client = getClient();
|
|
4261
|
-
const id =
|
|
5091
|
+
const id = crypto6.randomUUID();
|
|
4262
5092
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4263
5093
|
const signatureHash = hashSignature(opts.signature);
|
|
4264
5094
|
await client.execute({
|
|
@@ -4523,8 +5353,8 @@ __export(tasks_exports, {
|
|
|
4523
5353
|
updateTaskStatus: () => updateTaskStatus,
|
|
4524
5354
|
writeCheckpoint: () => writeCheckpoint
|
|
4525
5355
|
});
|
|
4526
|
-
import
|
|
4527
|
-
import { writeFileSync as
|
|
5356
|
+
import path16 from "path";
|
|
5357
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4528
5358
|
async function createTask(input) {
|
|
4529
5359
|
const result = await createTaskCore(input);
|
|
4530
5360
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4543,12 +5373,12 @@ async function updateTask(input) {
|
|
|
4543
5373
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4544
5374
|
try {
|
|
4545
5375
|
const agent = String(row.assigned_to);
|
|
4546
|
-
const cacheDir =
|
|
4547
|
-
const cachePath =
|
|
5376
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5377
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
4548
5378
|
if (input.status === "in_progress") {
|
|
4549
5379
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4550
|
-
|
|
4551
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5380
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5381
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4552
5382
|
try {
|
|
4553
5383
|
unlinkSync5(cachePath);
|
|
4554
5384
|
} catch {
|
|
@@ -4556,10 +5386,10 @@ async function updateTask(input) {
|
|
|
4556
5386
|
}
|
|
4557
5387
|
} catch {
|
|
4558
5388
|
}
|
|
4559
|
-
if (input.status === "done") {
|
|
5389
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4560
5390
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4561
5391
|
}
|
|
4562
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5392
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4563
5393
|
try {
|
|
4564
5394
|
const client = getClient();
|
|
4565
5395
|
const taskTitle = String(row.title);
|
|
@@ -4575,7 +5405,7 @@ async function updateTask(input) {
|
|
|
4575
5405
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4576
5406
|
try {
|
|
4577
5407
|
const draftClient = getClient();
|
|
4578
|
-
if (input.status === "done") {
|
|
5408
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4579
5409
|
await draftClient.execute({
|
|
4580
5410
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4581
5411
|
args: [assignedAgent]
|
|
@@ -4592,7 +5422,7 @@ async function updateTask(input) {
|
|
|
4592
5422
|
try {
|
|
4593
5423
|
const client = getClient();
|
|
4594
5424
|
const cascaded = await client.execute({
|
|
4595
|
-
sql: `UPDATE tasks SET status = '
|
|
5425
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4596
5426
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4597
5427
|
args: [now, taskId]
|
|
4598
5428
|
});
|
|
@@ -4605,14 +5435,14 @@ async function updateTask(input) {
|
|
|
4605
5435
|
} catch {
|
|
4606
5436
|
}
|
|
4607
5437
|
}
|
|
4608
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5438
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4609
5439
|
if (isTerminal) {
|
|
4610
5440
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4611
5441
|
if (!isCoordinator) {
|
|
4612
5442
|
notifyTaskDone();
|
|
4613
5443
|
}
|
|
4614
5444
|
await markTaskNotificationsRead(taskFile);
|
|
4615
|
-
if (input.status === "done") {
|
|
5445
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4616
5446
|
try {
|
|
4617
5447
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4618
5448
|
} catch {
|
|
@@ -4632,7 +5462,7 @@ async function updateTask(input) {
|
|
|
4632
5462
|
}
|
|
4633
5463
|
}
|
|
4634
5464
|
}
|
|
4635
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5465
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4636
5466
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4637
5467
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4638
5468
|
taskId,
|
|
@@ -5004,6 +5834,7 @@ __export(tmux_routing_exports, {
|
|
|
5004
5834
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5005
5835
|
isExeSession: () => isExeSession,
|
|
5006
5836
|
isSessionBusy: () => isSessionBusy,
|
|
5837
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5007
5838
|
notifyParentExe: () => notifyParentExe,
|
|
5008
5839
|
parseParentExe: () => parseParentExe,
|
|
5009
5840
|
registerParentExe: () => registerParentExe,
|
|
@@ -5014,13 +5845,13 @@ __export(tmux_routing_exports, {
|
|
|
5014
5845
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5015
5846
|
});
|
|
5016
5847
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5017
|
-
import { readFileSync as
|
|
5018
|
-
import
|
|
5019
|
-
import
|
|
5848
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5849
|
+
import path17 from "path";
|
|
5850
|
+
import os10 from "os";
|
|
5020
5851
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5021
5852
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5022
5853
|
function spawnLockPath(sessionName) {
|
|
5023
|
-
return
|
|
5854
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5024
5855
|
}
|
|
5025
5856
|
function isProcessAlive(pid) {
|
|
5026
5857
|
try {
|
|
@@ -5031,13 +5862,13 @@ function isProcessAlive(pid) {
|
|
|
5031
5862
|
}
|
|
5032
5863
|
}
|
|
5033
5864
|
function acquireSpawnLock2(sessionName) {
|
|
5034
|
-
if (!
|
|
5865
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5035
5866
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5036
5867
|
}
|
|
5037
5868
|
const lockFile = spawnLockPath(sessionName);
|
|
5038
|
-
if (
|
|
5869
|
+
if (existsSync14(lockFile)) {
|
|
5039
5870
|
try {
|
|
5040
|
-
const lock = JSON.parse(
|
|
5871
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5041
5872
|
const age = Date.now() - lock.timestamp;
|
|
5042
5873
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5043
5874
|
return false;
|
|
@@ -5045,7 +5876,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5045
5876
|
} catch {
|
|
5046
5877
|
}
|
|
5047
5878
|
}
|
|
5048
|
-
|
|
5879
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5049
5880
|
return true;
|
|
5050
5881
|
}
|
|
5051
5882
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5057,13 +5888,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5057
5888
|
function resolveBehaviorsExporterScript() {
|
|
5058
5889
|
try {
|
|
5059
5890
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5060
|
-
const scriptPath =
|
|
5061
|
-
|
|
5891
|
+
const scriptPath = path17.join(
|
|
5892
|
+
path17.dirname(thisFile),
|
|
5062
5893
|
"..",
|
|
5063
5894
|
"bin",
|
|
5064
5895
|
"exe-export-behaviors.js"
|
|
5065
5896
|
);
|
|
5066
|
-
return
|
|
5897
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5067
5898
|
} catch {
|
|
5068
5899
|
return null;
|
|
5069
5900
|
}
|
|
@@ -5129,12 +5960,12 @@ function extractRootExe(name) {
|
|
|
5129
5960
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5130
5961
|
}
|
|
5131
5962
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5132
|
-
if (!
|
|
5963
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5133
5964
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5134
5965
|
}
|
|
5135
5966
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5136
|
-
const filePath =
|
|
5137
|
-
|
|
5967
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5968
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5138
5969
|
parentExe: rootExe,
|
|
5139
5970
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5140
5971
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5142,7 +5973,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5142
5973
|
}
|
|
5143
5974
|
function getParentExe(sessionKey) {
|
|
5144
5975
|
try {
|
|
5145
|
-
const data = JSON.parse(
|
|
5976
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5146
5977
|
return data.parentExe || null;
|
|
5147
5978
|
} catch {
|
|
5148
5979
|
return null;
|
|
@@ -5150,8 +5981,8 @@ function getParentExe(sessionKey) {
|
|
|
5150
5981
|
}
|
|
5151
5982
|
function getDispatchedBy(sessionKey) {
|
|
5152
5983
|
try {
|
|
5153
|
-
const data = JSON.parse(
|
|
5154
|
-
|
|
5984
|
+
const data = JSON.parse(readFileSync12(
|
|
5985
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5155
5986
|
"utf8"
|
|
5156
5987
|
));
|
|
5157
5988
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5221,8 +6052,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5221
6052
|
}
|
|
5222
6053
|
function readDebounceState() {
|
|
5223
6054
|
try {
|
|
5224
|
-
if (!
|
|
5225
|
-
const raw = JSON.parse(
|
|
6055
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
6056
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5226
6057
|
const state = {};
|
|
5227
6058
|
for (const [key, val] of Object.entries(raw)) {
|
|
5228
6059
|
if (typeof val === "number") {
|
|
@@ -5238,8 +6069,8 @@ function readDebounceState() {
|
|
|
5238
6069
|
}
|
|
5239
6070
|
function writeDebounceState(state) {
|
|
5240
6071
|
try {
|
|
5241
|
-
if (!
|
|
5242
|
-
|
|
6072
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
6073
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5243
6074
|
} catch {
|
|
5244
6075
|
}
|
|
5245
6076
|
}
|
|
@@ -5337,8 +6168,8 @@ function sendIntercom(targetSession) {
|
|
|
5337
6168
|
try {
|
|
5338
6169
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5339
6170
|
const agent = baseAgentName(rawAgent);
|
|
5340
|
-
const markerPath =
|
|
5341
|
-
if (
|
|
6171
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6172
|
+
if (existsSync14(markerPath)) {
|
|
5342
6173
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
5343
6174
|
return "debounced";
|
|
5344
6175
|
}
|
|
@@ -5347,8 +6178,8 @@ function sendIntercom(targetSession) {
|
|
|
5347
6178
|
try {
|
|
5348
6179
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
5349
6180
|
const agent = baseAgentName(rawAgent);
|
|
5350
|
-
const taskDir =
|
|
5351
|
-
if (
|
|
6181
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
6182
|
+
if (existsSync14(taskDir)) {
|
|
5352
6183
|
const files = readdirSync3(taskDir).filter(
|
|
5353
6184
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
5354
6185
|
);
|
|
@@ -5408,6 +6239,21 @@ function notifyParentExe(sessionKey) {
|
|
|
5408
6239
|
}
|
|
5409
6240
|
return true;
|
|
5410
6241
|
}
|
|
6242
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6243
|
+
const transport = getTransport();
|
|
6244
|
+
try {
|
|
6245
|
+
const sessions = transport.listSessions();
|
|
6246
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6247
|
+
execSync7(
|
|
6248
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6249
|
+
{ timeout: 3e3 }
|
|
6250
|
+
);
|
|
6251
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6252
|
+
return true;
|
|
6253
|
+
} catch {
|
|
6254
|
+
return false;
|
|
6255
|
+
}
|
|
6256
|
+
}
|
|
5411
6257
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5412
6258
|
if (isCoordinatorName(employeeName)) {
|
|
5413
6259
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5481,26 +6327,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5481
6327
|
const transport = getTransport();
|
|
5482
6328
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5483
6329
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5484
|
-
const logDir =
|
|
5485
|
-
const logFile =
|
|
5486
|
-
if (!
|
|
6330
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
6331
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6332
|
+
if (!existsSync14(logDir)) {
|
|
5487
6333
|
mkdirSync6(logDir, { recursive: true });
|
|
5488
6334
|
}
|
|
5489
6335
|
transport.kill(sessionName);
|
|
5490
6336
|
let cleanupSuffix = "";
|
|
5491
6337
|
try {
|
|
5492
6338
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5493
|
-
const cleanupScript =
|
|
5494
|
-
if (
|
|
6339
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6340
|
+
if (existsSync14(cleanupScript)) {
|
|
5495
6341
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5496
6342
|
}
|
|
5497
6343
|
} catch {
|
|
5498
6344
|
}
|
|
5499
6345
|
try {
|
|
5500
|
-
const claudeJsonPath =
|
|
6346
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
5501
6347
|
let claudeJson = {};
|
|
5502
6348
|
try {
|
|
5503
|
-
claudeJson = JSON.parse(
|
|
6349
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5504
6350
|
} catch {
|
|
5505
6351
|
}
|
|
5506
6352
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5508,17 +6354,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5508
6354
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5509
6355
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5510
6356
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5511
|
-
|
|
6357
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5512
6358
|
} catch {
|
|
5513
6359
|
}
|
|
5514
6360
|
try {
|
|
5515
|
-
const settingsDir =
|
|
6361
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
5516
6362
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5517
|
-
const projSettingsDir =
|
|
5518
|
-
const settingsPath =
|
|
6363
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
6364
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
5519
6365
|
let settings = {};
|
|
5520
6366
|
try {
|
|
5521
|
-
settings = JSON.parse(
|
|
6367
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5522
6368
|
} catch {
|
|
5523
6369
|
}
|
|
5524
6370
|
const perms = settings.permissions ?? {};
|
|
@@ -5547,7 +6393,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5547
6393
|
perms.allow = allow;
|
|
5548
6394
|
settings.permissions = perms;
|
|
5549
6395
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5550
|
-
|
|
6396
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5551
6397
|
}
|
|
5552
6398
|
} catch {
|
|
5553
6399
|
}
|
|
@@ -5562,8 +6408,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5562
6408
|
let behaviorsFlag = "";
|
|
5563
6409
|
let legacyFallbackWarned = false;
|
|
5564
6410
|
if (!useExeAgent && !useBinSymlink) {
|
|
5565
|
-
const identityPath =
|
|
5566
|
-
|
|
6411
|
+
const identityPath = path17.join(
|
|
6412
|
+
os10.homedir(),
|
|
5567
6413
|
".exe-os",
|
|
5568
6414
|
"identity",
|
|
5569
6415
|
`${employeeName}.md`
|
|
@@ -5572,13 +6418,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5572
6418
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5573
6419
|
if (hasAgentFlag) {
|
|
5574
6420
|
identityFlag = ` --agent ${employeeName}`;
|
|
5575
|
-
} else if (
|
|
6421
|
+
} else if (existsSync14(identityPath)) {
|
|
5576
6422
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5577
6423
|
legacyFallbackWarned = true;
|
|
5578
6424
|
}
|
|
5579
6425
|
const behaviorsFile = exportBehaviorsSync(
|
|
5580
6426
|
employeeName,
|
|
5581
|
-
|
|
6427
|
+
path17.basename(spawnCwd),
|
|
5582
6428
|
sessionName
|
|
5583
6429
|
);
|
|
5584
6430
|
if (behaviorsFile) {
|
|
@@ -5593,16 +6439,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5593
6439
|
}
|
|
5594
6440
|
let sessionContextFlag = "";
|
|
5595
6441
|
try {
|
|
5596
|
-
const ctxDir =
|
|
6442
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5597
6443
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5598
|
-
const ctxFile =
|
|
6444
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5599
6445
|
const ctxContent = [
|
|
5600
6446
|
`## Session Context`,
|
|
5601
6447
|
`You are running in tmux session: ${sessionName}.`,
|
|
5602
6448
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5603
6449
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5604
6450
|
].join("\n");
|
|
5605
|
-
|
|
6451
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5606
6452
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5607
6453
|
} catch {
|
|
5608
6454
|
}
|
|
@@ -5679,8 +6525,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5679
6525
|
transport.pipeLog(sessionName, logFile);
|
|
5680
6526
|
try {
|
|
5681
6527
|
const mySession = getMySession();
|
|
5682
|
-
const dispatchInfo =
|
|
5683
|
-
|
|
6528
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6529
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5684
6530
|
dispatchedBy: mySession,
|
|
5685
6531
|
rootExe: exeSession,
|
|
5686
6532
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5754,15 +6600,15 @@ var init_tmux_routing = __esm({
|
|
|
5754
6600
|
init_intercom_queue();
|
|
5755
6601
|
init_plan_limits();
|
|
5756
6602
|
init_employees();
|
|
5757
|
-
SPAWN_LOCK_DIR =
|
|
5758
|
-
SESSION_CACHE =
|
|
6603
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6604
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5759
6605
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5760
6606
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5761
6607
|
VERIFY_PANE_LINES = 200;
|
|
5762
6608
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5763
6609
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5764
|
-
INTERCOM_LOG2 =
|
|
5765
|
-
DEBOUNCE_FILE =
|
|
6610
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6611
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5766
6612
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5767
6613
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5768
6614
|
}
|
|
@@ -6046,9 +6892,9 @@ __export(agent_signals_exports, {
|
|
|
6046
6892
|
hasOpenTasks: () => hasOpenTasks,
|
|
6047
6893
|
hasUnreadInbox: () => hasUnreadInbox
|
|
6048
6894
|
});
|
|
6049
|
-
import { readFileSync as
|
|
6050
|
-
import
|
|
6051
|
-
import
|
|
6895
|
+
import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
|
|
6896
|
+
import os11 from "os";
|
|
6897
|
+
import path18 from "path";
|
|
6052
6898
|
async function hasOpenTasks(client, agentId) {
|
|
6053
6899
|
try {
|
|
6054
6900
|
const scope = sessionScopeFilter(null);
|
|
@@ -6090,10 +6936,10 @@ async function hasUnreadInbox(client, agentId) {
|
|
|
6090
6936
|
return CONSERVATIVE_ON_ERROR;
|
|
6091
6937
|
}
|
|
6092
6938
|
}
|
|
6093
|
-
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog =
|
|
6094
|
-
if (!
|
|
6939
|
+
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path18.join(os11.homedir(), ".exe-os", "intercom.log")) {
|
|
6940
|
+
if (!existsSync15(intercomLog)) return false;
|
|
6095
6941
|
try {
|
|
6096
|
-
const raw =
|
|
6942
|
+
const raw = readFileSync13(intercomLog, "utf8");
|
|
6097
6943
|
const lines = raw.split("\n");
|
|
6098
6944
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6099
6945
|
const line = lines[i];
|
|
@@ -6166,7 +7012,7 @@ __export(daemon_orchestration_exports, {
|
|
|
6166
7012
|
shouldNudgeEmployee: () => shouldNudgeEmployee
|
|
6167
7013
|
});
|
|
6168
7014
|
import { execSync as execSync9 } from "child_process";
|
|
6169
|
-
import { existsSync as
|
|
7015
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
6170
7016
|
import { homedir } from "os";
|
|
6171
7017
|
import { join } from "path";
|
|
6172
7018
|
function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
|
|
@@ -6357,8 +7203,8 @@ async function pollReviewNudge(deps, state) {
|
|
|
6357
7203
|
function loadNudgeState() {
|
|
6358
7204
|
const state = { lastNudge: /* @__PURE__ */ new Map() };
|
|
6359
7205
|
try {
|
|
6360
|
-
if (!
|
|
6361
|
-
const raw = JSON.parse(
|
|
7206
|
+
if (!existsSync16(NUDGE_STATE_PATH)) return state;
|
|
7207
|
+
const raw = JSON.parse(readFileSync14(NUDGE_STATE_PATH, "utf8"));
|
|
6362
7208
|
if (Array.isArray(raw)) {
|
|
6363
7209
|
for (const [key, val] of raw) {
|
|
6364
7210
|
if (key && typeof val?.at === "number" && typeof val?.count === "number") {
|
|
@@ -6372,7 +7218,7 @@ function loadNudgeState() {
|
|
|
6372
7218
|
}
|
|
6373
7219
|
function saveNudgeState(state) {
|
|
6374
7220
|
const entries = Array.from(state.lastNudge.entries());
|
|
6375
|
-
|
|
7221
|
+
writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
|
|
6376
7222
|
}
|
|
6377
7223
|
function createReviewNudgeRealDeps(getClient2) {
|
|
6378
7224
|
return {
|
|
@@ -6420,7 +7266,7 @@ function createIdleNudgeRealDeps(getClient2) {
|
|
|
6420
7266
|
const doScope = sessionScopeFilter(null);
|
|
6421
7267
|
const result = await client.execute({
|
|
6422
7268
|
sql: `SELECT id, title, priority FROM tasks
|
|
6423
|
-
WHERE assigned_to = ? AND status IN ('open', 'in_progress'
|
|
7269
|
+
WHERE assigned_to = ? AND status IN ('open', 'in_progress')${doScope.sql}
|
|
6424
7270
|
ORDER BY CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 ELSE 2 END
|
|
6425
7271
|
LIMIT 1`,
|
|
6426
7272
|
args: [agentId, ...doScope.args]
|
|
@@ -6710,134 +7556,12 @@ var init_daemon_orchestration = __esm({
|
|
|
6710
7556
|
}
|
|
6711
7557
|
});
|
|
6712
7558
|
|
|
6713
|
-
// src/lib/keychain.ts
|
|
6714
|
-
var keychain_exports = {};
|
|
6715
|
-
__export(keychain_exports, {
|
|
6716
|
-
deleteMasterKey: () => deleteMasterKey,
|
|
6717
|
-
exportMnemonic: () => exportMnemonic,
|
|
6718
|
-
getMasterKey: () => getMasterKey,
|
|
6719
|
-
importMnemonic: () => importMnemonic,
|
|
6720
|
-
setMasterKey: () => setMasterKey
|
|
6721
|
-
});
|
|
6722
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
6723
|
-
import { existsSync as existsSync15 } from "fs";
|
|
6724
|
-
import path17 from "path";
|
|
6725
|
-
import os10 from "os";
|
|
6726
|
-
function getKeyDir() {
|
|
6727
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
|
|
6728
|
-
}
|
|
6729
|
-
function getKeyPath() {
|
|
6730
|
-
return path17.join(getKeyDir(), "master.key");
|
|
6731
|
-
}
|
|
6732
|
-
async function tryKeytar() {
|
|
6733
|
-
try {
|
|
6734
|
-
return await import("keytar");
|
|
6735
|
-
} catch {
|
|
6736
|
-
return null;
|
|
6737
|
-
}
|
|
6738
|
-
}
|
|
6739
|
-
async function getMasterKey() {
|
|
6740
|
-
const keytar = await tryKeytar();
|
|
6741
|
-
if (keytar) {
|
|
6742
|
-
try {
|
|
6743
|
-
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
6744
|
-
if (stored) {
|
|
6745
|
-
return Buffer.from(stored, "base64");
|
|
6746
|
-
}
|
|
6747
|
-
} catch {
|
|
6748
|
-
}
|
|
6749
|
-
}
|
|
6750
|
-
const keyPath = getKeyPath();
|
|
6751
|
-
if (!existsSync15(keyPath)) {
|
|
6752
|
-
process.stderr.write(
|
|
6753
|
-
`[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6754
|
-
`
|
|
6755
|
-
);
|
|
6756
|
-
return null;
|
|
6757
|
-
}
|
|
6758
|
-
try {
|
|
6759
|
-
const content = await readFile4(keyPath, "utf-8");
|
|
6760
|
-
return Buffer.from(content.trim(), "base64");
|
|
6761
|
-
} catch (err) {
|
|
6762
|
-
process.stderr.write(
|
|
6763
|
-
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
6764
|
-
`
|
|
6765
|
-
);
|
|
6766
|
-
return null;
|
|
6767
|
-
}
|
|
6768
|
-
}
|
|
6769
|
-
async function setMasterKey(key) {
|
|
6770
|
-
const b64 = key.toString("base64");
|
|
6771
|
-
const keytar = await tryKeytar();
|
|
6772
|
-
if (keytar) {
|
|
6773
|
-
try {
|
|
6774
|
-
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
6775
|
-
return;
|
|
6776
|
-
} catch {
|
|
6777
|
-
}
|
|
6778
|
-
}
|
|
6779
|
-
const dir = getKeyDir();
|
|
6780
|
-
await mkdir4(dir, { recursive: true });
|
|
6781
|
-
const keyPath = getKeyPath();
|
|
6782
|
-
await writeFile5(keyPath, b64 + "\n", "utf-8");
|
|
6783
|
-
await chmod2(keyPath, 384);
|
|
6784
|
-
}
|
|
6785
|
-
async function deleteMasterKey() {
|
|
6786
|
-
const keytar = await tryKeytar();
|
|
6787
|
-
if (keytar) {
|
|
6788
|
-
try {
|
|
6789
|
-
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
6790
|
-
} catch {
|
|
6791
|
-
}
|
|
6792
|
-
}
|
|
6793
|
-
const keyPath = getKeyPath();
|
|
6794
|
-
if (existsSync15(keyPath)) {
|
|
6795
|
-
await unlink(keyPath);
|
|
6796
|
-
}
|
|
6797
|
-
}
|
|
6798
|
-
async function loadBip39() {
|
|
6799
|
-
try {
|
|
6800
|
-
return await import("bip39");
|
|
6801
|
-
} catch {
|
|
6802
|
-
throw new Error(
|
|
6803
|
-
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
6804
|
-
);
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
async function exportMnemonic(key) {
|
|
6808
|
-
if (key.length !== 32) {
|
|
6809
|
-
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
6810
|
-
}
|
|
6811
|
-
const { entropyToMnemonic } = await loadBip39();
|
|
6812
|
-
return entropyToMnemonic(key.toString("hex"));
|
|
6813
|
-
}
|
|
6814
|
-
async function importMnemonic(mnemonic) {
|
|
6815
|
-
const trimmed = mnemonic.trim();
|
|
6816
|
-
const words = trimmed.split(/\s+/);
|
|
6817
|
-
if (words.length !== 24) {
|
|
6818
|
-
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
6819
|
-
}
|
|
6820
|
-
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
6821
|
-
if (!validateMnemonic(trimmed)) {
|
|
6822
|
-
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
6823
|
-
}
|
|
6824
|
-
const entropy = mnemonicToEntropy(trimmed);
|
|
6825
|
-
return Buffer.from(entropy, "hex");
|
|
6826
|
-
}
|
|
6827
|
-
var SERVICE, ACCOUNT;
|
|
6828
|
-
var init_keychain = __esm({
|
|
6829
|
-
"src/lib/keychain.ts"() {
|
|
6830
|
-
"use strict";
|
|
6831
|
-
SERVICE = "exe-mem";
|
|
6832
|
-
ACCOUNT = "master-key";
|
|
6833
|
-
}
|
|
6834
|
-
});
|
|
6835
|
-
|
|
6836
7559
|
// src/lib/shard-manager.ts
|
|
6837
7560
|
var shard_manager_exports = {};
|
|
6838
7561
|
__export(shard_manager_exports, {
|
|
6839
7562
|
disposeShards: () => disposeShards,
|
|
6840
7563
|
ensureShardSchema: () => ensureShardSchema,
|
|
7564
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
6841
7565
|
getReadyShardClient: () => getReadyShardClient,
|
|
6842
7566
|
getShardClient: () => getShardClient,
|
|
6843
7567
|
getShardsDir: () => getShardsDir,
|
|
@@ -6846,15 +7570,18 @@ __export(shard_manager_exports, {
|
|
|
6846
7570
|
listShards: () => listShards,
|
|
6847
7571
|
shardExists: () => shardExists
|
|
6848
7572
|
});
|
|
6849
|
-
import
|
|
6850
|
-
import { existsSync as
|
|
7573
|
+
import path19 from "path";
|
|
7574
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
6851
7575
|
import { createClient as createClient2 } from "@libsql/client";
|
|
6852
7576
|
function initShardManager(encryptionKey) {
|
|
6853
7577
|
_encryptionKey = encryptionKey;
|
|
6854
|
-
if (!
|
|
7578
|
+
if (!existsSync17(SHARDS_DIR)) {
|
|
6855
7579
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
6856
7580
|
}
|
|
6857
7581
|
_shardingEnabled = true;
|
|
7582
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
7583
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
7584
|
+
_evictionTimer.unref();
|
|
6858
7585
|
}
|
|
6859
7586
|
function isShardingEnabled() {
|
|
6860
7587
|
return _shardingEnabled;
|
|
@@ -6871,21 +7598,28 @@ function getShardClient(projectName) {
|
|
|
6871
7598
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
6872
7599
|
}
|
|
6873
7600
|
const cached = _shards.get(safeName);
|
|
6874
|
-
if (cached)
|
|
6875
|
-
|
|
7601
|
+
if (cached) {
|
|
7602
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
7603
|
+
return cached;
|
|
7604
|
+
}
|
|
7605
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
7606
|
+
evictLRU();
|
|
7607
|
+
}
|
|
7608
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
6876
7609
|
const client = createClient2({
|
|
6877
7610
|
url: `file:${dbPath}`,
|
|
6878
7611
|
encryptionKey: _encryptionKey
|
|
6879
7612
|
});
|
|
6880
7613
|
_shards.set(safeName, client);
|
|
7614
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6881
7615
|
return client;
|
|
6882
7616
|
}
|
|
6883
7617
|
function shardExists(projectName) {
|
|
6884
7618
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6885
|
-
return
|
|
7619
|
+
return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
6886
7620
|
}
|
|
6887
7621
|
function listShards() {
|
|
6888
|
-
if (!
|
|
7622
|
+
if (!existsSync17(SHARDS_DIR)) return [];
|
|
6889
7623
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
6890
7624
|
}
|
|
6891
7625
|
async function ensureShardSchema(client) {
|
|
@@ -6937,6 +7671,8 @@ async function ensureShardSchema(client) {
|
|
|
6937
7671
|
for (const col of [
|
|
6938
7672
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
6939
7673
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
7674
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
7675
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
6940
7676
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
6941
7677
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
6942
7678
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -6959,7 +7695,23 @@ async function ensureShardSchema(client) {
|
|
|
6959
7695
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
6960
7696
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
6961
7697
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
6962
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
7698
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
7699
|
+
// Metadata enrichment columns (must match database.ts)
|
|
7700
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
7701
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
7702
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
7703
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
7704
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
7705
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
7706
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
7707
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
7708
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
7709
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
7710
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
7711
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
7712
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
7713
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
7714
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
6963
7715
|
]) {
|
|
6964
7716
|
try {
|
|
6965
7717
|
await client.execute(col);
|
|
@@ -7053,31 +7805,202 @@ async function ensureShardSchema(client) {
|
|
|
7053
7805
|
}
|
|
7054
7806
|
}
|
|
7055
7807
|
}
|
|
7056
|
-
async function getReadyShardClient(projectName) {
|
|
7057
|
-
const client = getShardClient(projectName);
|
|
7058
|
-
await ensureShardSchema(client);
|
|
7059
|
-
return client;
|
|
7808
|
+
async function getReadyShardClient(projectName) {
|
|
7809
|
+
const client = getShardClient(projectName);
|
|
7810
|
+
await ensureShardSchema(client);
|
|
7811
|
+
return client;
|
|
7812
|
+
}
|
|
7813
|
+
function evictLRU() {
|
|
7814
|
+
let oldest = null;
|
|
7815
|
+
let oldestTime = Infinity;
|
|
7816
|
+
for (const [name, time] of _shardLastAccess) {
|
|
7817
|
+
if (time < oldestTime) {
|
|
7818
|
+
oldestTime = time;
|
|
7819
|
+
oldest = name;
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
if (oldest) {
|
|
7823
|
+
const client = _shards.get(oldest);
|
|
7824
|
+
if (client) {
|
|
7825
|
+
client.close();
|
|
7826
|
+
}
|
|
7827
|
+
_shards.delete(oldest);
|
|
7828
|
+
_shardLastAccess.delete(oldest);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
function evictIdleShards() {
|
|
7832
|
+
const now = Date.now();
|
|
7833
|
+
const toEvict = [];
|
|
7834
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
7835
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
7836
|
+
toEvict.push(name);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
for (const name of toEvict) {
|
|
7840
|
+
const client = _shards.get(name);
|
|
7841
|
+
if (client) {
|
|
7842
|
+
client.close();
|
|
7843
|
+
}
|
|
7844
|
+
_shards.delete(name);
|
|
7845
|
+
_shardLastAccess.delete(name);
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
function getOpenShardCount() {
|
|
7849
|
+
return _shards.size;
|
|
7060
7850
|
}
|
|
7061
7851
|
function disposeShards() {
|
|
7852
|
+
if (_evictionTimer) {
|
|
7853
|
+
clearInterval(_evictionTimer);
|
|
7854
|
+
_evictionTimer = null;
|
|
7855
|
+
}
|
|
7062
7856
|
for (const [, client] of _shards) {
|
|
7063
7857
|
client.close();
|
|
7064
7858
|
}
|
|
7065
7859
|
_shards.clear();
|
|
7860
|
+
_shardLastAccess.clear();
|
|
7066
7861
|
_shardingEnabled = false;
|
|
7067
7862
|
_encryptionKey = null;
|
|
7068
7863
|
}
|
|
7069
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
7864
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
7070
7865
|
var init_shard_manager = __esm({
|
|
7071
7866
|
"src/lib/shard-manager.ts"() {
|
|
7072
7867
|
"use strict";
|
|
7073
7868
|
init_config();
|
|
7074
|
-
SHARDS_DIR =
|
|
7869
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
7870
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
7871
|
+
MAX_OPEN_SHARDS = 10;
|
|
7872
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
7075
7873
|
_shards = /* @__PURE__ */ new Map();
|
|
7874
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
7875
|
+
_evictionTimer = null;
|
|
7076
7876
|
_encryptionKey = null;
|
|
7077
7877
|
_shardingEnabled = false;
|
|
7078
7878
|
}
|
|
7079
7879
|
});
|
|
7080
7880
|
|
|
7881
|
+
// src/lib/keychain.ts
|
|
7882
|
+
var keychain_exports = {};
|
|
7883
|
+
__export(keychain_exports, {
|
|
7884
|
+
deleteMasterKey: () => deleteMasterKey,
|
|
7885
|
+
exportMnemonic: () => exportMnemonic,
|
|
7886
|
+
getMasterKey: () => getMasterKey,
|
|
7887
|
+
importMnemonic: () => importMnemonic,
|
|
7888
|
+
setMasterKey: () => setMasterKey
|
|
7889
|
+
});
|
|
7890
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7891
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7892
|
+
import path20 from "path";
|
|
7893
|
+
import os12 from "os";
|
|
7894
|
+
function getKeyDir() {
|
|
7895
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
|
|
7896
|
+
}
|
|
7897
|
+
function getKeyPath() {
|
|
7898
|
+
return path20.join(getKeyDir(), "master.key");
|
|
7899
|
+
}
|
|
7900
|
+
async function tryKeytar() {
|
|
7901
|
+
try {
|
|
7902
|
+
return await import("keytar");
|
|
7903
|
+
} catch {
|
|
7904
|
+
return null;
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
async function getMasterKey() {
|
|
7908
|
+
const keytar = await tryKeytar();
|
|
7909
|
+
if (keytar) {
|
|
7910
|
+
try {
|
|
7911
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
7912
|
+
if (stored) {
|
|
7913
|
+
return Buffer.from(stored, "base64");
|
|
7914
|
+
}
|
|
7915
|
+
} catch {
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
const keyPath = getKeyPath();
|
|
7919
|
+
if (!existsSync18(keyPath)) {
|
|
7920
|
+
process.stderr.write(
|
|
7921
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7922
|
+
`
|
|
7923
|
+
);
|
|
7924
|
+
return null;
|
|
7925
|
+
}
|
|
7926
|
+
try {
|
|
7927
|
+
const content = await readFile4(keyPath, "utf-8");
|
|
7928
|
+
return Buffer.from(content.trim(), "base64");
|
|
7929
|
+
} catch (err) {
|
|
7930
|
+
process.stderr.write(
|
|
7931
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
7932
|
+
`
|
|
7933
|
+
);
|
|
7934
|
+
return null;
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
async function setMasterKey(key) {
|
|
7938
|
+
const b64 = key.toString("base64");
|
|
7939
|
+
const keytar = await tryKeytar();
|
|
7940
|
+
if (keytar) {
|
|
7941
|
+
try {
|
|
7942
|
+
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
7943
|
+
return;
|
|
7944
|
+
} catch {
|
|
7945
|
+
}
|
|
7946
|
+
}
|
|
7947
|
+
const dir = getKeyDir();
|
|
7948
|
+
await mkdir4(dir, { recursive: true });
|
|
7949
|
+
const keyPath = getKeyPath();
|
|
7950
|
+
await writeFile5(keyPath, b64 + "\n", "utf-8");
|
|
7951
|
+
await chmod2(keyPath, 384);
|
|
7952
|
+
}
|
|
7953
|
+
async function deleteMasterKey() {
|
|
7954
|
+
const keytar = await tryKeytar();
|
|
7955
|
+
if (keytar) {
|
|
7956
|
+
try {
|
|
7957
|
+
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
7958
|
+
} catch {
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7961
|
+
const keyPath = getKeyPath();
|
|
7962
|
+
if (existsSync18(keyPath)) {
|
|
7963
|
+
await unlink(keyPath);
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
async function loadBip39() {
|
|
7967
|
+
try {
|
|
7968
|
+
return await import("bip39");
|
|
7969
|
+
} catch {
|
|
7970
|
+
throw new Error(
|
|
7971
|
+
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
7972
|
+
);
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
async function exportMnemonic(key) {
|
|
7976
|
+
if (key.length !== 32) {
|
|
7977
|
+
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
7978
|
+
}
|
|
7979
|
+
const { entropyToMnemonic } = await loadBip39();
|
|
7980
|
+
return entropyToMnemonic(key.toString("hex"));
|
|
7981
|
+
}
|
|
7982
|
+
async function importMnemonic(mnemonic) {
|
|
7983
|
+
const trimmed = mnemonic.trim();
|
|
7984
|
+
const words = trimmed.split(/\s+/);
|
|
7985
|
+
if (words.length !== 24) {
|
|
7986
|
+
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
7987
|
+
}
|
|
7988
|
+
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
7989
|
+
if (!validateMnemonic(trimmed)) {
|
|
7990
|
+
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
7991
|
+
}
|
|
7992
|
+
const entropy = mnemonicToEntropy(trimmed);
|
|
7993
|
+
return Buffer.from(entropy, "hex");
|
|
7994
|
+
}
|
|
7995
|
+
var SERVICE, ACCOUNT;
|
|
7996
|
+
var init_keychain = __esm({
|
|
7997
|
+
"src/lib/keychain.ts"() {
|
|
7998
|
+
"use strict";
|
|
7999
|
+
SERVICE = "exe-mem";
|
|
8000
|
+
ACCOUNT = "master-key";
|
|
8001
|
+
}
|
|
8002
|
+
});
|
|
8003
|
+
|
|
7081
8004
|
// src/lib/platform-procedures.ts
|
|
7082
8005
|
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
7083
8006
|
var init_platform_procedures = __esm({
|
|
@@ -7850,15 +8773,15 @@ async function pollPendingReviews(deps, state) {
|
|
|
7850
8773
|
return [];
|
|
7851
8774
|
}
|
|
7852
8775
|
if (sessions.length === 0) return [];
|
|
7853
|
-
let reviewCount;
|
|
7854
|
-
try {
|
|
7855
|
-
reviewCount = await deps.countPendingReviews();
|
|
7856
|
-
} catch {
|
|
7857
|
-
return [];
|
|
7858
|
-
}
|
|
7859
|
-
if (reviewCount === 0) return [];
|
|
7860
8776
|
const sent = [];
|
|
7861
8777
|
for (const exeSession of sessions) {
|
|
8778
|
+
let reviewCount = 0;
|
|
8779
|
+
try {
|
|
8780
|
+
reviewCount = await deps.countPendingReviews(exeSession);
|
|
8781
|
+
} catch {
|
|
8782
|
+
continue;
|
|
8783
|
+
}
|
|
8784
|
+
if (reviewCount === 0) continue;
|
|
7862
8785
|
const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
|
|
7863
8786
|
if (Date.now() - lastSent < state.intervalMs) continue;
|
|
7864
8787
|
try {
|
|
@@ -7868,15 +8791,17 @@ async function pollPendingReviews(deps, state) {
|
|
|
7868
8791
|
} catch {
|
|
7869
8792
|
}
|
|
7870
8793
|
}
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
8794
|
+
for (const exeSession of sessions) {
|
|
8795
|
+
try {
|
|
8796
|
+
const orphans = await deps.findOrphanedDoneTasks(exeSession);
|
|
8797
|
+
for (const orphan of orphans) {
|
|
8798
|
+
try {
|
|
8799
|
+
await deps.createReviewForOrphan(orphan);
|
|
8800
|
+
} catch {
|
|
8801
|
+
}
|
|
7877
8802
|
}
|
|
8803
|
+
} catch {
|
|
7878
8804
|
}
|
|
7879
|
-
} catch {
|
|
7880
8805
|
}
|
|
7881
8806
|
if (deps.findStaleTasks && deps.sendNudge) {
|
|
7882
8807
|
try {
|
|
@@ -7894,50 +8819,56 @@ async function pollPendingReviews(deps, state) {
|
|
|
7894
8819
|
}
|
|
7895
8820
|
}
|
|
7896
8821
|
if (deps.findUrgentUnread) {
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
}
|
|
7905
|
-
const ageMs = Date.now() - new Date(msg.created_at).getTime();
|
|
7906
|
-
if (ageMs > 5 * 60 * 1e3) {
|
|
7907
|
-
process.stderr.write(
|
|
7908
|
-
`[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
|
|
7909
|
-
`
|
|
8822
|
+
for (const exeSession of sessions) {
|
|
8823
|
+
try {
|
|
8824
|
+
const urgent = await deps.findUrgentUnread(exeSession);
|
|
8825
|
+
for (const msg of urgent) {
|
|
8826
|
+
try {
|
|
8827
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8828
|
+
(s) => s.startsWith(`${msg.target_agent}-`) && s.endsWith(`-${exeSession}`)
|
|
7910
8829
|
);
|
|
8830
|
+
for (const sess of employeeSessions) {
|
|
8831
|
+
deps.sendIntercom(sess);
|
|
8832
|
+
}
|
|
8833
|
+
const ageMs = Date.now() - new Date(msg.created_at).getTime();
|
|
8834
|
+
if (ageMs > 5 * 60 * 1e3) {
|
|
8835
|
+
process.stderr.write(
|
|
8836
|
+
`[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
|
|
8837
|
+
`
|
|
8838
|
+
);
|
|
8839
|
+
}
|
|
8840
|
+
} catch {
|
|
7911
8841
|
}
|
|
7912
|
-
} catch {
|
|
7913
8842
|
}
|
|
8843
|
+
} catch {
|
|
7914
8844
|
}
|
|
7915
|
-
} catch {
|
|
7916
8845
|
}
|
|
7917
8846
|
}
|
|
7918
8847
|
if (deps.findUnstartedTasks) {
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
for (const sess of employeeSessions) {
|
|
7927
|
-
deps.sendIntercom(sess);
|
|
7928
|
-
}
|
|
7929
|
-
const ageMs = Date.now() - new Date(task.created_at).getTime();
|
|
7930
|
-
const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
|
|
7931
|
-
if (ageMs > UNSTARTED_WARNING_MS) {
|
|
7932
|
-
process.stderr.write(
|
|
7933
|
-
`[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
|
|
7934
|
-
`
|
|
8848
|
+
for (const exeSession of sessions) {
|
|
8849
|
+
try {
|
|
8850
|
+
const unstarted = await deps.findUnstartedTasks(exeSession);
|
|
8851
|
+
for (const task of unstarted) {
|
|
8852
|
+
try {
|
|
8853
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8854
|
+
(s) => (s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)) && s.endsWith(`-${exeSession}`)
|
|
7935
8855
|
);
|
|
8856
|
+
for (const sess of employeeSessions) {
|
|
8857
|
+
deps.sendIntercom(sess);
|
|
8858
|
+
}
|
|
8859
|
+
const ageMs = Date.now() - new Date(task.created_at).getTime();
|
|
8860
|
+
const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
|
|
8861
|
+
if (ageMs > UNSTARTED_WARNING_MS) {
|
|
8862
|
+
process.stderr.write(
|
|
8863
|
+
`[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
|
|
8864
|
+
`
|
|
8865
|
+
);
|
|
8866
|
+
}
|
|
8867
|
+
} catch {
|
|
7936
8868
|
}
|
|
7937
|
-
} catch {
|
|
7938
8869
|
}
|
|
8870
|
+
} catch {
|
|
7939
8871
|
}
|
|
7940
|
-
} catch {
|
|
7941
8872
|
}
|
|
7942
8873
|
}
|
|
7943
8874
|
return sent;
|
|
@@ -7950,9 +8881,9 @@ function createRealDeps(getClient2) {
|
|
|
7950
8881
|
timeout: 3e3
|
|
7951
8882
|
}).trim().split("\n").filter(Boolean);
|
|
7952
8883
|
},
|
|
7953
|
-
countPendingReviews: async () => {
|
|
8884
|
+
countPendingReviews: async (sessionScope) => {
|
|
7954
8885
|
const client = getClient2();
|
|
7955
|
-
const rpScope =
|
|
8886
|
+
const rpScope = strictSessionScopeFilter(sessionScope);
|
|
7956
8887
|
const result = await client.execute({
|
|
7957
8888
|
sql: `SELECT COUNT(*) as count FROM tasks
|
|
7958
8889
|
WHERE status = 'needs_review'${rpScope.sql}`,
|
|
@@ -7964,10 +8895,10 @@ function createRealDeps(getClient2) {
|
|
|
7964
8895
|
const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
|
|
7965
8896
|
centralSend(session);
|
|
7966
8897
|
},
|
|
7967
|
-
findOrphanedDoneTasks: async () => {
|
|
8898
|
+
findOrphanedDoneTasks: async (sessionScope) => {
|
|
7968
8899
|
const client = getClient2();
|
|
7969
8900
|
const coordinatorName = getCoordinatorName();
|
|
7970
|
-
const odScope =
|
|
8901
|
+
const odScope = strictSessionScopeFilter(sessionScope, "t");
|
|
7971
8902
|
const result = await client.execute({
|
|
7972
8903
|
sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
|
|
7973
8904
|
t.project_name, t.task_file, t.result, t.status
|
|
@@ -7992,24 +8923,25 @@ function createRealDeps(getClient2) {
|
|
|
7992
8923
|
process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
|
|
7993
8924
|
`);
|
|
7994
8925
|
},
|
|
7995
|
-
findUrgentUnread: async () => {
|
|
8926
|
+
findUrgentUnread: async (sessionScope) => {
|
|
7996
8927
|
const client = getClient2();
|
|
8928
|
+
const msgScope = strictSessionScopeFilter(sessionScope);
|
|
7997
8929
|
const result = await client.execute({
|
|
7998
8930
|
sql: `SELECT id, target_agent, content, created_at
|
|
7999
8931
|
FROM messages
|
|
8000
8932
|
WHERE priority = 'urgent'
|
|
8001
8933
|
AND status IN ('pending', 'delivered')
|
|
8002
|
-
AND created_at <= datetime('now', '-2 minutes')
|
|
8934
|
+
AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
|
|
8003
8935
|
ORDER BY created_at ASC
|
|
8004
8936
|
LIMIT 10`,
|
|
8005
|
-
args: []
|
|
8937
|
+
args: [...msgScope.args]
|
|
8006
8938
|
});
|
|
8007
8939
|
return result.rows;
|
|
8008
8940
|
},
|
|
8009
|
-
findUnstartedTasks: async () => {
|
|
8941
|
+
findUnstartedTasks: async (sessionScope) => {
|
|
8010
8942
|
const client = getClient2();
|
|
8011
8943
|
const coordinatorName = getCoordinatorName();
|
|
8012
|
-
const usScope =
|
|
8944
|
+
const usScope = strictSessionScopeFilter(sessionScope);
|
|
8013
8945
|
const result = await client.execute({
|
|
8014
8946
|
sql: `SELECT id, title, assigned_to, created_at
|
|
8015
8947
|
FROM tasks
|
|
@@ -8444,10 +9376,10 @@ async function disposeEmbedder() {
|
|
|
8444
9376
|
async function embedDirect(text) {
|
|
8445
9377
|
const llamaCpp = await import("node-llama-cpp");
|
|
8446
9378
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8447
|
-
const { existsSync:
|
|
8448
|
-
const
|
|
8449
|
-
const modelPath =
|
|
8450
|
-
if (!
|
|
9379
|
+
const { existsSync: existsSync21 } = await import("fs");
|
|
9380
|
+
const path25 = await import("path");
|
|
9381
|
+
const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
9382
|
+
if (!existsSync21(modelPath)) {
|
|
8451
9383
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
8452
9384
|
}
|
|
8453
9385
|
const llama = await llamaCpp.getLlama();
|
|
@@ -8669,13 +9601,13 @@ __export(graph_rag_exports, {
|
|
|
8669
9601
|
resolveAlias: () => resolveAlias,
|
|
8670
9602
|
storeExtraction: () => storeExtraction
|
|
8671
9603
|
});
|
|
8672
|
-
import
|
|
9604
|
+
import crypto7 from "crypto";
|
|
8673
9605
|
function normalizeEntityName(name) {
|
|
8674
9606
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
8675
9607
|
}
|
|
8676
9608
|
function entityId(name, type) {
|
|
8677
9609
|
const normalized = normalizeEntityName(name);
|
|
8678
|
-
return
|
|
9610
|
+
return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
8679
9611
|
}
|
|
8680
9612
|
async function resolveAlias(client, name) {
|
|
8681
9613
|
const normalized = normalizeEntityName(name);
|
|
@@ -8925,7 +9857,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
8925
9857
|
const targetAlias = await resolveAlias(client, r.target);
|
|
8926
9858
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
8927
9859
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
8928
|
-
const relId =
|
|
9860
|
+
const relId = crypto7.randomUUID().slice(0, 16);
|
|
8929
9861
|
try {
|
|
8930
9862
|
await client.execute({
|
|
8931
9863
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -8988,7 +9920,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
8988
9920
|
}
|
|
8989
9921
|
}
|
|
8990
9922
|
for (const h of extraction.hyperedges) {
|
|
8991
|
-
const hId =
|
|
9923
|
+
const hId = crypto7.randomUUID().slice(0, 16);
|
|
8992
9924
|
try {
|
|
8993
9925
|
await client.execute({
|
|
8994
9926
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -9052,7 +9984,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
9052
9984
|
totalEntities += stored.entitiesStored;
|
|
9053
9985
|
totalRelationships += stored.relationshipsStored;
|
|
9054
9986
|
}
|
|
9055
|
-
const contentHash =
|
|
9987
|
+
const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
9056
9988
|
await client.execute({
|
|
9057
9989
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
9058
9990
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -9169,8 +10101,8 @@ __export(wiki_sync_exports, {
|
|
|
9169
10101
|
listWorkspaces: () => listWorkspaces,
|
|
9170
10102
|
syncMemories: () => syncMemories
|
|
9171
10103
|
});
|
|
9172
|
-
async function wikiRequest(config,
|
|
9173
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
10104
|
+
async function wikiRequest(config, path25, method = "GET", body) {
|
|
10105
|
+
const url = `${config.wikiUrl}/api/v1${path25}`;
|
|
9174
10106
|
const headers = {
|
|
9175
10107
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
9176
10108
|
"Content-Type": "application/json"
|
|
@@ -9182,7 +10114,7 @@ async function wikiRequest(config, path23, method = "GET", body) {
|
|
|
9182
10114
|
signal: AbortSignal.timeout(3e4)
|
|
9183
10115
|
});
|
|
9184
10116
|
if (!response.ok) {
|
|
9185
|
-
throw new Error(`Wiki API ${method} ${
|
|
10117
|
+
throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
|
|
9186
10118
|
}
|
|
9187
10119
|
return response.json();
|
|
9188
10120
|
}
|
|
@@ -9294,8 +10226,8 @@ __export(token_spend_exports, {
|
|
|
9294
10226
|
import { readdir } from "fs/promises";
|
|
9295
10227
|
import { createReadStream } from "fs";
|
|
9296
10228
|
import { createInterface } from "readline";
|
|
9297
|
-
import
|
|
9298
|
-
import
|
|
10229
|
+
import path21 from "path";
|
|
10230
|
+
import os13 from "os";
|
|
9299
10231
|
function getPricing(model) {
|
|
9300
10232
|
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
9301
10233
|
const stripped = model.replace(/-\d{8}$/, "");
|
|
@@ -9307,29 +10239,33 @@ function getPricing(model) {
|
|
|
9307
10239
|
return DEFAULT_PRICING;
|
|
9308
10240
|
}
|
|
9309
10241
|
async function getAgentSpend(period = "7d") {
|
|
10242
|
+
const cached = _spendCache.get(period);
|
|
10243
|
+
if (cached && Date.now() < cached.expires) {
|
|
10244
|
+
return cached.result;
|
|
10245
|
+
}
|
|
9310
10246
|
const cutoff = periodToCutoff(period);
|
|
9311
10247
|
const client = getClient();
|
|
9312
|
-
const
|
|
10248
|
+
const dbResult = await client.execute({
|
|
9313
10249
|
sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
|
|
9314
10250
|
args: [cutoff]
|
|
9315
10251
|
});
|
|
9316
|
-
if (
|
|
10252
|
+
if (dbResult.rows.length === 0) return [];
|
|
9317
10253
|
const sessionAgent = /* @__PURE__ */ new Map();
|
|
9318
|
-
for (const row of
|
|
10254
|
+
for (const row of dbResult.rows) {
|
|
9319
10255
|
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
9320
10256
|
}
|
|
9321
|
-
const claudeDir =
|
|
10257
|
+
const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
|
|
9322
10258
|
let projectDirs = [];
|
|
9323
10259
|
try {
|
|
9324
10260
|
const entries = await readdir(claudeDir);
|
|
9325
|
-
projectDirs = entries.map((e) =>
|
|
10261
|
+
projectDirs = entries.map((e) => path21.join(claudeDir, e));
|
|
9326
10262
|
} catch {
|
|
9327
10263
|
return [];
|
|
9328
10264
|
}
|
|
9329
10265
|
const agentTotals = /* @__PURE__ */ new Map();
|
|
9330
10266
|
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
9331
10267
|
for (const dir of projectDirs) {
|
|
9332
|
-
const jsonlPath =
|
|
10268
|
+
const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
|
|
9333
10269
|
try {
|
|
9334
10270
|
const usage = await extractSessionUsage(jsonlPath);
|
|
9335
10271
|
if (usage.input === 0 && usage.output === 0) continue;
|
|
@@ -9353,7 +10289,7 @@ async function getAgentSpend(period = "7d") {
|
|
|
9353
10289
|
}
|
|
9354
10290
|
}
|
|
9355
10291
|
}
|
|
9356
|
-
|
|
10292
|
+
const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
|
|
9357
10293
|
agentId,
|
|
9358
10294
|
inputTokens: t.input,
|
|
9359
10295
|
outputTokens: t.output,
|
|
@@ -9363,6 +10299,8 @@ async function getAgentSpend(period = "7d") {
|
|
|
9363
10299
|
sessions: t.sessions.size,
|
|
9364
10300
|
period
|
|
9365
10301
|
})).sort((a, b) => b.costUSD - a.costUSD);
|
|
10302
|
+
_spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
|
|
10303
|
+
return result;
|
|
9366
10304
|
}
|
|
9367
10305
|
async function extractSessionUsage(jsonlPath) {
|
|
9368
10306
|
let input = 0;
|
|
@@ -9409,7 +10347,7 @@ function periodToCutoff(period) {
|
|
|
9409
10347
|
const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
|
|
9410
10348
|
return new Date(Date.now() - ms).toISOString();
|
|
9411
10349
|
}
|
|
9412
|
-
var MODEL_PRICING, DEFAULT_PRICING;
|
|
10350
|
+
var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
|
|
9413
10351
|
var init_token_spend = __esm({
|
|
9414
10352
|
"src/lib/token-spend.ts"() {
|
|
9415
10353
|
"use strict";
|
|
@@ -9439,6 +10377,8 @@ var init_token_spend = __esm({
|
|
|
9439
10377
|
"claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
|
|
9440
10378
|
};
|
|
9441
10379
|
DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
|
|
10380
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10381
|
+
_spendCache = /* @__PURE__ */ new Map();
|
|
9442
10382
|
}
|
|
9443
10383
|
});
|
|
9444
10384
|
|
|
@@ -9450,11 +10390,11 @@ __export(update_check_exports, {
|
|
|
9450
10390
|
getRemoteVersion: () => getRemoteVersion
|
|
9451
10391
|
});
|
|
9452
10392
|
import { execSync as execSync11 } from "child_process";
|
|
9453
|
-
import { readFileSync as
|
|
9454
|
-
import
|
|
10393
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
10394
|
+
import path22 from "path";
|
|
9455
10395
|
function getLocalVersion(packageRoot) {
|
|
9456
|
-
const pkgPath =
|
|
9457
|
-
const pkg = JSON.parse(
|
|
10396
|
+
const pkgPath = path22.join(packageRoot, "package.json");
|
|
10397
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
9458
10398
|
return pkg.version;
|
|
9459
10399
|
}
|
|
9460
10400
|
function getRemoteVersion() {
|
|
@@ -9497,16 +10437,16 @@ __export(ws_auth_exports, {
|
|
|
9497
10437
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
9498
10438
|
hashAuthToken: () => hashAuthToken
|
|
9499
10439
|
});
|
|
9500
|
-
import
|
|
10440
|
+
import crypto8 from "crypto";
|
|
9501
10441
|
function deriveWsAuthToken(masterKey) {
|
|
9502
|
-
return Buffer.from(
|
|
10442
|
+
return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
9503
10443
|
}
|
|
9504
10444
|
function deriveOrgId(masterKey) {
|
|
9505
|
-
const raw = Buffer.from(
|
|
9506
|
-
return
|
|
10445
|
+
const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
10446
|
+
return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
9507
10447
|
}
|
|
9508
10448
|
function hashAuthToken(token) {
|
|
9509
|
-
return
|
|
10449
|
+
return crypto8.createHash("sha256").update(token).digest("hex");
|
|
9510
10450
|
}
|
|
9511
10451
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
9512
10452
|
var init_ws_auth = __esm({
|
|
@@ -9524,14 +10464,14 @@ __export(device_registry_exports, {
|
|
|
9524
10464
|
resolveTargetDevice: () => resolveTargetDevice,
|
|
9525
10465
|
setFriendlyName: () => setFriendlyName
|
|
9526
10466
|
});
|
|
9527
|
-
import
|
|
9528
|
-
import
|
|
9529
|
-
import { readFileSync as
|
|
9530
|
-
import
|
|
10467
|
+
import crypto9 from "crypto";
|
|
10468
|
+
import os14 from "os";
|
|
10469
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
|
|
10470
|
+
import path23 from "path";
|
|
9531
10471
|
function getDeviceInfo() {
|
|
9532
|
-
if (
|
|
10472
|
+
if (existsSync19(DEVICE_JSON_PATH)) {
|
|
9533
10473
|
try {
|
|
9534
|
-
const raw =
|
|
10474
|
+
const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
|
|
9535
10475
|
const data = JSON.parse(raw);
|
|
9536
10476
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
9537
10477
|
return data;
|
|
@@ -9539,20 +10479,20 @@ function getDeviceInfo() {
|
|
|
9539
10479
|
} catch {
|
|
9540
10480
|
}
|
|
9541
10481
|
}
|
|
9542
|
-
const hostname =
|
|
10482
|
+
const hostname = os14.hostname();
|
|
9543
10483
|
const info = {
|
|
9544
|
-
deviceId:
|
|
10484
|
+
deviceId: crypto9.randomUUID(),
|
|
9545
10485
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
9546
10486
|
hostname
|
|
9547
10487
|
};
|
|
9548
|
-
mkdirSync8(
|
|
9549
|
-
|
|
10488
|
+
mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
10489
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
9550
10490
|
return info;
|
|
9551
10491
|
}
|
|
9552
10492
|
function setFriendlyName(name) {
|
|
9553
10493
|
const info = getDeviceInfo();
|
|
9554
10494
|
info.friendlyName = name;
|
|
9555
|
-
|
|
10495
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
9556
10496
|
}
|
|
9557
10497
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
9558
10498
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -9586,7 +10526,7 @@ var init_device_registry = __esm({
|
|
|
9586
10526
|
"src/lib/device-registry.ts"() {
|
|
9587
10527
|
"use strict";
|
|
9588
10528
|
init_config();
|
|
9589
|
-
DEVICE_JSON_PATH =
|
|
10529
|
+
DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
|
|
9590
10530
|
}
|
|
9591
10531
|
});
|
|
9592
10532
|
|
|
@@ -9810,10 +10750,10 @@ __export(messaging_exports, {
|
|
|
9810
10750
|
sendMessage: () => sendMessage,
|
|
9811
10751
|
setWsClientSend: () => setWsClientSend
|
|
9812
10752
|
});
|
|
9813
|
-
import
|
|
10753
|
+
import crypto10 from "crypto";
|
|
9814
10754
|
function generateUlid() {
|
|
9815
10755
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
9816
|
-
const random =
|
|
10756
|
+
const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
|
|
9817
10757
|
return (timestamp + random).toUpperCase();
|
|
9818
10758
|
}
|
|
9819
10759
|
function rowToMessage(row) {
|
|
@@ -9824,6 +10764,7 @@ function rowToMessage(row) {
|
|
|
9824
10764
|
targetAgent: row.target_agent,
|
|
9825
10765
|
targetProject: row.target_project ?? null,
|
|
9826
10766
|
targetDevice: row.target_device,
|
|
10767
|
+
sessionScope: row.session_scope ?? null,
|
|
9827
10768
|
content: row.content,
|
|
9828
10769
|
priority: row.priority ?? "normal",
|
|
9829
10770
|
status: row.status ?? "pending",
|
|
@@ -9841,15 +10782,17 @@ async function sendMessage(input) {
|
|
|
9841
10782
|
const id = generateUlid();
|
|
9842
10783
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9843
10784
|
const targetDevice = input.targetDevice ?? "local";
|
|
10785
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
9844
10786
|
await client.execute({
|
|
9845
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
9846
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10787
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
10788
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
9847
10789
|
args: [
|
|
9848
10790
|
id,
|
|
9849
10791
|
input.fromAgent,
|
|
9850
10792
|
input.targetAgent,
|
|
9851
10793
|
input.targetProject ?? null,
|
|
9852
10794
|
targetDevice,
|
|
10795
|
+
sessionScope,
|
|
9853
10796
|
input.content,
|
|
9854
10797
|
input.priority ?? "normal",
|
|
9855
10798
|
now
|
|
@@ -9863,9 +10806,10 @@ async function sendMessage(input) {
|
|
|
9863
10806
|
}
|
|
9864
10807
|
} catch {
|
|
9865
10808
|
}
|
|
10809
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
9866
10810
|
const result = await client.execute({
|
|
9867
|
-
sql:
|
|
9868
|
-
args: [id]
|
|
10811
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
10812
|
+
args: [id, ...sentScope.args]
|
|
9869
10813
|
});
|
|
9870
10814
|
return rowToMessage(result.rows[0]);
|
|
9871
10815
|
}
|
|
@@ -9889,6 +10833,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
9889
10833
|
fromAgent: msg.fromAgent,
|
|
9890
10834
|
targetAgent: msg.targetAgent,
|
|
9891
10835
|
targetProject: msg.targetProject,
|
|
10836
|
+
sessionScope: msg.sessionScope,
|
|
9892
10837
|
content: msg.content,
|
|
9893
10838
|
priority: msg.priority,
|
|
9894
10839
|
createdAt: msg.createdAt
|
|
@@ -9932,7 +10877,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
9932
10877
|
} catch {
|
|
9933
10878
|
const newRetryCount = msg.retryCount + 1;
|
|
9934
10879
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
9935
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
10880
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
9936
10881
|
} else {
|
|
9937
10882
|
await client.execute({
|
|
9938
10883
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -9942,85 +10887,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
9942
10887
|
return false;
|
|
9943
10888
|
}
|
|
9944
10889
|
}
|
|
9945
|
-
async function getPendingMessages(targetAgent) {
|
|
10890
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
9946
10891
|
const client = getClient();
|
|
10892
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9947
10893
|
const result = await client.execute({
|
|
9948
10894
|
sql: `SELECT * FROM messages
|
|
9949
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
10895
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
9950
10896
|
ORDER BY id`,
|
|
9951
|
-
args: [targetAgent]
|
|
10897
|
+
args: [targetAgent, ...scope.args]
|
|
9952
10898
|
});
|
|
9953
10899
|
return result.rows.map((row) => rowToMessage(row));
|
|
9954
10900
|
}
|
|
9955
|
-
async function markRead(messageId) {
|
|
10901
|
+
async function markRead(messageId, sessionScope) {
|
|
9956
10902
|
const client = getClient();
|
|
10903
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9957
10904
|
await client.execute({
|
|
9958
|
-
sql:
|
|
9959
|
-
|
|
10905
|
+
sql: `UPDATE messages SET status = 'read'
|
|
10906
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
10907
|
+
args: [messageId, ...scope.args]
|
|
9960
10908
|
});
|
|
9961
10909
|
}
|
|
9962
|
-
async function markAcknowledged(messageId) {
|
|
10910
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
9963
10911
|
const client = getClient();
|
|
10912
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9964
10913
|
await client.execute({
|
|
9965
|
-
sql:
|
|
9966
|
-
|
|
10914
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
10915
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
10916
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9967
10917
|
});
|
|
9968
10918
|
}
|
|
9969
|
-
async function markProcessed(messageId) {
|
|
10919
|
+
async function markProcessed(messageId, sessionScope) {
|
|
9970
10920
|
const client = getClient();
|
|
10921
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9971
10922
|
await client.execute({
|
|
9972
|
-
sql:
|
|
9973
|
-
|
|
10923
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
10924
|
+
WHERE id = ?${scope.sql}`,
|
|
10925
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
9974
10926
|
});
|
|
9975
10927
|
}
|
|
9976
|
-
async function getMessageStatus(messageId) {
|
|
10928
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
9977
10929
|
const client = getClient();
|
|
10930
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9978
10931
|
const result = await client.execute({
|
|
9979
|
-
sql:
|
|
9980
|
-
args: [messageId]
|
|
10932
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
10933
|
+
args: [messageId, ...scope.args]
|
|
9981
10934
|
});
|
|
9982
10935
|
return result.rows[0]?.status ?? null;
|
|
9983
10936
|
}
|
|
9984
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
10937
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
9985
10938
|
const client = getClient();
|
|
10939
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9986
10940
|
const result = await client.execute({
|
|
9987
10941
|
sql: `SELECT * FROM messages
|
|
9988
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
10942
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
9989
10943
|
ORDER BY id`,
|
|
9990
|
-
args: [targetAgent]
|
|
10944
|
+
args: [targetAgent, ...scope.args]
|
|
9991
10945
|
});
|
|
9992
10946
|
return result.rows.map((row) => rowToMessage(row));
|
|
9993
10947
|
}
|
|
9994
|
-
async function getReadMessages(targetAgent) {
|
|
10948
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
9995
10949
|
const client = getClient();
|
|
10950
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
9996
10951
|
const result = await client.execute({
|
|
9997
|
-
sql:
|
|
9998
|
-
|
|
10952
|
+
sql: `SELECT * FROM messages
|
|
10953
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
10954
|
+
ORDER BY id`,
|
|
10955
|
+
args: [targetAgent, ...scope.args]
|
|
9999
10956
|
});
|
|
10000
10957
|
return result.rows.map((row) => rowToMessage(row));
|
|
10001
10958
|
}
|
|
10002
|
-
async function markFailed(messageId, reason) {
|
|
10959
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
10003
10960
|
const client = getClient();
|
|
10961
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10004
10962
|
await client.execute({
|
|
10005
|
-
sql:
|
|
10006
|
-
|
|
10963
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
10964
|
+
WHERE id = ?${scope.sql}`,
|
|
10965
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
10007
10966
|
});
|
|
10008
10967
|
}
|
|
10009
|
-
async function getFailedMessages() {
|
|
10968
|
+
async function getFailedMessages(sessionScope) {
|
|
10010
10969
|
const client = getClient();
|
|
10970
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10011
10971
|
const result = await client.execute({
|
|
10012
|
-
sql:
|
|
10013
|
-
args: []
|
|
10972
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
10973
|
+
args: [...scope.args]
|
|
10014
10974
|
});
|
|
10015
10975
|
return result.rows.map((row) => rowToMessage(row));
|
|
10016
10976
|
}
|
|
10017
|
-
async function retryPendingMessages() {
|
|
10977
|
+
async function retryPendingMessages(sessionScope) {
|
|
10018
10978
|
const client = getClient();
|
|
10979
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10019
10980
|
const result = await client.execute({
|
|
10020
10981
|
sql: `SELECT * FROM messages
|
|
10021
|
-
WHERE status = 'pending' AND retry_count <
|
|
10982
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
10022
10983
|
ORDER BY id`,
|
|
10023
|
-
args: [MAX_RETRIES3]
|
|
10984
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
10024
10985
|
});
|
|
10025
10986
|
let delivered = 0;
|
|
10026
10987
|
for (const row of result.rows) {
|
|
@@ -10039,6 +11000,7 @@ var init_messaging = __esm({
|
|
|
10039
11000
|
"use strict";
|
|
10040
11001
|
init_database();
|
|
10041
11002
|
init_tmux_routing();
|
|
11003
|
+
init_task_scope();
|
|
10042
11004
|
MAX_RETRIES3 = 10;
|
|
10043
11005
|
_wsClientSend = null;
|
|
10044
11006
|
}
|
|
@@ -10048,19 +11010,22 @@ var init_messaging = __esm({
|
|
|
10048
11010
|
init_config();
|
|
10049
11011
|
init_memory();
|
|
10050
11012
|
init_daemon_protocol();
|
|
11013
|
+
init_daemon_auth();
|
|
10051
11014
|
init_daemon_orchestration();
|
|
10052
11015
|
import net2 from "net";
|
|
10053
|
-
import { writeFileSync as
|
|
10054
|
-
import
|
|
11016
|
+
import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
|
|
11017
|
+
import path24 from "path";
|
|
10055
11018
|
import { getLlama } from "node-llama-cpp";
|
|
10056
|
-
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
10057
|
-
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
11019
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
|
|
11020
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
|
|
10058
11021
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
10059
11022
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
10060
11023
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
11024
|
+
var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
|
|
10061
11025
|
var _context = null;
|
|
10062
11026
|
var _model = null;
|
|
10063
11027
|
var _llama = null;
|
|
11028
|
+
var _daemonToken = "";
|
|
10064
11029
|
var MAX_QUEUE_SIZE = 1e3;
|
|
10065
11030
|
var highQueue = [];
|
|
10066
11031
|
var lowQueue = [];
|
|
@@ -10080,8 +11045,8 @@ function enqueue(queue, entry) {
|
|
|
10080
11045
|
queue.push(entry);
|
|
10081
11046
|
}
|
|
10082
11047
|
async function loadModel() {
|
|
10083
|
-
const modelPath =
|
|
10084
|
-
if (!
|
|
11048
|
+
const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
|
|
11049
|
+
if (!existsSync20(modelPath)) {
|
|
10085
11050
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
10086
11051
|
`);
|
|
10087
11052
|
process.exit(1);
|
|
@@ -10105,6 +11070,7 @@ async function processQueue() {
|
|
|
10105
11070
|
for (const text of entry.request.texts) {
|
|
10106
11071
|
const embedding = await _context.getEmbeddingFor(text);
|
|
10107
11072
|
const vector = Array.from(embedding.vector);
|
|
11073
|
+
embedding.vector = null;
|
|
10108
11074
|
if (vector.length !== EMBEDDING_DIM) {
|
|
10109
11075
|
throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
|
|
10110
11076
|
}
|
|
@@ -10150,6 +11116,11 @@ function checkIdle() {
|
|
|
10150
11116
|
}
|
|
10151
11117
|
async function shutdown() {
|
|
10152
11118
|
resetIdleTimer();
|
|
11119
|
+
try {
|
|
11120
|
+
const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
11121
|
+
disposeShards2();
|
|
11122
|
+
} catch {
|
|
11123
|
+
}
|
|
10153
11124
|
if (_context) {
|
|
10154
11125
|
try {
|
|
10155
11126
|
await _context.dispose();
|
|
@@ -10188,6 +11159,7 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10188
11159
|
}
|
|
10189
11160
|
}
|
|
10190
11161
|
const dbConnected = _storeInitialized;
|
|
11162
|
+
const mem = process.memoryUsage();
|
|
10191
11163
|
sendResponse(socket, {
|
|
10192
11164
|
id: requestId,
|
|
10193
11165
|
...healthy && testOk ? {
|
|
@@ -10195,7 +11167,13 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10195
11167
|
status: "ok",
|
|
10196
11168
|
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
10197
11169
|
requests_served: _requestsServed,
|
|
10198
|
-
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
|
|
11170
|
+
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed },
|
|
11171
|
+
memory: {
|
|
11172
|
+
rss_mb: Math.round(mem.rss / 1024 / 1024),
|
|
11173
|
+
heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
|
|
11174
|
+
external_mb: Math.round(mem.external / 1024 / 1024),
|
|
11175
|
+
array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
|
|
11176
|
+
}
|
|
10199
11177
|
}
|
|
10200
11178
|
} : { error: "unhealthy: model not loaded or test embed failed" }
|
|
10201
11179
|
});
|
|
@@ -10249,13 +11227,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
|
|
|
10249
11227
|
});
|
|
10250
11228
|
}
|
|
10251
11229
|
}
|
|
11230
|
+
var _ingestCount = 0;
|
|
11231
|
+
async function handleIngest(req) {
|
|
11232
|
+
try {
|
|
11233
|
+
if (!await ensureStoreForPolling()) return;
|
|
11234
|
+
if (!req.rawText || req.rawText.length < 50) return;
|
|
11235
|
+
let vectorBlob = null;
|
|
11236
|
+
if (_context) {
|
|
11237
|
+
try {
|
|
11238
|
+
const embedding = await _context.getEmbeddingFor(req.rawText);
|
|
11239
|
+
const vector = Array.from(embedding.vector);
|
|
11240
|
+
embedding.vector = null;
|
|
11241
|
+
if (vector.length === EMBEDDING_DIM) {
|
|
11242
|
+
const { vectorToBlob: vectorToBlob2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
11243
|
+
vectorBlob = vectorToBlob2(vector);
|
|
11244
|
+
}
|
|
11245
|
+
} catch {
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
11249
|
+
const client = getClient2();
|
|
11250
|
+
const { randomUUID: randomUUID5 } = await import("crypto");
|
|
11251
|
+
const id = randomUUID5();
|
|
11252
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11253
|
+
await client.execute({
|
|
11254
|
+
sql: `INSERT INTO memories (id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, vector, task_id, confidence, draft, memory_type, trajectory)
|
|
11255
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
|
|
11256
|
+
args: [
|
|
11257
|
+
id,
|
|
11258
|
+
req.agentId,
|
|
11259
|
+
req.agentRole,
|
|
11260
|
+
req.sessionId,
|
|
11261
|
+
now,
|
|
11262
|
+
req.toolName,
|
|
11263
|
+
req.projectName,
|
|
11264
|
+
req.hasError ? 1 : 0,
|
|
11265
|
+
req.rawText,
|
|
11266
|
+
vectorBlob,
|
|
11267
|
+
req.taskId ?? null,
|
|
11268
|
+
req.confidence ?? 0.7,
|
|
11269
|
+
req.draft ? 1 : 0,
|
|
11270
|
+
req.trajectory ? JSON.stringify(req.trajectory) : null
|
|
11271
|
+
]
|
|
11272
|
+
});
|
|
11273
|
+
_ingestCount++;
|
|
11274
|
+
} catch (err) {
|
|
11275
|
+
process.stderr.write(`[exed] Ingest error: ${err instanceof Error ? err.message : String(err)}
|
|
11276
|
+
`);
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
10252
11279
|
function startServer() {
|
|
10253
|
-
mkdirSync9(
|
|
11280
|
+
mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
|
|
11281
|
+
try {
|
|
11282
|
+
chmodSync2(path24.dirname(SOCKET_PATH2), 448);
|
|
11283
|
+
} catch {
|
|
11284
|
+
}
|
|
11285
|
+
_daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
|
|
10254
11286
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
10255
|
-
const oldPath =
|
|
11287
|
+
const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
|
|
10256
11288
|
try {
|
|
10257
11289
|
if (oldFile.endsWith(".pid")) {
|
|
10258
|
-
const pid = parseInt(
|
|
11290
|
+
const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
|
|
10259
11291
|
if (pid > 0) try {
|
|
10260
11292
|
process.kill(pid, "SIGKILL");
|
|
10261
11293
|
} catch {
|
|
@@ -10287,6 +11319,10 @@ function startServer() {
|
|
|
10287
11319
|
if (!line) continue;
|
|
10288
11320
|
try {
|
|
10289
11321
|
const request = JSON.parse(line);
|
|
11322
|
+
if (!request.token || request.token !== _daemonToken) {
|
|
11323
|
+
sendResponse(socket, { id: request.id ?? "unauthorized", error: "Unauthorized daemon request" });
|
|
11324
|
+
continue;
|
|
11325
|
+
}
|
|
10290
11326
|
if (request.type === "health") {
|
|
10291
11327
|
void handleHealthCheck(socket, request.id ?? "health");
|
|
10292
11328
|
continue;
|
|
@@ -10307,6 +11343,10 @@ function startServer() {
|
|
|
10307
11343
|
void handleDbBatch(socket, request.id, request.statements, request.mode);
|
|
10308
11344
|
continue;
|
|
10309
11345
|
}
|
|
11346
|
+
if (request.type === "ingest") {
|
|
11347
|
+
void handleIngest(request);
|
|
11348
|
+
continue;
|
|
11349
|
+
}
|
|
10310
11350
|
if (!request.id || !Array.isArray(request.texts)) {
|
|
10311
11351
|
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
|
|
10312
11352
|
continue;
|
|
@@ -10344,7 +11384,15 @@ function startServer() {
|
|
|
10344
11384
|
server.listen(SOCKET_PATH2, () => {
|
|
10345
11385
|
process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
|
|
10346
11386
|
`);
|
|
10347
|
-
|
|
11387
|
+
try {
|
|
11388
|
+
chmodSync2(SOCKET_PATH2, 384);
|
|
11389
|
+
} catch {
|
|
11390
|
+
}
|
|
11391
|
+
writeFileSync11(PID_PATH2, String(process.pid));
|
|
11392
|
+
try {
|
|
11393
|
+
chmodSync2(PID_PATH2, 384);
|
|
11394
|
+
} catch {
|
|
11395
|
+
}
|
|
10348
11396
|
checkIdle();
|
|
10349
11397
|
});
|
|
10350
11398
|
}
|
|
@@ -10637,7 +11685,7 @@ function startWikiSync() {
|
|
|
10637
11685
|
});
|
|
10638
11686
|
}
|
|
10639
11687
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
10640
|
-
var AGENT_STATS_PATH =
|
|
11688
|
+
var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
|
|
10641
11689
|
async function writeAgentStats() {
|
|
10642
11690
|
if (!await ensureStoreForPolling()) return;
|
|
10643
11691
|
try {
|
|
@@ -10695,7 +11743,7 @@ async function writeAgentStats() {
|
|
|
10695
11743
|
pid: process.pid
|
|
10696
11744
|
}
|
|
10697
11745
|
};
|
|
10698
|
-
|
|
11746
|
+
writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
10699
11747
|
} catch (err) {
|
|
10700
11748
|
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
10701
11749
|
`);
|
|
@@ -10767,12 +11815,12 @@ function startIntercomQueueDrain() {
|
|
|
10767
11815
|
const hasInProgressTask = (session) => {
|
|
10768
11816
|
try {
|
|
10769
11817
|
const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
|
|
10770
|
-
const
|
|
10771
|
-
const { existsSync:
|
|
10772
|
-
const
|
|
11818
|
+
const path25 = __require("path");
|
|
11819
|
+
const { existsSync: existsSync21 } = __require("fs");
|
|
11820
|
+
const os15 = __require("os");
|
|
10773
11821
|
const agent = ban(session.split("-")[0] ?? session);
|
|
10774
|
-
const markerPath =
|
|
10775
|
-
return
|
|
11822
|
+
const markerPath = path25.join(os15.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
|
|
11823
|
+
return existsSync21(markerPath);
|
|
10776
11824
|
} catch {
|
|
10777
11825
|
return false;
|
|
10778
11826
|
}
|
|
@@ -10861,12 +11909,43 @@ function startAutoWake() {
|
|
|
10861
11909
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
10862
11910
|
`);
|
|
10863
11911
|
}
|
|
11912
|
+
var RSS_WARN_BYTES = 1024 * 1024 * 1024;
|
|
11913
|
+
var RSS_RESTART_BYTES = 2048 * 1024 * 1024;
|
|
11914
|
+
var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
11915
|
+
var _rssWarned = false;
|
|
11916
|
+
function startRssWatchdog() {
|
|
11917
|
+
const tick = () => {
|
|
11918
|
+
const rss = process.memoryUsage.rss();
|
|
11919
|
+
if (rss > RSS_RESTART_BYTES) {
|
|
11920
|
+
process.stderr.write(
|
|
11921
|
+
`[exed] RSS CRITICAL: ${(rss / 1024 / 1024).toFixed(0)} MB exceeds 2 GB limit \u2014 restarting.
|
|
11922
|
+
`
|
|
11923
|
+
);
|
|
11924
|
+
void shutdown();
|
|
11925
|
+
return;
|
|
11926
|
+
}
|
|
11927
|
+
if (rss > RSS_WARN_BYTES && !_rssWarned) {
|
|
11928
|
+
_rssWarned = true;
|
|
11929
|
+
const heap = process.memoryUsage();
|
|
11930
|
+
process.stderr.write(
|
|
11931
|
+
`[exed] RSS WARNING: ${(rss / 1024 / 1024).toFixed(0)} MB (heap used: ${(heap.heapUsed / 1024 / 1024).toFixed(0)} MB, external: ${(heap.external / 1024 / 1024).toFixed(0)} MB, arrayBuffers: ${(heap.arrayBuffers / 1024 / 1024).toFixed(0)} MB)
|
|
11932
|
+
`
|
|
11933
|
+
);
|
|
11934
|
+
} else if (rss < RSS_WARN_BYTES && _rssWarned) {
|
|
11935
|
+
_rssWarned = false;
|
|
11936
|
+
}
|
|
11937
|
+
};
|
|
11938
|
+
const timer = setInterval(tick, RSS_CHECK_INTERVAL_MS);
|
|
11939
|
+
timer.unref();
|
|
11940
|
+
process.stderr.write(`[exed] RSS watchdog started (warn: 1 GB, restart: 2 GB)
|
|
11941
|
+
`);
|
|
11942
|
+
}
|
|
10864
11943
|
process.on("SIGINT", () => void shutdown());
|
|
10865
11944
|
process.on("SIGTERM", () => void shutdown());
|
|
10866
11945
|
function checkExistingDaemon() {
|
|
10867
11946
|
try {
|
|
10868
|
-
if (!
|
|
10869
|
-
const pid = parseInt(
|
|
11947
|
+
if (!existsSync20(PID_PATH2)) return false;
|
|
11948
|
+
const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
|
|
10870
11949
|
if (!pid || isNaN(pid)) return false;
|
|
10871
11950
|
process.kill(pid, 0);
|
|
10872
11951
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -10954,6 +12033,7 @@ try {
|
|
|
10954
12033
|
startIntercomQueueDrain();
|
|
10955
12034
|
startConfidenceDecay();
|
|
10956
12035
|
startAutoUpdateCheck();
|
|
12036
|
+
startRssWatchdog();
|
|
10957
12037
|
try {
|
|
10958
12038
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
10959
12039
|
const config = await loadConfig2();
|