@askexenow/exe-os 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1411 -953
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +913 -543
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +418 -262
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +793 -485
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +566 -357
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +530 -319
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +547 -336
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +649 -417
- package/dist/hooks/bug-report-worker.js +486 -316
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +528 -317
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3442 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +534 -323
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +614 -382
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +569 -347
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +664 -431
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +1049 -680
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +422 -357
- package/dist/lib/tmux-routing.js +314 -248
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1408 -672
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +448 -371
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1983 -315
- package/dist/runtime/index.js +567 -355
- package/dist/tui/App.js +887 -531
- package/package.json +4 -4
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
|
|
|
@@ -998,9 +1075,9 @@ __export(employees_exports, {
|
|
|
998
1075
|
validateEmployeeName: () => validateEmployeeName
|
|
999
1076
|
});
|
|
1000
1077
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1001
|
-
import { existsSync as
|
|
1078
|
+
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1002
1079
|
import { execSync as execSync4 } from "child_process";
|
|
1003
|
-
import
|
|
1080
|
+
import path6 from "path";
|
|
1004
1081
|
import os4 from "os";
|
|
1005
1082
|
function normalizeRole(role) {
|
|
1006
1083
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -1037,7 +1114,7 @@ function validateEmployeeName(name) {
|
|
|
1037
1114
|
return { valid: true };
|
|
1038
1115
|
}
|
|
1039
1116
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1040
|
-
if (!
|
|
1117
|
+
if (!existsSync7(employeesPath)) {
|
|
1041
1118
|
return [];
|
|
1042
1119
|
}
|
|
1043
1120
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -1048,13 +1125,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
1048
1125
|
}
|
|
1049
1126
|
}
|
|
1050
1127
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1051
|
-
await mkdir2(
|
|
1128
|
+
await mkdir2(path6.dirname(employeesPath), { recursive: true });
|
|
1052
1129
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1053
1130
|
}
|
|
1054
1131
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1055
|
-
if (!
|
|
1132
|
+
if (!existsSync7(employeesPath)) return [];
|
|
1056
1133
|
try {
|
|
1057
|
-
return JSON.parse(
|
|
1134
|
+
return JSON.parse(readFileSync6(employeesPath, "utf-8"));
|
|
1058
1135
|
} catch {
|
|
1059
1136
|
return [];
|
|
1060
1137
|
}
|
|
@@ -1099,9 +1176,9 @@ function addEmployee(employees, employee) {
|
|
|
1099
1176
|
function appendToCoordinatorTeam(employee) {
|
|
1100
1177
|
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1101
1178
|
if (!coordinator) return;
|
|
1102
|
-
const idPath =
|
|
1103
|
-
if (!
|
|
1104
|
-
const content =
|
|
1179
|
+
const idPath = path6.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1180
|
+
if (!existsSync7(idPath)) return;
|
|
1181
|
+
const content = readFileSync6(idPath, "utf-8");
|
|
1105
1182
|
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1106
1183
|
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1107
1184
|
if (!teamMatch || teamMatch.index === void 0) return;
|
|
@@ -1117,7 +1194,7 @@ function appendToCoordinatorTeam(employee) {
|
|
|
1117
1194
|
} else {
|
|
1118
1195
|
updated = content.trimEnd() + "\n" + entry;
|
|
1119
1196
|
}
|
|
1120
|
-
|
|
1197
|
+
writeFileSync5(idPath, updated, "utf-8");
|
|
1121
1198
|
}
|
|
1122
1199
|
function capitalize(s) {
|
|
1123
1200
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -1151,14 +1228,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
1151
1228
|
emp.name = emp.name.toLowerCase();
|
|
1152
1229
|
changed = true;
|
|
1153
1230
|
try {
|
|
1154
|
-
const identityDir =
|
|
1155
|
-
const oldPath =
|
|
1156
|
-
const newPath =
|
|
1157
|
-
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)) {
|
|
1158
1235
|
renameSync3(oldPath, newPath);
|
|
1159
|
-
} else if (
|
|
1160
|
-
const content =
|
|
1161
|
-
|
|
1236
|
+
} else if (existsSync7(oldPath) && oldPath !== newPath) {
|
|
1237
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
1238
|
+
writeFileSync5(newPath, content, "utf-8");
|
|
1162
1239
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1163
1240
|
unlinkSync(oldPath);
|
|
1164
1241
|
}
|
|
@@ -1188,7 +1265,7 @@ function registerBinSymlinks(name) {
|
|
|
1188
1265
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1189
1266
|
return { created, skipped, errors };
|
|
1190
1267
|
}
|
|
1191
|
-
const binDir =
|
|
1268
|
+
const binDir = path6.dirname(exeBinPath);
|
|
1192
1269
|
let target;
|
|
1193
1270
|
try {
|
|
1194
1271
|
target = readlinkSync(exeBinPath);
|
|
@@ -1198,8 +1275,8 @@ function registerBinSymlinks(name) {
|
|
|
1198
1275
|
}
|
|
1199
1276
|
for (const suffix of ["", "-opencode"]) {
|
|
1200
1277
|
const linkName = `${name}${suffix}`;
|
|
1201
|
-
const linkPath =
|
|
1202
|
-
if (
|
|
1278
|
+
const linkPath = path6.join(binDir, linkName);
|
|
1279
|
+
if (existsSync7(linkPath)) {
|
|
1203
1280
|
skipped.push(linkName);
|
|
1204
1281
|
continue;
|
|
1205
1282
|
}
|
|
@@ -1217,18 +1294,18 @@ var init_employees = __esm({
|
|
|
1217
1294
|
"src/lib/employees.ts"() {
|
|
1218
1295
|
"use strict";
|
|
1219
1296
|
init_config();
|
|
1220
|
-
EMPLOYEES_PATH =
|
|
1297
|
+
EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
|
|
1221
1298
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1222
1299
|
COORDINATOR_ROLE = "COO";
|
|
1223
1300
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1224
|
-
IDENTITY_DIR =
|
|
1301
|
+
IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
|
|
1225
1302
|
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1226
1303
|
}
|
|
1227
1304
|
});
|
|
1228
1305
|
|
|
1229
1306
|
// src/lib/database-adapter.ts
|
|
1230
1307
|
import os5 from "os";
|
|
1231
|
-
import
|
|
1308
|
+
import path7 from "path";
|
|
1232
1309
|
import { createRequire } from "module";
|
|
1233
1310
|
import { pathToFileURL } from "url";
|
|
1234
1311
|
function quotedIdentifier(identifier) {
|
|
@@ -1539,8 +1616,8 @@ async function loadPrismaClient() {
|
|
|
1539
1616
|
}
|
|
1540
1617
|
return new PrismaClient2();
|
|
1541
1618
|
}
|
|
1542
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
1543
|
-
const requireFromExeDb = createRequire(
|
|
1619
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1620
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1544
1621
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1545
1622
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1546
1623
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1815,8 +1892,8 @@ import net from "net";
|
|
|
1815
1892
|
import os6 from "os";
|
|
1816
1893
|
import { spawn } from "child_process";
|
|
1817
1894
|
import { randomUUID } from "crypto";
|
|
1818
|
-
import { existsSync as
|
|
1819
|
-
import
|
|
1895
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1896
|
+
import path8 from "path";
|
|
1820
1897
|
import { fileURLToPath } from "url";
|
|
1821
1898
|
function handleData(chunk) {
|
|
1822
1899
|
_buffer += chunk.toString();
|
|
@@ -1844,9 +1921,9 @@ function handleData(chunk) {
|
|
|
1844
1921
|
}
|
|
1845
1922
|
}
|
|
1846
1923
|
function cleanupStaleFiles() {
|
|
1847
|
-
if (
|
|
1924
|
+
if (existsSync8(PID_PATH)) {
|
|
1848
1925
|
try {
|
|
1849
|
-
const pid = parseInt(
|
|
1926
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1850
1927
|
if (pid > 0) {
|
|
1851
1928
|
try {
|
|
1852
1929
|
process.kill(pid, 0);
|
|
@@ -1867,11 +1944,11 @@ function cleanupStaleFiles() {
|
|
|
1867
1944
|
}
|
|
1868
1945
|
}
|
|
1869
1946
|
function findPackageRoot() {
|
|
1870
|
-
let dir =
|
|
1871
|
-
const { root } =
|
|
1947
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1948
|
+
const { root } = path8.parse(dir);
|
|
1872
1949
|
while (dir !== root) {
|
|
1873
|
-
if (
|
|
1874
|
-
dir =
|
|
1950
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1951
|
+
dir = path8.dirname(dir);
|
|
1875
1952
|
}
|
|
1876
1953
|
return null;
|
|
1877
1954
|
}
|
|
@@ -1897,16 +1974,17 @@ function spawnDaemon() {
|
|
|
1897
1974
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1898
1975
|
return;
|
|
1899
1976
|
}
|
|
1900
|
-
const daemonPath =
|
|
1901
|
-
if (!
|
|
1977
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1978
|
+
if (!existsSync8(daemonPath)) {
|
|
1902
1979
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1903
1980
|
`);
|
|
1904
1981
|
return;
|
|
1905
1982
|
}
|
|
1906
1983
|
const resolvedPath = daemonPath;
|
|
1984
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1907
1985
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1908
1986
|
`);
|
|
1909
|
-
const logPath =
|
|
1987
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
1910
1988
|
let stderrFd = "ignore";
|
|
1911
1989
|
try {
|
|
1912
1990
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1924,7 +2002,8 @@ function spawnDaemon() {
|
|
|
1924
2002
|
TMUX_PANE: void 0,
|
|
1925
2003
|
// Prevents resolveExeSession() from scoping to one session
|
|
1926
2004
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1927
|
-
EXE_DAEMON_PID: PID_PATH
|
|
2005
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
2006
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
1928
2007
|
}
|
|
1929
2008
|
});
|
|
1930
2009
|
child.unref();
|
|
@@ -2034,13 +2113,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
2034
2113
|
return;
|
|
2035
2114
|
}
|
|
2036
2115
|
const id = randomUUID();
|
|
2116
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
2037
2117
|
const timer = setTimeout(() => {
|
|
2038
2118
|
_pending.delete(id);
|
|
2039
2119
|
resolve({ error: "Request timeout" });
|
|
2040
2120
|
}, timeoutMs);
|
|
2041
2121
|
_pending.set(id, { resolve, timer });
|
|
2042
2122
|
try {
|
|
2043
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
2123
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
2044
2124
|
} catch {
|
|
2045
2125
|
clearTimeout(timer);
|
|
2046
2126
|
_pending.delete(id);
|
|
@@ -2069,9 +2149,9 @@ function killAndRespawnDaemon() {
|
|
|
2069
2149
|
}
|
|
2070
2150
|
try {
|
|
2071
2151
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2072
|
-
if (
|
|
2152
|
+
if (existsSync8(PID_PATH)) {
|
|
2073
2153
|
try {
|
|
2074
|
-
const pid = parseInt(
|
|
2154
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
2075
2155
|
if (pid > 0) {
|
|
2076
2156
|
try {
|
|
2077
2157
|
process.kill(pid, "SIGKILL");
|
|
@@ -2191,17 +2271,19 @@ function disconnectClient() {
|
|
|
2191
2271
|
function isClientConnected() {
|
|
2192
2272
|
return _connected;
|
|
2193
2273
|
}
|
|
2194
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
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;
|
|
2195
2275
|
var init_exe_daemon_client = __esm({
|
|
2196
2276
|
"src/lib/exe-daemon-client.ts"() {
|
|
2197
2277
|
"use strict";
|
|
2198
2278
|
init_config();
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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");
|
|
2202
2283
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2203
2284
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2204
2285
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
2286
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
2205
2287
|
_socket = null;
|
|
2206
2288
|
_connected = false;
|
|
2207
2289
|
_buffer = "";
|
|
@@ -2722,6 +2804,7 @@ async function ensureSchema() {
|
|
|
2722
2804
|
project TEXT NOT NULL,
|
|
2723
2805
|
summary TEXT NOT NULL,
|
|
2724
2806
|
task_file TEXT,
|
|
2807
|
+
session_scope TEXT,
|
|
2725
2808
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2726
2809
|
created_at TEXT NOT NULL
|
|
2727
2810
|
);
|
|
@@ -2730,7 +2813,7 @@ async function ensureSchema() {
|
|
|
2730
2813
|
ON notifications(read);
|
|
2731
2814
|
|
|
2732
2815
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2733
|
-
ON notifications(agent_id);
|
|
2816
|
+
ON notifications(agent_id, session_scope);
|
|
2734
2817
|
|
|
2735
2818
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2736
2819
|
ON notifications(task_file);
|
|
@@ -2768,6 +2851,7 @@ async function ensureSchema() {
|
|
|
2768
2851
|
target_agent TEXT NOT NULL,
|
|
2769
2852
|
target_project TEXT,
|
|
2770
2853
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2854
|
+
session_scope TEXT,
|
|
2771
2855
|
content TEXT NOT NULL,
|
|
2772
2856
|
priority TEXT DEFAULT 'normal',
|
|
2773
2857
|
status TEXT DEFAULT 'pending',
|
|
@@ -2781,10 +2865,31 @@ async function ensureSchema() {
|
|
|
2781
2865
|
);
|
|
2782
2866
|
|
|
2783
2867
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2784
|
-
ON messages(target_agent, status);
|
|
2868
|
+
ON messages(target_agent, session_scope, status);
|
|
2785
2869
|
|
|
2786
2870
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2787
|
-
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);
|
|
2788
2893
|
`);
|
|
2789
2894
|
try {
|
|
2790
2895
|
await client.execute({
|
|
@@ -3368,6 +3473,13 @@ async function ensureSchema() {
|
|
|
3368
3473
|
} catch {
|
|
3369
3474
|
}
|
|
3370
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
|
+
}
|
|
3371
3483
|
}
|
|
3372
3484
|
async function disposeDatabase() {
|
|
3373
3485
|
if (_walCheckpointTimer) {
|
|
@@ -3406,18 +3518,21 @@ var init_database = __esm({
|
|
|
3406
3518
|
});
|
|
3407
3519
|
|
|
3408
3520
|
// src/lib/license.ts
|
|
3409
|
-
import { readFileSync as
|
|
3521
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
3410
3522
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3411
|
-
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";
|
|
3412
3527
|
import { jwtVerify, importSPKI } from "jose";
|
|
3413
3528
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
3414
3529
|
var init_license = __esm({
|
|
3415
3530
|
"src/lib/license.ts"() {
|
|
3416
3531
|
"use strict";
|
|
3417
3532
|
init_config();
|
|
3418
|
-
LICENSE_PATH =
|
|
3419
|
-
CACHE_PATH =
|
|
3420
|
-
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");
|
|
3421
3536
|
PLAN_LIMITS = {
|
|
3422
3537
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3423
3538
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3429,12 +3544,12 @@ var init_license = __esm({
|
|
|
3429
3544
|
});
|
|
3430
3545
|
|
|
3431
3546
|
// src/lib/plan-limits.ts
|
|
3432
|
-
import { readFileSync as
|
|
3433
|
-
import
|
|
3547
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3548
|
+
import path10 from "path";
|
|
3434
3549
|
function getLicenseSync() {
|
|
3435
3550
|
try {
|
|
3436
|
-
if (!
|
|
3437
|
-
const raw = JSON.parse(
|
|
3551
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3552
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
3438
3553
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3439
3554
|
const parts = raw.token.split(".");
|
|
3440
3555
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3472,8 +3587,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3472
3587
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3473
3588
|
let count = 0;
|
|
3474
3589
|
try {
|
|
3475
|
-
if (
|
|
3476
|
-
const raw =
|
|
3590
|
+
if (existsSync10(filePath)) {
|
|
3591
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3477
3592
|
const employees = JSON.parse(raw);
|
|
3478
3593
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3479
3594
|
}
|
|
@@ -3502,29 +3617,63 @@ var init_plan_limits = __esm({
|
|
|
3502
3617
|
this.name = "PlanLimitError";
|
|
3503
3618
|
}
|
|
3504
3619
|
};
|
|
3505
|
-
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();
|
|
3506
3654
|
}
|
|
3507
3655
|
});
|
|
3508
3656
|
|
|
3509
3657
|
// src/lib/notifications.ts
|
|
3510
|
-
import
|
|
3511
|
-
import
|
|
3512
|
-
import
|
|
3658
|
+
import crypto2 from "crypto";
|
|
3659
|
+
import path11 from "path";
|
|
3660
|
+
import os8 from "os";
|
|
3513
3661
|
import {
|
|
3514
|
-
readFileSync as
|
|
3662
|
+
readFileSync as readFileSync10,
|
|
3515
3663
|
readdirSync,
|
|
3516
3664
|
unlinkSync as unlinkSync3,
|
|
3517
|
-
existsSync as
|
|
3665
|
+
existsSync as existsSync11,
|
|
3518
3666
|
rmdirSync
|
|
3519
3667
|
} from "fs";
|
|
3520
3668
|
async function writeNotification(notification) {
|
|
3521
3669
|
try {
|
|
3522
3670
|
const client = getClient();
|
|
3523
|
-
const id =
|
|
3671
|
+
const id = crypto2.randomUUID();
|
|
3524
3672
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3673
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3525
3674
|
await client.execute({
|
|
3526
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3527
|
-
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, ?)`,
|
|
3528
3677
|
args: [
|
|
3529
3678
|
id,
|
|
3530
3679
|
notification.agentId,
|
|
@@ -3533,6 +3682,7 @@ async function writeNotification(notification) {
|
|
|
3533
3682
|
notification.project,
|
|
3534
3683
|
notification.summary,
|
|
3535
3684
|
notification.taskFile ?? null,
|
|
3685
|
+
sessionScope,
|
|
3536
3686
|
now
|
|
3537
3687
|
]
|
|
3538
3688
|
});
|
|
@@ -3541,12 +3691,14 @@ async function writeNotification(notification) {
|
|
|
3541
3691
|
`);
|
|
3542
3692
|
}
|
|
3543
3693
|
}
|
|
3544
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3694
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3545
3695
|
try {
|
|
3546
3696
|
const client = getClient();
|
|
3697
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3547
3698
|
await client.execute({
|
|
3548
|
-
sql:
|
|
3549
|
-
|
|
3699
|
+
sql: `UPDATE notifications SET read = 1
|
|
3700
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3701
|
+
args: [taskFile, ...scope.args]
|
|
3550
3702
|
});
|
|
3551
3703
|
} catch {
|
|
3552
3704
|
}
|
|
@@ -3555,6 +3707,7 @@ var init_notifications = __esm({
|
|
|
3555
3707
|
"src/lib/notifications.ts"() {
|
|
3556
3708
|
"use strict";
|
|
3557
3709
|
init_database();
|
|
3710
|
+
init_task_scope();
|
|
3558
3711
|
}
|
|
3559
3712
|
});
|
|
3560
3713
|
|
|
@@ -3571,7 +3724,7 @@ __export(session_kill_telemetry_exports, {
|
|
|
3571
3724
|
recordSessionKill: () => recordSessionKill,
|
|
3572
3725
|
sumTokensSavedSince: () => sumTokensSavedSince
|
|
3573
3726
|
});
|
|
3574
|
-
import
|
|
3727
|
+
import crypto3 from "crypto";
|
|
3575
3728
|
async function recordSessionKill(input) {
|
|
3576
3729
|
try {
|
|
3577
3730
|
const client = getClient();
|
|
@@ -3581,7 +3734,7 @@ async function recordSessionKill(input) {
|
|
|
3581
3734
|
ticks_idle, estimated_tokens_saved)
|
|
3582
3735
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3583
3736
|
args: [
|
|
3584
|
-
|
|
3737
|
+
crypto3.randomUUID(),
|
|
3585
3738
|
input.sessionName,
|
|
3586
3739
|
input.agentId,
|
|
3587
3740
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3662,30 +3815,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
3662
3815
|
}
|
|
3663
3816
|
});
|
|
3664
3817
|
|
|
3665
|
-
// src/lib/task-scope.ts
|
|
3666
|
-
function getCurrentSessionScope() {
|
|
3667
|
-
try {
|
|
3668
|
-
return resolveExeSession();
|
|
3669
|
-
} catch {
|
|
3670
|
-
return null;
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3674
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3675
|
-
if (!scope) return { sql: "", args: [] };
|
|
3676
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3677
|
-
return {
|
|
3678
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3679
|
-
args: [scope]
|
|
3680
|
-
};
|
|
3681
|
-
}
|
|
3682
|
-
var init_task_scope = __esm({
|
|
3683
|
-
"src/lib/task-scope.ts"() {
|
|
3684
|
-
"use strict";
|
|
3685
|
-
init_tmux_routing();
|
|
3686
|
-
}
|
|
3687
|
-
});
|
|
3688
|
-
|
|
3689
3818
|
// src/lib/state-bus.ts
|
|
3690
3819
|
var StateBus, orgBus;
|
|
3691
3820
|
var init_state_bus = __esm({
|
|
@@ -3741,13 +3870,117 @@ var init_state_bus = __esm({
|
|
|
3741
3870
|
}
|
|
3742
3871
|
});
|
|
3743
3872
|
|
|
3744
|
-
// src/lib/
|
|
3745
|
-
import crypto3 from "crypto";
|
|
3746
|
-
import path11 from "path";
|
|
3747
|
-
import os8 from "os";
|
|
3873
|
+
// src/lib/project-name.ts
|
|
3748
3874
|
import { execSync as execSync5 } from "child_process";
|
|
3875
|
+
import path12 from "path";
|
|
3876
|
+
function getProjectName(cwd) {
|
|
3877
|
+
const dir = cwd ?? process.cwd();
|
|
3878
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3879
|
+
try {
|
|
3880
|
+
let repoRoot;
|
|
3881
|
+
try {
|
|
3882
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3883
|
+
cwd: dir,
|
|
3884
|
+
encoding: "utf8",
|
|
3885
|
+
timeout: 2e3,
|
|
3886
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3887
|
+
}).trim();
|
|
3888
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
3889
|
+
} catch {
|
|
3890
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3891
|
+
cwd: dir,
|
|
3892
|
+
encoding: "utf8",
|
|
3893
|
+
timeout: 2e3,
|
|
3894
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3895
|
+
}).trim();
|
|
3896
|
+
}
|
|
3897
|
+
_cached2 = path12.basename(repoRoot);
|
|
3898
|
+
_cachedCwd = dir;
|
|
3899
|
+
return _cached2;
|
|
3900
|
+
} catch {
|
|
3901
|
+
_cached2 = path12.basename(dir);
|
|
3902
|
+
_cachedCwd = dir;
|
|
3903
|
+
return _cached2;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
var _cached2, _cachedCwd;
|
|
3907
|
+
var init_project_name = __esm({
|
|
3908
|
+
"src/lib/project-name.ts"() {
|
|
3909
|
+
"use strict";
|
|
3910
|
+
_cached2 = null;
|
|
3911
|
+
_cachedCwd = null;
|
|
3912
|
+
}
|
|
3913
|
+
});
|
|
3914
|
+
|
|
3915
|
+
// src/lib/session-scope.ts
|
|
3916
|
+
var session_scope_exports = {};
|
|
3917
|
+
__export(session_scope_exports, {
|
|
3918
|
+
assertSessionScope: () => assertSessionScope,
|
|
3919
|
+
findSessionForProject: () => findSessionForProject,
|
|
3920
|
+
getSessionProject: () => getSessionProject
|
|
3921
|
+
});
|
|
3922
|
+
function getSessionProject(sessionName) {
|
|
3923
|
+
const sessions = listSessions();
|
|
3924
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
3925
|
+
if (!entry) return null;
|
|
3926
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
3927
|
+
return parts[parts.length - 1] ?? null;
|
|
3928
|
+
}
|
|
3929
|
+
function findSessionForProject(projectName) {
|
|
3930
|
+
const sessions = listSessions();
|
|
3931
|
+
for (const s of sessions) {
|
|
3932
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3933
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3934
|
+
}
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
function assertSessionScope(actionType, targetProject) {
|
|
3938
|
+
try {
|
|
3939
|
+
const currentProject = getProjectName();
|
|
3940
|
+
const exeSession = resolveExeSession();
|
|
3941
|
+
if (!exeSession) {
|
|
3942
|
+
return { allowed: true, reason: "no_session" };
|
|
3943
|
+
}
|
|
3944
|
+
if (currentProject === targetProject) {
|
|
3945
|
+
return {
|
|
3946
|
+
allowed: true,
|
|
3947
|
+
reason: "same_session",
|
|
3948
|
+
currentProject,
|
|
3949
|
+
targetProject
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
process.stderr.write(
|
|
3953
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
3954
|
+
`
|
|
3955
|
+
);
|
|
3956
|
+
return {
|
|
3957
|
+
allowed: false,
|
|
3958
|
+
reason: "cross_session_denied",
|
|
3959
|
+
currentProject,
|
|
3960
|
+
targetProject,
|
|
3961
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
3962
|
+
};
|
|
3963
|
+
} catch {
|
|
3964
|
+
return { allowed: true, reason: "no_session" };
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
var init_session_scope = __esm({
|
|
3968
|
+
"src/lib/session-scope.ts"() {
|
|
3969
|
+
"use strict";
|
|
3970
|
+
init_session_registry();
|
|
3971
|
+
init_project_name();
|
|
3972
|
+
init_tmux_routing();
|
|
3973
|
+
init_employees();
|
|
3974
|
+
}
|
|
3975
|
+
});
|
|
3976
|
+
|
|
3977
|
+
// src/lib/tasks-crud.ts
|
|
3978
|
+
import crypto4 from "crypto";
|
|
3979
|
+
import path13 from "path";
|
|
3980
|
+
import os9 from "os";
|
|
3981
|
+
import { execSync as execSync6 } from "child_process";
|
|
3749
3982
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3750
|
-
import { existsSync as
|
|
3983
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3751
3984
|
async function writeCheckpoint(input) {
|
|
3752
3985
|
const client = getClient();
|
|
3753
3986
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3863,13 +4096,28 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3863
4096
|
}
|
|
3864
4097
|
async function createTaskCore(input) {
|
|
3865
4098
|
const client = getClient();
|
|
3866
|
-
const id =
|
|
4099
|
+
const id = crypto4.randomUUID();
|
|
3867
4100
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3868
4101
|
const slug = slugify(input.title);
|
|
3869
4102
|
let earlySessionScope = null;
|
|
4103
|
+
let scopeMismatchWarning;
|
|
3870
4104
|
try {
|
|
3871
4105
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3872
|
-
|
|
4106
|
+
const resolved = resolveExeSession2();
|
|
4107
|
+
if (resolved && input.projectName) {
|
|
4108
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
4109
|
+
const sessionProject = getSessionProject2(resolved);
|
|
4110
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
4111
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
4112
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
4113
|
+
`);
|
|
4114
|
+
earlySessionScope = null;
|
|
4115
|
+
} else {
|
|
4116
|
+
earlySessionScope = resolved;
|
|
4117
|
+
}
|
|
4118
|
+
} else {
|
|
4119
|
+
earlySessionScope = resolved;
|
|
4120
|
+
}
|
|
3873
4121
|
} catch {
|
|
3874
4122
|
}
|
|
3875
4123
|
const scope = earlySessionScope ?? "default";
|
|
@@ -3920,10 +4168,14 @@ async function createTaskCore(input) {
|
|
|
3920
4168
|
${laneWarning}` : laneWarning;
|
|
3921
4169
|
}
|
|
3922
4170
|
}
|
|
4171
|
+
if (scopeMismatchWarning) {
|
|
4172
|
+
warning = warning ? `${warning}
|
|
4173
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
4174
|
+
}
|
|
3923
4175
|
if (input.baseDir) {
|
|
3924
4176
|
try {
|
|
3925
|
-
await mkdir3(
|
|
3926
|
-
await mkdir3(
|
|
4177
|
+
await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4178
|
+
await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3927
4179
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3928
4180
|
await ensureGitignoreExe(input.baseDir);
|
|
3929
4181
|
} catch {
|
|
@@ -3959,18 +4211,24 @@ ${laneWarning}` : laneWarning;
|
|
|
3959
4211
|
});
|
|
3960
4212
|
if (input.baseDir) {
|
|
3961
4213
|
try {
|
|
3962
|
-
const EXE_OS_DIR =
|
|
3963
|
-
const mdPath =
|
|
3964
|
-
const mdDir =
|
|
3965
|
-
if (!
|
|
4214
|
+
const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
|
|
4215
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
4216
|
+
const mdDir = path13.dirname(mdPath);
|
|
4217
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3966
4218
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3967
4219
|
const mdContent = `# ${input.title}
|
|
3968
4220
|
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
4221
|
+
## MANDATORY: When done
|
|
4222
|
+
|
|
4223
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4224
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4225
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4226
|
+
|
|
4227
|
+
**ID:** ${id}
|
|
4228
|
+
**Status:** ${initialStatus}
|
|
4229
|
+
**Priority:** ${input.priority}
|
|
4230
|
+
**Assigned by:** ${input.assignedBy}
|
|
4231
|
+
**Assigned to:** ${input.assignedTo}
|
|
3974
4232
|
**Project:** ${input.projectName}
|
|
3975
4233
|
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
3976
4234
|
**Parent task:** ${parentTaskId}` : ""}
|
|
@@ -3979,12 +4237,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3979
4237
|
## Context
|
|
3980
4238
|
|
|
3981
4239
|
${input.context}
|
|
3982
|
-
|
|
3983
|
-
## MANDATORY: When done
|
|
3984
|
-
|
|
3985
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
3986
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3987
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3988
4240
|
`;
|
|
3989
4241
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3990
4242
|
} catch (err) {
|
|
@@ -4066,14 +4318,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4066
4318
|
if (!identifier || identifier === "unknown") return true;
|
|
4067
4319
|
try {
|
|
4068
4320
|
if (identifier.startsWith("%")) {
|
|
4069
|
-
const output =
|
|
4321
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
4070
4322
|
timeout: 2e3,
|
|
4071
4323
|
encoding: "utf8",
|
|
4072
4324
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4073
4325
|
});
|
|
4074
4326
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
4075
4327
|
} else {
|
|
4076
|
-
|
|
4328
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
4077
4329
|
timeout: 2e3,
|
|
4078
4330
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4079
4331
|
});
|
|
@@ -4082,7 +4334,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
4082
4334
|
} catch {
|
|
4083
4335
|
if (identifier.startsWith("%")) return true;
|
|
4084
4336
|
try {
|
|
4085
|
-
|
|
4337
|
+
execSync6("tmux list-sessions", {
|
|
4086
4338
|
timeout: 2e3,
|
|
4087
4339
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4088
4340
|
});
|
|
@@ -4097,12 +4349,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
4097
4349
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4098
4350
|
try {
|
|
4099
4351
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4100
|
-
const branch =
|
|
4352
|
+
const branch = execSync6(
|
|
4101
4353
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4102
4354
|
{ encoding: "utf8", timeout: 3e3 }
|
|
4103
4355
|
).trim();
|
|
4104
4356
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4105
|
-
const commitCount =
|
|
4357
|
+
const commitCount = execSync6(
|
|
4106
4358
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4107
4359
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4108
4360
|
).trim();
|
|
@@ -4233,7 +4485,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4233
4485
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4234
4486
|
} catch {
|
|
4235
4487
|
}
|
|
4236
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4488
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4237
4489
|
try {
|
|
4238
4490
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4239
4491
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4262,9 +4514,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4262
4514
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4263
4515
|
}
|
|
4264
4516
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4265
|
-
const archPath =
|
|
4517
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4266
4518
|
try {
|
|
4267
|
-
if (
|
|
4519
|
+
if (existsSync12(archPath)) return;
|
|
4268
4520
|
const template = [
|
|
4269
4521
|
`# ${projectName} \u2014 System Architecture`,
|
|
4270
4522
|
"",
|
|
@@ -4297,10 +4549,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4297
4549
|
}
|
|
4298
4550
|
}
|
|
4299
4551
|
async function ensureGitignoreExe(baseDir) {
|
|
4300
|
-
const gitignorePath =
|
|
4552
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
4301
4553
|
try {
|
|
4302
|
-
if (
|
|
4303
|
-
const content =
|
|
4554
|
+
if (existsSync12(gitignorePath)) {
|
|
4555
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
4304
4556
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4305
4557
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4306
4558
|
} else {
|
|
@@ -4343,8 +4595,8 @@ __export(tasks_review_exports, {
|
|
|
4343
4595
|
isStale: () => isStale,
|
|
4344
4596
|
listPendingReviews: () => listPendingReviews
|
|
4345
4597
|
});
|
|
4346
|
-
import
|
|
4347
|
-
import { existsSync as
|
|
4598
|
+
import path14 from "path";
|
|
4599
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
4348
4600
|
function formatAge(isoTimestamp) {
|
|
4349
4601
|
if (!isoTimestamp) return "";
|
|
4350
4602
|
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
@@ -4362,54 +4614,38 @@ function isStale(isoTimestamp) {
|
|
|
4362
4614
|
}
|
|
4363
4615
|
async function countPendingReviews(sessionScope) {
|
|
4364
4616
|
const client = getClient();
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
args: [sessionScope]
|
|
4369
|
-
});
|
|
4370
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4371
|
-
}
|
|
4617
|
+
const scope = strictSessionScopeFilter(
|
|
4618
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4619
|
+
);
|
|
4372
4620
|
const result = await client.execute({
|
|
4373
|
-
sql:
|
|
4374
|
-
|
|
4621
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4622
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4623
|
+
args: [...scope.args]
|
|
4375
4624
|
});
|
|
4376
4625
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4377
4626
|
}
|
|
4378
4627
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4379
4628
|
const client = getClient();
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
4384
|
-
AND session_scope = ?`,
|
|
4385
|
-
args: [sinceIso, sessionScope]
|
|
4386
|
-
});
|
|
4387
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4388
|
-
}
|
|
4629
|
+
const scope = strictSessionScopeFilter(
|
|
4630
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4631
|
+
);
|
|
4389
4632
|
const result = await client.execute({
|
|
4390
4633
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4391
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4392
|
-
args: [sinceIso]
|
|
4634
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4635
|
+
args: [sinceIso, ...scope.args]
|
|
4393
4636
|
});
|
|
4394
4637
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4395
4638
|
}
|
|
4396
4639
|
async function listPendingReviews(limit, sessionScope) {
|
|
4397
4640
|
const client = getClient();
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
WHERE status = 'needs_review'
|
|
4402
|
-
AND session_scope = ?
|
|
4403
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
4404
|
-
args: [sessionScope, limit]
|
|
4405
|
-
});
|
|
4406
|
-
return result2.rows;
|
|
4407
|
-
}
|
|
4641
|
+
const scope = strictSessionScopeFilter(
|
|
4642
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4643
|
+
);
|
|
4408
4644
|
const result = await client.execute({
|
|
4409
4645
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4410
|
-
WHERE status = 'needs_review'
|
|
4646
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4411
4647
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4412
|
-
args: [limit]
|
|
4648
|
+
args: [...scope.args, limit]
|
|
4413
4649
|
});
|
|
4414
4650
|
return result.rows;
|
|
4415
4651
|
}
|
|
@@ -4421,7 +4657,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4421
4657
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4422
4658
|
AND assigned_by = 'system'
|
|
4423
4659
|
AND title LIKE 'Review:%'
|
|
4424
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4660
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4425
4661
|
args: [now]
|
|
4426
4662
|
});
|
|
4427
4663
|
const r1b = await client.execute({
|
|
@@ -4629,11 +4865,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4629
4865
|
);
|
|
4630
4866
|
}
|
|
4631
4867
|
try {
|
|
4632
|
-
const cacheDir =
|
|
4633
|
-
if (
|
|
4868
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4869
|
+
if (existsSync13(cacheDir)) {
|
|
4634
4870
|
for (const f of readdirSync2(cacheDir)) {
|
|
4635
4871
|
if (f.startsWith("review-notified-")) {
|
|
4636
|
-
unlinkSync4(
|
|
4872
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
4637
4873
|
}
|
|
4638
4874
|
}
|
|
4639
4875
|
}
|
|
@@ -4650,11 +4886,12 @@ var init_tasks_review = __esm({
|
|
|
4650
4886
|
init_tmux_routing();
|
|
4651
4887
|
init_session_key();
|
|
4652
4888
|
init_state_bus();
|
|
4889
|
+
init_task_scope();
|
|
4653
4890
|
}
|
|
4654
4891
|
});
|
|
4655
4892
|
|
|
4656
4893
|
// src/lib/tasks-chain.ts
|
|
4657
|
-
import
|
|
4894
|
+
import path15 from "path";
|
|
4658
4895
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
4659
4896
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4660
4897
|
const client = getClient();
|
|
@@ -4671,7 +4908,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4671
4908
|
});
|
|
4672
4909
|
for (const ur of unblockedRows.rows) {
|
|
4673
4910
|
try {
|
|
4674
|
-
const ubFile =
|
|
4911
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
4675
4912
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
4676
4913
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4677
4914
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4706,7 +4943,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4706
4943
|
const scScope = sessionScopeFilter();
|
|
4707
4944
|
const remaining = await client.execute({
|
|
4708
4945
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4709
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4946
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4710
4947
|
args: [parentTaskId, ...scScope.args]
|
|
4711
4948
|
});
|
|
4712
4949
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4738,110 +4975,6 @@ var init_tasks_chain = __esm({
|
|
|
4738
4975
|
}
|
|
4739
4976
|
});
|
|
4740
4977
|
|
|
4741
|
-
// src/lib/project-name.ts
|
|
4742
|
-
import { execSync as execSync6 } from "child_process";
|
|
4743
|
-
import path14 from "path";
|
|
4744
|
-
function getProjectName(cwd) {
|
|
4745
|
-
const dir = cwd ?? process.cwd();
|
|
4746
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4747
|
-
try {
|
|
4748
|
-
let repoRoot;
|
|
4749
|
-
try {
|
|
4750
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4751
|
-
cwd: dir,
|
|
4752
|
-
encoding: "utf8",
|
|
4753
|
-
timeout: 2e3,
|
|
4754
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4755
|
-
}).trim();
|
|
4756
|
-
repoRoot = path14.dirname(gitCommonDir);
|
|
4757
|
-
} catch {
|
|
4758
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4759
|
-
cwd: dir,
|
|
4760
|
-
encoding: "utf8",
|
|
4761
|
-
timeout: 2e3,
|
|
4762
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4763
|
-
}).trim();
|
|
4764
|
-
}
|
|
4765
|
-
_cached2 = path14.basename(repoRoot);
|
|
4766
|
-
_cachedCwd = dir;
|
|
4767
|
-
return _cached2;
|
|
4768
|
-
} catch {
|
|
4769
|
-
_cached2 = path14.basename(dir);
|
|
4770
|
-
_cachedCwd = dir;
|
|
4771
|
-
return _cached2;
|
|
4772
|
-
}
|
|
4773
|
-
}
|
|
4774
|
-
var _cached2, _cachedCwd;
|
|
4775
|
-
var init_project_name = __esm({
|
|
4776
|
-
"src/lib/project-name.ts"() {
|
|
4777
|
-
"use strict";
|
|
4778
|
-
_cached2 = null;
|
|
4779
|
-
_cachedCwd = null;
|
|
4780
|
-
}
|
|
4781
|
-
});
|
|
4782
|
-
|
|
4783
|
-
// src/lib/session-scope.ts
|
|
4784
|
-
var session_scope_exports = {};
|
|
4785
|
-
__export(session_scope_exports, {
|
|
4786
|
-
assertSessionScope: () => assertSessionScope,
|
|
4787
|
-
findSessionForProject: () => findSessionForProject,
|
|
4788
|
-
getSessionProject: () => getSessionProject
|
|
4789
|
-
});
|
|
4790
|
-
function getSessionProject(sessionName) {
|
|
4791
|
-
const sessions = listSessions();
|
|
4792
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4793
|
-
if (!entry) return null;
|
|
4794
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4795
|
-
return parts[parts.length - 1] ?? null;
|
|
4796
|
-
}
|
|
4797
|
-
function findSessionForProject(projectName) {
|
|
4798
|
-
const sessions = listSessions();
|
|
4799
|
-
for (const s of sessions) {
|
|
4800
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4801
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4802
|
-
}
|
|
4803
|
-
return null;
|
|
4804
|
-
}
|
|
4805
|
-
function assertSessionScope(actionType, targetProject) {
|
|
4806
|
-
try {
|
|
4807
|
-
const currentProject = getProjectName();
|
|
4808
|
-
const exeSession = resolveExeSession();
|
|
4809
|
-
if (!exeSession) {
|
|
4810
|
-
return { allowed: true, reason: "no_session" };
|
|
4811
|
-
}
|
|
4812
|
-
if (currentProject === targetProject) {
|
|
4813
|
-
return {
|
|
4814
|
-
allowed: true,
|
|
4815
|
-
reason: "same_session",
|
|
4816
|
-
currentProject,
|
|
4817
|
-
targetProject
|
|
4818
|
-
};
|
|
4819
|
-
}
|
|
4820
|
-
process.stderr.write(
|
|
4821
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4822
|
-
`
|
|
4823
|
-
);
|
|
4824
|
-
return {
|
|
4825
|
-
allowed: false,
|
|
4826
|
-
reason: "cross_session_denied",
|
|
4827
|
-
currentProject,
|
|
4828
|
-
targetProject,
|
|
4829
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4830
|
-
};
|
|
4831
|
-
} catch {
|
|
4832
|
-
return { allowed: true, reason: "no_session" };
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
var init_session_scope = __esm({
|
|
4836
|
-
"src/lib/session-scope.ts"() {
|
|
4837
|
-
"use strict";
|
|
4838
|
-
init_session_registry();
|
|
4839
|
-
init_project_name();
|
|
4840
|
-
init_tmux_routing();
|
|
4841
|
-
init_employees();
|
|
4842
|
-
}
|
|
4843
|
-
});
|
|
4844
|
-
|
|
4845
4978
|
// src/lib/tasks-notify.ts
|
|
4846
4979
|
async function dispatchTaskToEmployee(input) {
|
|
4847
4980
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
@@ -4909,10 +5042,10 @@ var init_tasks_notify = __esm({
|
|
|
4909
5042
|
});
|
|
4910
5043
|
|
|
4911
5044
|
// src/lib/behaviors.ts
|
|
4912
|
-
import
|
|
5045
|
+
import crypto5 from "crypto";
|
|
4913
5046
|
async function storeBehavior(opts) {
|
|
4914
5047
|
const client = getClient();
|
|
4915
|
-
const id =
|
|
5048
|
+
const id = crypto5.randomUUID();
|
|
4916
5049
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4917
5050
|
await client.execute({
|
|
4918
5051
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4941,7 +5074,7 @@ __export(skill_learning_exports, {
|
|
|
4941
5074
|
storeTrajectory: () => storeTrajectory,
|
|
4942
5075
|
sweepTrajectories: () => sweepTrajectories
|
|
4943
5076
|
});
|
|
4944
|
-
import
|
|
5077
|
+
import crypto6 from "crypto";
|
|
4945
5078
|
async function extractTrajectory(taskId, agentId) {
|
|
4946
5079
|
const client = getClient();
|
|
4947
5080
|
const result = await client.execute({
|
|
@@ -4970,11 +5103,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4970
5103
|
return signature;
|
|
4971
5104
|
}
|
|
4972
5105
|
function hashSignature(signature) {
|
|
4973
|
-
return
|
|
5106
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4974
5107
|
}
|
|
4975
5108
|
async function storeTrajectory(opts) {
|
|
4976
5109
|
const client = getClient();
|
|
4977
|
-
const id =
|
|
5110
|
+
const id = crypto6.randomUUID();
|
|
4978
5111
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4979
5112
|
const signatureHash = hashSignature(opts.signature);
|
|
4980
5113
|
await client.execute({
|
|
@@ -5239,8 +5372,8 @@ __export(tasks_exports, {
|
|
|
5239
5372
|
updateTaskStatus: () => updateTaskStatus,
|
|
5240
5373
|
writeCheckpoint: () => writeCheckpoint
|
|
5241
5374
|
});
|
|
5242
|
-
import
|
|
5243
|
-
import { writeFileSync as
|
|
5375
|
+
import path16 from "path";
|
|
5376
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5244
5377
|
async function createTask(input) {
|
|
5245
5378
|
const result = await createTaskCore(input);
|
|
5246
5379
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5259,12 +5392,12 @@ async function updateTask(input) {
|
|
|
5259
5392
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5260
5393
|
try {
|
|
5261
5394
|
const agent = String(row.assigned_to);
|
|
5262
|
-
const cacheDir =
|
|
5263
|
-
const cachePath =
|
|
5395
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5396
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
5264
5397
|
if (input.status === "in_progress") {
|
|
5265
5398
|
mkdirSync5(cacheDir, { recursive: true });
|
|
5266
|
-
|
|
5267
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5399
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5400
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5268
5401
|
try {
|
|
5269
5402
|
unlinkSync5(cachePath);
|
|
5270
5403
|
} catch {
|
|
@@ -5272,10 +5405,10 @@ async function updateTask(input) {
|
|
|
5272
5405
|
}
|
|
5273
5406
|
} catch {
|
|
5274
5407
|
}
|
|
5275
|
-
if (input.status === "done") {
|
|
5408
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5276
5409
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5277
5410
|
}
|
|
5278
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5411
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5279
5412
|
try {
|
|
5280
5413
|
const client = getClient();
|
|
5281
5414
|
const taskTitle = String(row.title);
|
|
@@ -5291,7 +5424,7 @@ async function updateTask(input) {
|
|
|
5291
5424
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5292
5425
|
try {
|
|
5293
5426
|
const draftClient = getClient();
|
|
5294
|
-
if (input.status === "done") {
|
|
5427
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5295
5428
|
await draftClient.execute({
|
|
5296
5429
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5297
5430
|
args: [assignedAgent]
|
|
@@ -5308,7 +5441,7 @@ async function updateTask(input) {
|
|
|
5308
5441
|
try {
|
|
5309
5442
|
const client = getClient();
|
|
5310
5443
|
const cascaded = await client.execute({
|
|
5311
|
-
sql: `UPDATE tasks SET status = '
|
|
5444
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5312
5445
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5313
5446
|
args: [now, taskId]
|
|
5314
5447
|
});
|
|
@@ -5321,14 +5454,14 @@ async function updateTask(input) {
|
|
|
5321
5454
|
} catch {
|
|
5322
5455
|
}
|
|
5323
5456
|
}
|
|
5324
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5457
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5325
5458
|
if (isTerminal) {
|
|
5326
5459
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5327
5460
|
if (!isCoordinator) {
|
|
5328
5461
|
notifyTaskDone();
|
|
5329
5462
|
}
|
|
5330
5463
|
await markTaskNotificationsRead(taskFile);
|
|
5331
|
-
if (input.status === "done") {
|
|
5464
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5332
5465
|
try {
|
|
5333
5466
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5334
5467
|
} catch {
|
|
@@ -5348,7 +5481,7 @@ async function updateTask(input) {
|
|
|
5348
5481
|
}
|
|
5349
5482
|
}
|
|
5350
5483
|
}
|
|
5351
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5484
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5352
5485
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5353
5486
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5354
5487
|
taskId,
|
|
@@ -5720,6 +5853,7 @@ __export(tmux_routing_exports, {
|
|
|
5720
5853
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5721
5854
|
isExeSession: () => isExeSession,
|
|
5722
5855
|
isSessionBusy: () => isSessionBusy,
|
|
5856
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5723
5857
|
notifyParentExe: () => notifyParentExe,
|
|
5724
5858
|
parseParentExe: () => parseParentExe,
|
|
5725
5859
|
registerParentExe: () => registerParentExe,
|
|
@@ -5730,13 +5864,13 @@ __export(tmux_routing_exports, {
|
|
|
5730
5864
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5731
5865
|
});
|
|
5732
5866
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5733
|
-
import { readFileSync as
|
|
5734
|
-
import
|
|
5735
|
-
import
|
|
5867
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5868
|
+
import path17 from "path";
|
|
5869
|
+
import os10 from "os";
|
|
5736
5870
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5737
5871
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5738
5872
|
function spawnLockPath(sessionName) {
|
|
5739
|
-
return
|
|
5873
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5740
5874
|
}
|
|
5741
5875
|
function isProcessAlive(pid) {
|
|
5742
5876
|
try {
|
|
@@ -5747,13 +5881,13 @@ function isProcessAlive(pid) {
|
|
|
5747
5881
|
}
|
|
5748
5882
|
}
|
|
5749
5883
|
function acquireSpawnLock2(sessionName) {
|
|
5750
|
-
if (!
|
|
5884
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5751
5885
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5752
5886
|
}
|
|
5753
5887
|
const lockFile = spawnLockPath(sessionName);
|
|
5754
|
-
if (
|
|
5888
|
+
if (existsSync14(lockFile)) {
|
|
5755
5889
|
try {
|
|
5756
|
-
const lock = JSON.parse(
|
|
5890
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5757
5891
|
const age = Date.now() - lock.timestamp;
|
|
5758
5892
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5759
5893
|
return false;
|
|
@@ -5761,7 +5895,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5761
5895
|
} catch {
|
|
5762
5896
|
}
|
|
5763
5897
|
}
|
|
5764
|
-
|
|
5898
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5765
5899
|
return true;
|
|
5766
5900
|
}
|
|
5767
5901
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5773,13 +5907,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5773
5907
|
function resolveBehaviorsExporterScript() {
|
|
5774
5908
|
try {
|
|
5775
5909
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5776
|
-
const scriptPath =
|
|
5777
|
-
|
|
5910
|
+
const scriptPath = path17.join(
|
|
5911
|
+
path17.dirname(thisFile),
|
|
5778
5912
|
"..",
|
|
5779
5913
|
"bin",
|
|
5780
5914
|
"exe-export-behaviors.js"
|
|
5781
5915
|
);
|
|
5782
|
-
return
|
|
5916
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5783
5917
|
} catch {
|
|
5784
5918
|
return null;
|
|
5785
5919
|
}
|
|
@@ -5845,12 +5979,12 @@ function extractRootExe(name) {
|
|
|
5845
5979
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5846
5980
|
}
|
|
5847
5981
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5848
|
-
if (!
|
|
5982
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5849
5983
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5850
5984
|
}
|
|
5851
5985
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5852
|
-
const filePath =
|
|
5853
|
-
|
|
5986
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5987
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5854
5988
|
parentExe: rootExe,
|
|
5855
5989
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5856
5990
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5858,7 +5992,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5858
5992
|
}
|
|
5859
5993
|
function getParentExe(sessionKey) {
|
|
5860
5994
|
try {
|
|
5861
|
-
const data = JSON.parse(
|
|
5995
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5862
5996
|
return data.parentExe || null;
|
|
5863
5997
|
} catch {
|
|
5864
5998
|
return null;
|
|
@@ -5866,8 +6000,8 @@ function getParentExe(sessionKey) {
|
|
|
5866
6000
|
}
|
|
5867
6001
|
function getDispatchedBy(sessionKey) {
|
|
5868
6002
|
try {
|
|
5869
|
-
const data = JSON.parse(
|
|
5870
|
-
|
|
6003
|
+
const data = JSON.parse(readFileSync12(
|
|
6004
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5871
6005
|
"utf8"
|
|
5872
6006
|
));
|
|
5873
6007
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5937,8 +6071,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5937
6071
|
}
|
|
5938
6072
|
function readDebounceState() {
|
|
5939
6073
|
try {
|
|
5940
|
-
if (!
|
|
5941
|
-
const raw = JSON.parse(
|
|
6074
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
6075
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5942
6076
|
const state = {};
|
|
5943
6077
|
for (const [key, val] of Object.entries(raw)) {
|
|
5944
6078
|
if (typeof val === "number") {
|
|
@@ -5954,8 +6088,8 @@ function readDebounceState() {
|
|
|
5954
6088
|
}
|
|
5955
6089
|
function writeDebounceState(state) {
|
|
5956
6090
|
try {
|
|
5957
|
-
if (!
|
|
5958
|
-
|
|
6091
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
6092
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5959
6093
|
} catch {
|
|
5960
6094
|
}
|
|
5961
6095
|
}
|
|
@@ -6053,8 +6187,8 @@ function sendIntercom(targetSession) {
|
|
|
6053
6187
|
try {
|
|
6054
6188
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6055
6189
|
const agent = baseAgentName(rawAgent);
|
|
6056
|
-
const markerPath =
|
|
6057
|
-
if (
|
|
6190
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6191
|
+
if (existsSync14(markerPath)) {
|
|
6058
6192
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
6059
6193
|
return "debounced";
|
|
6060
6194
|
}
|
|
@@ -6063,8 +6197,8 @@ function sendIntercom(targetSession) {
|
|
|
6063
6197
|
try {
|
|
6064
6198
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6065
6199
|
const agent = baseAgentName(rawAgent);
|
|
6066
|
-
const taskDir =
|
|
6067
|
-
if (
|
|
6200
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
6201
|
+
if (existsSync14(taskDir)) {
|
|
6068
6202
|
const files = readdirSync3(taskDir).filter(
|
|
6069
6203
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
6070
6204
|
);
|
|
@@ -6124,6 +6258,21 @@ function notifyParentExe(sessionKey) {
|
|
|
6124
6258
|
}
|
|
6125
6259
|
return true;
|
|
6126
6260
|
}
|
|
6261
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6262
|
+
const transport = getTransport();
|
|
6263
|
+
try {
|
|
6264
|
+
const sessions = transport.listSessions();
|
|
6265
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6266
|
+
execSync7(
|
|
6267
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6268
|
+
{ timeout: 3e3 }
|
|
6269
|
+
);
|
|
6270
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6271
|
+
return true;
|
|
6272
|
+
} catch {
|
|
6273
|
+
return false;
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6127
6276
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6128
6277
|
if (isCoordinatorName(employeeName)) {
|
|
6129
6278
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6197,26 +6346,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6197
6346
|
const transport = getTransport();
|
|
6198
6347
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6199
6348
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6200
|
-
const logDir =
|
|
6201
|
-
const logFile =
|
|
6202
|
-
if (!
|
|
6349
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
6350
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6351
|
+
if (!existsSync14(logDir)) {
|
|
6203
6352
|
mkdirSync6(logDir, { recursive: true });
|
|
6204
6353
|
}
|
|
6205
6354
|
transport.kill(sessionName);
|
|
6206
6355
|
let cleanupSuffix = "";
|
|
6207
6356
|
try {
|
|
6208
6357
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6209
|
-
const cleanupScript =
|
|
6210
|
-
if (
|
|
6358
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6359
|
+
if (existsSync14(cleanupScript)) {
|
|
6211
6360
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6212
6361
|
}
|
|
6213
6362
|
} catch {
|
|
6214
6363
|
}
|
|
6215
6364
|
try {
|
|
6216
|
-
const claudeJsonPath =
|
|
6365
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
6217
6366
|
let claudeJson = {};
|
|
6218
6367
|
try {
|
|
6219
|
-
claudeJson = JSON.parse(
|
|
6368
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6220
6369
|
} catch {
|
|
6221
6370
|
}
|
|
6222
6371
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6224,17 +6373,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6224
6373
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6225
6374
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6226
6375
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6227
|
-
|
|
6376
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6228
6377
|
} catch {
|
|
6229
6378
|
}
|
|
6230
6379
|
try {
|
|
6231
|
-
const settingsDir =
|
|
6380
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
6232
6381
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6233
|
-
const projSettingsDir =
|
|
6234
|
-
const settingsPath =
|
|
6382
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
6383
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
6235
6384
|
let settings = {};
|
|
6236
6385
|
try {
|
|
6237
|
-
settings = JSON.parse(
|
|
6386
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6238
6387
|
} catch {
|
|
6239
6388
|
}
|
|
6240
6389
|
const perms = settings.permissions ?? {};
|
|
@@ -6263,7 +6412,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6263
6412
|
perms.allow = allow;
|
|
6264
6413
|
settings.permissions = perms;
|
|
6265
6414
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
6266
|
-
|
|
6415
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6267
6416
|
}
|
|
6268
6417
|
} catch {
|
|
6269
6418
|
}
|
|
@@ -6278,8 +6427,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6278
6427
|
let behaviorsFlag = "";
|
|
6279
6428
|
let legacyFallbackWarned = false;
|
|
6280
6429
|
if (!useExeAgent && !useBinSymlink) {
|
|
6281
|
-
const identityPath =
|
|
6282
|
-
|
|
6430
|
+
const identityPath = path17.join(
|
|
6431
|
+
os10.homedir(),
|
|
6283
6432
|
".exe-os",
|
|
6284
6433
|
"identity",
|
|
6285
6434
|
`${employeeName}.md`
|
|
@@ -6288,13 +6437,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6288
6437
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6289
6438
|
if (hasAgentFlag) {
|
|
6290
6439
|
identityFlag = ` --agent ${employeeName}`;
|
|
6291
|
-
} else if (
|
|
6440
|
+
} else if (existsSync14(identityPath)) {
|
|
6292
6441
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6293
6442
|
legacyFallbackWarned = true;
|
|
6294
6443
|
}
|
|
6295
6444
|
const behaviorsFile = exportBehaviorsSync(
|
|
6296
6445
|
employeeName,
|
|
6297
|
-
|
|
6446
|
+
path17.basename(spawnCwd),
|
|
6298
6447
|
sessionName
|
|
6299
6448
|
);
|
|
6300
6449
|
if (behaviorsFile) {
|
|
@@ -6309,16 +6458,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6309
6458
|
}
|
|
6310
6459
|
let sessionContextFlag = "";
|
|
6311
6460
|
try {
|
|
6312
|
-
const ctxDir =
|
|
6461
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
6313
6462
|
mkdirSync6(ctxDir, { recursive: true });
|
|
6314
|
-
const ctxFile =
|
|
6463
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6315
6464
|
const ctxContent = [
|
|
6316
6465
|
`## Session Context`,
|
|
6317
6466
|
`You are running in tmux session: ${sessionName}.`,
|
|
6318
6467
|
`Your parent coordinator session is ${exeSession}.`,
|
|
6319
6468
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
6320
6469
|
].join("\n");
|
|
6321
|
-
|
|
6470
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6322
6471
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6323
6472
|
} catch {
|
|
6324
6473
|
}
|
|
@@ -6395,8 +6544,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6395
6544
|
transport.pipeLog(sessionName, logFile);
|
|
6396
6545
|
try {
|
|
6397
6546
|
const mySession = getMySession();
|
|
6398
|
-
const dispatchInfo =
|
|
6399
|
-
|
|
6547
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6548
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
6400
6549
|
dispatchedBy: mySession,
|
|
6401
6550
|
rootExe: exeSession,
|
|
6402
6551
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -6470,15 +6619,15 @@ var init_tmux_routing = __esm({
|
|
|
6470
6619
|
init_intercom_queue();
|
|
6471
6620
|
init_plan_limits();
|
|
6472
6621
|
init_employees();
|
|
6473
|
-
SPAWN_LOCK_DIR =
|
|
6474
|
-
SESSION_CACHE =
|
|
6622
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6623
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
6475
6624
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6476
6625
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
6477
6626
|
VERIFY_PANE_LINES = 200;
|
|
6478
6627
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
6479
6628
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
6480
|
-
INTERCOM_LOG2 =
|
|
6481
|
-
DEBOUNCE_FILE =
|
|
6629
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6630
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6482
6631
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6483
6632
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
6484
6633
|
}
|
|
@@ -6762,9 +6911,9 @@ __export(agent_signals_exports, {
|
|
|
6762
6911
|
hasOpenTasks: () => hasOpenTasks,
|
|
6763
6912
|
hasUnreadInbox: () => hasUnreadInbox
|
|
6764
6913
|
});
|
|
6765
|
-
import { readFileSync as
|
|
6766
|
-
import
|
|
6767
|
-
import
|
|
6914
|
+
import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
|
|
6915
|
+
import os11 from "os";
|
|
6916
|
+
import path18 from "path";
|
|
6768
6917
|
async function hasOpenTasks(client, agentId) {
|
|
6769
6918
|
try {
|
|
6770
6919
|
const scope = sessionScopeFilter(null);
|
|
@@ -6806,10 +6955,10 @@ async function hasUnreadInbox(client, agentId) {
|
|
|
6806
6955
|
return CONSERVATIVE_ON_ERROR;
|
|
6807
6956
|
}
|
|
6808
6957
|
}
|
|
6809
|
-
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog =
|
|
6810
|
-
if (!
|
|
6958
|
+
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path18.join(os11.homedir(), ".exe-os", "intercom.log")) {
|
|
6959
|
+
if (!existsSync15(intercomLog)) return false;
|
|
6811
6960
|
try {
|
|
6812
|
-
const raw =
|
|
6961
|
+
const raw = readFileSync13(intercomLog, "utf8");
|
|
6813
6962
|
const lines = raw.split("\n");
|
|
6814
6963
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6815
6964
|
const line = lines[i];
|
|
@@ -6882,7 +7031,7 @@ __export(daemon_orchestration_exports, {
|
|
|
6882
7031
|
shouldNudgeEmployee: () => shouldNudgeEmployee
|
|
6883
7032
|
});
|
|
6884
7033
|
import { execSync as execSync9 } from "child_process";
|
|
6885
|
-
import { existsSync as
|
|
7034
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
6886
7035
|
import { homedir } from "os";
|
|
6887
7036
|
import { join } from "path";
|
|
6888
7037
|
function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
|
|
@@ -7073,8 +7222,8 @@ async function pollReviewNudge(deps, state) {
|
|
|
7073
7222
|
function loadNudgeState() {
|
|
7074
7223
|
const state = { lastNudge: /* @__PURE__ */ new Map() };
|
|
7075
7224
|
try {
|
|
7076
|
-
if (!
|
|
7077
|
-
const raw = JSON.parse(
|
|
7225
|
+
if (!existsSync16(NUDGE_STATE_PATH)) return state;
|
|
7226
|
+
const raw = JSON.parse(readFileSync14(NUDGE_STATE_PATH, "utf8"));
|
|
7078
7227
|
if (Array.isArray(raw)) {
|
|
7079
7228
|
for (const [key, val] of raw) {
|
|
7080
7229
|
if (key && typeof val?.at === "number" && typeof val?.count === "number") {
|
|
@@ -7088,7 +7237,7 @@ function loadNudgeState() {
|
|
|
7088
7237
|
}
|
|
7089
7238
|
function saveNudgeState(state) {
|
|
7090
7239
|
const entries = Array.from(state.lastNudge.entries());
|
|
7091
|
-
|
|
7240
|
+
writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
|
|
7092
7241
|
}
|
|
7093
7242
|
function createReviewNudgeRealDeps(getClient2) {
|
|
7094
7243
|
return {
|
|
@@ -7426,134 +7575,12 @@ var init_daemon_orchestration = __esm({
|
|
|
7426
7575
|
}
|
|
7427
7576
|
});
|
|
7428
7577
|
|
|
7429
|
-
// src/lib/keychain.ts
|
|
7430
|
-
var keychain_exports = {};
|
|
7431
|
-
__export(keychain_exports, {
|
|
7432
|
-
deleteMasterKey: () => deleteMasterKey,
|
|
7433
|
-
exportMnemonic: () => exportMnemonic,
|
|
7434
|
-
getMasterKey: () => getMasterKey,
|
|
7435
|
-
importMnemonic: () => importMnemonic,
|
|
7436
|
-
setMasterKey: () => setMasterKey
|
|
7437
|
-
});
|
|
7438
|
-
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7439
|
-
import { existsSync as existsSync15 } from "fs";
|
|
7440
|
-
import path18 from "path";
|
|
7441
|
-
import os11 from "os";
|
|
7442
|
-
function getKeyDir() {
|
|
7443
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
7444
|
-
}
|
|
7445
|
-
function getKeyPath() {
|
|
7446
|
-
return path18.join(getKeyDir(), "master.key");
|
|
7447
|
-
}
|
|
7448
|
-
async function tryKeytar() {
|
|
7449
|
-
try {
|
|
7450
|
-
return await import("keytar");
|
|
7451
|
-
} catch {
|
|
7452
|
-
return null;
|
|
7453
|
-
}
|
|
7454
|
-
}
|
|
7455
|
-
async function getMasterKey() {
|
|
7456
|
-
const keytar = await tryKeytar();
|
|
7457
|
-
if (keytar) {
|
|
7458
|
-
try {
|
|
7459
|
-
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
7460
|
-
if (stored) {
|
|
7461
|
-
return Buffer.from(stored, "base64");
|
|
7462
|
-
}
|
|
7463
|
-
} catch {
|
|
7464
|
-
}
|
|
7465
|
-
}
|
|
7466
|
-
const keyPath = getKeyPath();
|
|
7467
|
-
if (!existsSync15(keyPath)) {
|
|
7468
|
-
process.stderr.write(
|
|
7469
|
-
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7470
|
-
`
|
|
7471
|
-
);
|
|
7472
|
-
return null;
|
|
7473
|
-
}
|
|
7474
|
-
try {
|
|
7475
|
-
const content = await readFile4(keyPath, "utf-8");
|
|
7476
|
-
return Buffer.from(content.trim(), "base64");
|
|
7477
|
-
} catch (err) {
|
|
7478
|
-
process.stderr.write(
|
|
7479
|
-
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
7480
|
-
`
|
|
7481
|
-
);
|
|
7482
|
-
return null;
|
|
7483
|
-
}
|
|
7484
|
-
}
|
|
7485
|
-
async function setMasterKey(key) {
|
|
7486
|
-
const b64 = key.toString("base64");
|
|
7487
|
-
const keytar = await tryKeytar();
|
|
7488
|
-
if (keytar) {
|
|
7489
|
-
try {
|
|
7490
|
-
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
7491
|
-
return;
|
|
7492
|
-
} catch {
|
|
7493
|
-
}
|
|
7494
|
-
}
|
|
7495
|
-
const dir = getKeyDir();
|
|
7496
|
-
await mkdir4(dir, { recursive: true });
|
|
7497
|
-
const keyPath = getKeyPath();
|
|
7498
|
-
await writeFile5(keyPath, b64 + "\n", "utf-8");
|
|
7499
|
-
await chmod2(keyPath, 384);
|
|
7500
|
-
}
|
|
7501
|
-
async function deleteMasterKey() {
|
|
7502
|
-
const keytar = await tryKeytar();
|
|
7503
|
-
if (keytar) {
|
|
7504
|
-
try {
|
|
7505
|
-
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
7506
|
-
} catch {
|
|
7507
|
-
}
|
|
7508
|
-
}
|
|
7509
|
-
const keyPath = getKeyPath();
|
|
7510
|
-
if (existsSync15(keyPath)) {
|
|
7511
|
-
await unlink(keyPath);
|
|
7512
|
-
}
|
|
7513
|
-
}
|
|
7514
|
-
async function loadBip39() {
|
|
7515
|
-
try {
|
|
7516
|
-
return await import("bip39");
|
|
7517
|
-
} catch {
|
|
7518
|
-
throw new Error(
|
|
7519
|
-
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
7520
|
-
);
|
|
7521
|
-
}
|
|
7522
|
-
}
|
|
7523
|
-
async function exportMnemonic(key) {
|
|
7524
|
-
if (key.length !== 32) {
|
|
7525
|
-
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
7526
|
-
}
|
|
7527
|
-
const { entropyToMnemonic } = await loadBip39();
|
|
7528
|
-
return entropyToMnemonic(key.toString("hex"));
|
|
7529
|
-
}
|
|
7530
|
-
async function importMnemonic(mnemonic) {
|
|
7531
|
-
const trimmed = mnemonic.trim();
|
|
7532
|
-
const words = trimmed.split(/\s+/);
|
|
7533
|
-
if (words.length !== 24) {
|
|
7534
|
-
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
7535
|
-
}
|
|
7536
|
-
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
7537
|
-
if (!validateMnemonic(trimmed)) {
|
|
7538
|
-
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
7539
|
-
}
|
|
7540
|
-
const entropy = mnemonicToEntropy(trimmed);
|
|
7541
|
-
return Buffer.from(entropy, "hex");
|
|
7542
|
-
}
|
|
7543
|
-
var SERVICE, ACCOUNT;
|
|
7544
|
-
var init_keychain = __esm({
|
|
7545
|
-
"src/lib/keychain.ts"() {
|
|
7546
|
-
"use strict";
|
|
7547
|
-
SERVICE = "exe-mem";
|
|
7548
|
-
ACCOUNT = "master-key";
|
|
7549
|
-
}
|
|
7550
|
-
});
|
|
7551
|
-
|
|
7552
7578
|
// src/lib/shard-manager.ts
|
|
7553
7579
|
var shard_manager_exports = {};
|
|
7554
7580
|
__export(shard_manager_exports, {
|
|
7555
7581
|
disposeShards: () => disposeShards,
|
|
7556
7582
|
ensureShardSchema: () => ensureShardSchema,
|
|
7583
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
7557
7584
|
getReadyShardClient: () => getReadyShardClient,
|
|
7558
7585
|
getShardClient: () => getShardClient,
|
|
7559
7586
|
getShardsDir: () => getShardsDir,
|
|
@@ -7563,14 +7590,17 @@ __export(shard_manager_exports, {
|
|
|
7563
7590
|
shardExists: () => shardExists
|
|
7564
7591
|
});
|
|
7565
7592
|
import path19 from "path";
|
|
7566
|
-
import { existsSync as
|
|
7593
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
7567
7594
|
import { createClient as createClient2 } from "@libsql/client";
|
|
7568
7595
|
function initShardManager(encryptionKey) {
|
|
7569
7596
|
_encryptionKey = encryptionKey;
|
|
7570
|
-
if (!
|
|
7597
|
+
if (!existsSync17(SHARDS_DIR)) {
|
|
7571
7598
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
7572
7599
|
}
|
|
7573
7600
|
_shardingEnabled = true;
|
|
7601
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
7602
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
7603
|
+
_evictionTimer.unref();
|
|
7574
7604
|
}
|
|
7575
7605
|
function isShardingEnabled() {
|
|
7576
7606
|
return _shardingEnabled;
|
|
@@ -7587,21 +7617,28 @@ function getShardClient(projectName) {
|
|
|
7587
7617
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
7588
7618
|
}
|
|
7589
7619
|
const cached = _shards.get(safeName);
|
|
7590
|
-
if (cached)
|
|
7620
|
+
if (cached) {
|
|
7621
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
7622
|
+
return cached;
|
|
7623
|
+
}
|
|
7624
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
7625
|
+
evictLRU();
|
|
7626
|
+
}
|
|
7591
7627
|
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
7592
7628
|
const client = createClient2({
|
|
7593
7629
|
url: `file:${dbPath}`,
|
|
7594
7630
|
encryptionKey: _encryptionKey
|
|
7595
7631
|
});
|
|
7596
7632
|
_shards.set(safeName, client);
|
|
7633
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
7597
7634
|
return client;
|
|
7598
7635
|
}
|
|
7599
7636
|
function shardExists(projectName) {
|
|
7600
7637
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7601
|
-
return
|
|
7638
|
+
return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
7602
7639
|
}
|
|
7603
7640
|
function listShards() {
|
|
7604
|
-
if (!
|
|
7641
|
+
if (!existsSync17(SHARDS_DIR)) return [];
|
|
7605
7642
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
7606
7643
|
}
|
|
7607
7644
|
async function ensureShardSchema(client) {
|
|
@@ -7653,6 +7690,8 @@ async function ensureShardSchema(client) {
|
|
|
7653
7690
|
for (const col of [
|
|
7654
7691
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
7655
7692
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
7693
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
7694
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
7656
7695
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
7657
7696
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
7658
7697
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -7785,31 +7824,202 @@ async function ensureShardSchema(client) {
|
|
|
7785
7824
|
}
|
|
7786
7825
|
}
|
|
7787
7826
|
}
|
|
7788
|
-
async function getReadyShardClient(projectName) {
|
|
7789
|
-
const client = getShardClient(projectName);
|
|
7790
|
-
await ensureShardSchema(client);
|
|
7791
|
-
return client;
|
|
7827
|
+
async function getReadyShardClient(projectName) {
|
|
7828
|
+
const client = getShardClient(projectName);
|
|
7829
|
+
await ensureShardSchema(client);
|
|
7830
|
+
return client;
|
|
7831
|
+
}
|
|
7832
|
+
function evictLRU() {
|
|
7833
|
+
let oldest = null;
|
|
7834
|
+
let oldestTime = Infinity;
|
|
7835
|
+
for (const [name, time] of _shardLastAccess) {
|
|
7836
|
+
if (time < oldestTime) {
|
|
7837
|
+
oldestTime = time;
|
|
7838
|
+
oldest = name;
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
if (oldest) {
|
|
7842
|
+
const client = _shards.get(oldest);
|
|
7843
|
+
if (client) {
|
|
7844
|
+
client.close();
|
|
7845
|
+
}
|
|
7846
|
+
_shards.delete(oldest);
|
|
7847
|
+
_shardLastAccess.delete(oldest);
|
|
7848
|
+
}
|
|
7849
|
+
}
|
|
7850
|
+
function evictIdleShards() {
|
|
7851
|
+
const now = Date.now();
|
|
7852
|
+
const toEvict = [];
|
|
7853
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
7854
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
7855
|
+
toEvict.push(name);
|
|
7856
|
+
}
|
|
7857
|
+
}
|
|
7858
|
+
for (const name of toEvict) {
|
|
7859
|
+
const client = _shards.get(name);
|
|
7860
|
+
if (client) {
|
|
7861
|
+
client.close();
|
|
7862
|
+
}
|
|
7863
|
+
_shards.delete(name);
|
|
7864
|
+
_shardLastAccess.delete(name);
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
7867
|
+
function getOpenShardCount() {
|
|
7868
|
+
return _shards.size;
|
|
7792
7869
|
}
|
|
7793
7870
|
function disposeShards() {
|
|
7871
|
+
if (_evictionTimer) {
|
|
7872
|
+
clearInterval(_evictionTimer);
|
|
7873
|
+
_evictionTimer = null;
|
|
7874
|
+
}
|
|
7794
7875
|
for (const [, client] of _shards) {
|
|
7795
7876
|
client.close();
|
|
7796
7877
|
}
|
|
7797
7878
|
_shards.clear();
|
|
7879
|
+
_shardLastAccess.clear();
|
|
7798
7880
|
_shardingEnabled = false;
|
|
7799
7881
|
_encryptionKey = null;
|
|
7800
7882
|
}
|
|
7801
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
7883
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
7802
7884
|
var init_shard_manager = __esm({
|
|
7803
7885
|
"src/lib/shard-manager.ts"() {
|
|
7804
7886
|
"use strict";
|
|
7805
7887
|
init_config();
|
|
7806
7888
|
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
7889
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
7890
|
+
MAX_OPEN_SHARDS = 10;
|
|
7891
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
7807
7892
|
_shards = /* @__PURE__ */ new Map();
|
|
7893
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
7894
|
+
_evictionTimer = null;
|
|
7808
7895
|
_encryptionKey = null;
|
|
7809
7896
|
_shardingEnabled = false;
|
|
7810
7897
|
}
|
|
7811
7898
|
});
|
|
7812
7899
|
|
|
7900
|
+
// src/lib/keychain.ts
|
|
7901
|
+
var keychain_exports = {};
|
|
7902
|
+
__export(keychain_exports, {
|
|
7903
|
+
deleteMasterKey: () => deleteMasterKey,
|
|
7904
|
+
exportMnemonic: () => exportMnemonic,
|
|
7905
|
+
getMasterKey: () => getMasterKey,
|
|
7906
|
+
importMnemonic: () => importMnemonic,
|
|
7907
|
+
setMasterKey: () => setMasterKey
|
|
7908
|
+
});
|
|
7909
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7910
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7911
|
+
import path20 from "path";
|
|
7912
|
+
import os12 from "os";
|
|
7913
|
+
function getKeyDir() {
|
|
7914
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
|
|
7915
|
+
}
|
|
7916
|
+
function getKeyPath() {
|
|
7917
|
+
return path20.join(getKeyDir(), "master.key");
|
|
7918
|
+
}
|
|
7919
|
+
async function tryKeytar() {
|
|
7920
|
+
try {
|
|
7921
|
+
return await import("keytar");
|
|
7922
|
+
} catch {
|
|
7923
|
+
return null;
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7926
|
+
async function getMasterKey() {
|
|
7927
|
+
const keytar = await tryKeytar();
|
|
7928
|
+
if (keytar) {
|
|
7929
|
+
try {
|
|
7930
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
7931
|
+
if (stored) {
|
|
7932
|
+
return Buffer.from(stored, "base64");
|
|
7933
|
+
}
|
|
7934
|
+
} catch {
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
const keyPath = getKeyPath();
|
|
7938
|
+
if (!existsSync18(keyPath)) {
|
|
7939
|
+
process.stderr.write(
|
|
7940
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7941
|
+
`
|
|
7942
|
+
);
|
|
7943
|
+
return null;
|
|
7944
|
+
}
|
|
7945
|
+
try {
|
|
7946
|
+
const content = await readFile4(keyPath, "utf-8");
|
|
7947
|
+
return Buffer.from(content.trim(), "base64");
|
|
7948
|
+
} catch (err) {
|
|
7949
|
+
process.stderr.write(
|
|
7950
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
7951
|
+
`
|
|
7952
|
+
);
|
|
7953
|
+
return null;
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
async function setMasterKey(key) {
|
|
7957
|
+
const b64 = key.toString("base64");
|
|
7958
|
+
const keytar = await tryKeytar();
|
|
7959
|
+
if (keytar) {
|
|
7960
|
+
try {
|
|
7961
|
+
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
7962
|
+
return;
|
|
7963
|
+
} catch {
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
const dir = getKeyDir();
|
|
7967
|
+
await mkdir4(dir, { recursive: true });
|
|
7968
|
+
const keyPath = getKeyPath();
|
|
7969
|
+
await writeFile5(keyPath, b64 + "\n", "utf-8");
|
|
7970
|
+
await chmod2(keyPath, 384);
|
|
7971
|
+
}
|
|
7972
|
+
async function deleteMasterKey() {
|
|
7973
|
+
const keytar = await tryKeytar();
|
|
7974
|
+
if (keytar) {
|
|
7975
|
+
try {
|
|
7976
|
+
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
7977
|
+
} catch {
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
const keyPath = getKeyPath();
|
|
7981
|
+
if (existsSync18(keyPath)) {
|
|
7982
|
+
await unlink(keyPath);
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
async function loadBip39() {
|
|
7986
|
+
try {
|
|
7987
|
+
return await import("bip39");
|
|
7988
|
+
} catch {
|
|
7989
|
+
throw new Error(
|
|
7990
|
+
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
7991
|
+
);
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
async function exportMnemonic(key) {
|
|
7995
|
+
if (key.length !== 32) {
|
|
7996
|
+
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
7997
|
+
}
|
|
7998
|
+
const { entropyToMnemonic } = await loadBip39();
|
|
7999
|
+
return entropyToMnemonic(key.toString("hex"));
|
|
8000
|
+
}
|
|
8001
|
+
async function importMnemonic(mnemonic) {
|
|
8002
|
+
const trimmed = mnemonic.trim();
|
|
8003
|
+
const words = trimmed.split(/\s+/);
|
|
8004
|
+
if (words.length !== 24) {
|
|
8005
|
+
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
8006
|
+
}
|
|
8007
|
+
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
8008
|
+
if (!validateMnemonic(trimmed)) {
|
|
8009
|
+
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
8010
|
+
}
|
|
8011
|
+
const entropy = mnemonicToEntropy(trimmed);
|
|
8012
|
+
return Buffer.from(entropy, "hex");
|
|
8013
|
+
}
|
|
8014
|
+
var SERVICE, ACCOUNT;
|
|
8015
|
+
var init_keychain = __esm({
|
|
8016
|
+
"src/lib/keychain.ts"() {
|
|
8017
|
+
"use strict";
|
|
8018
|
+
SERVICE = "exe-mem";
|
|
8019
|
+
ACCOUNT = "master-key";
|
|
8020
|
+
}
|
|
8021
|
+
});
|
|
8022
|
+
|
|
7813
8023
|
// src/lib/platform-procedures.ts
|
|
7814
8024
|
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
7815
8025
|
var init_platform_procedures = __esm({
|
|
@@ -8582,15 +8792,15 @@ async function pollPendingReviews(deps, state) {
|
|
|
8582
8792
|
return [];
|
|
8583
8793
|
}
|
|
8584
8794
|
if (sessions.length === 0) return [];
|
|
8585
|
-
let reviewCount;
|
|
8586
|
-
try {
|
|
8587
|
-
reviewCount = await deps.countPendingReviews();
|
|
8588
|
-
} catch {
|
|
8589
|
-
return [];
|
|
8590
|
-
}
|
|
8591
|
-
if (reviewCount === 0) return [];
|
|
8592
8795
|
const sent = [];
|
|
8593
8796
|
for (const exeSession of sessions) {
|
|
8797
|
+
let reviewCount = 0;
|
|
8798
|
+
try {
|
|
8799
|
+
reviewCount = await deps.countPendingReviews(exeSession);
|
|
8800
|
+
} catch {
|
|
8801
|
+
continue;
|
|
8802
|
+
}
|
|
8803
|
+
if (reviewCount === 0) continue;
|
|
8594
8804
|
const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
|
|
8595
8805
|
if (Date.now() - lastSent < state.intervalMs) continue;
|
|
8596
8806
|
try {
|
|
@@ -8600,15 +8810,17 @@ async function pollPendingReviews(deps, state) {
|
|
|
8600
8810
|
} catch {
|
|
8601
8811
|
}
|
|
8602
8812
|
}
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8813
|
+
for (const exeSession of sessions) {
|
|
8814
|
+
try {
|
|
8815
|
+
const orphans = await deps.findOrphanedDoneTasks(exeSession);
|
|
8816
|
+
for (const orphan of orphans) {
|
|
8817
|
+
try {
|
|
8818
|
+
await deps.createReviewForOrphan(orphan);
|
|
8819
|
+
} catch {
|
|
8820
|
+
}
|
|
8609
8821
|
}
|
|
8822
|
+
} catch {
|
|
8610
8823
|
}
|
|
8611
|
-
} catch {
|
|
8612
8824
|
}
|
|
8613
8825
|
if (deps.findStaleTasks && deps.sendNudge) {
|
|
8614
8826
|
try {
|
|
@@ -8626,50 +8838,56 @@ async function pollPendingReviews(deps, state) {
|
|
|
8626
8838
|
}
|
|
8627
8839
|
}
|
|
8628
8840
|
if (deps.findUrgentUnread) {
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
}
|
|
8637
|
-
const ageMs = Date.now() - new Date(msg.created_at).getTime();
|
|
8638
|
-
if (ageMs > 5 * 60 * 1e3) {
|
|
8639
|
-
process.stderr.write(
|
|
8640
|
-
`[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
|
|
8641
|
-
`
|
|
8841
|
+
for (const exeSession of sessions) {
|
|
8842
|
+
try {
|
|
8843
|
+
const urgent = await deps.findUrgentUnread(exeSession);
|
|
8844
|
+
for (const msg of urgent) {
|
|
8845
|
+
try {
|
|
8846
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8847
|
+
(s) => s.startsWith(`${msg.target_agent}-`) && s.endsWith(`-${exeSession}`)
|
|
8642
8848
|
);
|
|
8849
|
+
for (const sess of employeeSessions) {
|
|
8850
|
+
deps.sendIntercom(sess);
|
|
8851
|
+
}
|
|
8852
|
+
const ageMs = Date.now() - new Date(msg.created_at).getTime();
|
|
8853
|
+
if (ageMs > 5 * 60 * 1e3) {
|
|
8854
|
+
process.stderr.write(
|
|
8855
|
+
`[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
|
|
8856
|
+
`
|
|
8857
|
+
);
|
|
8858
|
+
}
|
|
8859
|
+
} catch {
|
|
8643
8860
|
}
|
|
8644
|
-
} catch {
|
|
8645
8861
|
}
|
|
8862
|
+
} catch {
|
|
8646
8863
|
}
|
|
8647
|
-
} catch {
|
|
8648
8864
|
}
|
|
8649
8865
|
}
|
|
8650
8866
|
if (deps.findUnstartedTasks) {
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
for (const sess of employeeSessions) {
|
|
8659
|
-
deps.sendIntercom(sess);
|
|
8660
|
-
}
|
|
8661
|
-
const ageMs = Date.now() - new Date(task.created_at).getTime();
|
|
8662
|
-
const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
|
|
8663
|
-
if (ageMs > UNSTARTED_WARNING_MS) {
|
|
8664
|
-
process.stderr.write(
|
|
8665
|
-
`[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
|
|
8666
|
-
`
|
|
8867
|
+
for (const exeSession of sessions) {
|
|
8868
|
+
try {
|
|
8869
|
+
const unstarted = await deps.findUnstartedTasks(exeSession);
|
|
8870
|
+
for (const task of unstarted) {
|
|
8871
|
+
try {
|
|
8872
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8873
|
+
(s) => (s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)) && s.endsWith(`-${exeSession}`)
|
|
8667
8874
|
);
|
|
8875
|
+
for (const sess of employeeSessions) {
|
|
8876
|
+
deps.sendIntercom(sess);
|
|
8877
|
+
}
|
|
8878
|
+
const ageMs = Date.now() - new Date(task.created_at).getTime();
|
|
8879
|
+
const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
|
|
8880
|
+
if (ageMs > UNSTARTED_WARNING_MS) {
|
|
8881
|
+
process.stderr.write(
|
|
8882
|
+
`[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
|
|
8883
|
+
`
|
|
8884
|
+
);
|
|
8885
|
+
}
|
|
8886
|
+
} catch {
|
|
8668
8887
|
}
|
|
8669
|
-
} catch {
|
|
8670
8888
|
}
|
|
8889
|
+
} catch {
|
|
8671
8890
|
}
|
|
8672
|
-
} catch {
|
|
8673
8891
|
}
|
|
8674
8892
|
}
|
|
8675
8893
|
return sent;
|
|
@@ -8682,9 +8900,9 @@ function createRealDeps(getClient2) {
|
|
|
8682
8900
|
timeout: 3e3
|
|
8683
8901
|
}).trim().split("\n").filter(Boolean);
|
|
8684
8902
|
},
|
|
8685
|
-
countPendingReviews: async () => {
|
|
8903
|
+
countPendingReviews: async (sessionScope) => {
|
|
8686
8904
|
const client = getClient2();
|
|
8687
|
-
const rpScope =
|
|
8905
|
+
const rpScope = strictSessionScopeFilter(sessionScope);
|
|
8688
8906
|
const result = await client.execute({
|
|
8689
8907
|
sql: `SELECT COUNT(*) as count FROM tasks
|
|
8690
8908
|
WHERE status = 'needs_review'${rpScope.sql}`,
|
|
@@ -8696,10 +8914,10 @@ function createRealDeps(getClient2) {
|
|
|
8696
8914
|
const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
|
|
8697
8915
|
centralSend(session);
|
|
8698
8916
|
},
|
|
8699
|
-
findOrphanedDoneTasks: async () => {
|
|
8917
|
+
findOrphanedDoneTasks: async (sessionScope) => {
|
|
8700
8918
|
const client = getClient2();
|
|
8701
8919
|
const coordinatorName = getCoordinatorName();
|
|
8702
|
-
const odScope =
|
|
8920
|
+
const odScope = strictSessionScopeFilter(sessionScope, "t");
|
|
8703
8921
|
const result = await client.execute({
|
|
8704
8922
|
sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
|
|
8705
8923
|
t.project_name, t.task_file, t.result, t.status
|
|
@@ -8724,24 +8942,25 @@ function createRealDeps(getClient2) {
|
|
|
8724
8942
|
process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
|
|
8725
8943
|
`);
|
|
8726
8944
|
},
|
|
8727
|
-
findUrgentUnread: async () => {
|
|
8945
|
+
findUrgentUnread: async (sessionScope) => {
|
|
8728
8946
|
const client = getClient2();
|
|
8947
|
+
const msgScope = strictSessionScopeFilter(sessionScope);
|
|
8729
8948
|
const result = await client.execute({
|
|
8730
8949
|
sql: `SELECT id, target_agent, content, created_at
|
|
8731
8950
|
FROM messages
|
|
8732
8951
|
WHERE priority = 'urgent'
|
|
8733
8952
|
AND status IN ('pending', 'delivered')
|
|
8734
|
-
AND created_at <= datetime('now', '-2 minutes')
|
|
8953
|
+
AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
|
|
8735
8954
|
ORDER BY created_at ASC
|
|
8736
8955
|
LIMIT 10`,
|
|
8737
|
-
args: []
|
|
8956
|
+
args: [...msgScope.args]
|
|
8738
8957
|
});
|
|
8739
8958
|
return result.rows;
|
|
8740
8959
|
},
|
|
8741
|
-
findUnstartedTasks: async () => {
|
|
8960
|
+
findUnstartedTasks: async (sessionScope) => {
|
|
8742
8961
|
const client = getClient2();
|
|
8743
8962
|
const coordinatorName = getCoordinatorName();
|
|
8744
|
-
const usScope =
|
|
8963
|
+
const usScope = strictSessionScopeFilter(sessionScope);
|
|
8745
8964
|
const result = await client.execute({
|
|
8746
8965
|
sql: `SELECT id, title, assigned_to, created_at
|
|
8747
8966
|
FROM tasks
|
|
@@ -9176,10 +9395,10 @@ async function disposeEmbedder() {
|
|
|
9176
9395
|
async function embedDirect(text) {
|
|
9177
9396
|
const llamaCpp = await import("node-llama-cpp");
|
|
9178
9397
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9179
|
-
const { existsSync:
|
|
9180
|
-
const
|
|
9181
|
-
const modelPath =
|
|
9182
|
-
if (!
|
|
9398
|
+
const { existsSync: existsSync21 } = await import("fs");
|
|
9399
|
+
const path25 = await import("path");
|
|
9400
|
+
const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
9401
|
+
if (!existsSync21(modelPath)) {
|
|
9183
9402
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
9184
9403
|
}
|
|
9185
9404
|
const llama = await llamaCpp.getLlama();
|
|
@@ -9401,13 +9620,13 @@ __export(graph_rag_exports, {
|
|
|
9401
9620
|
resolveAlias: () => resolveAlias,
|
|
9402
9621
|
storeExtraction: () => storeExtraction
|
|
9403
9622
|
});
|
|
9404
|
-
import
|
|
9623
|
+
import crypto7 from "crypto";
|
|
9405
9624
|
function normalizeEntityName(name) {
|
|
9406
9625
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
9407
9626
|
}
|
|
9408
9627
|
function entityId(name, type) {
|
|
9409
9628
|
const normalized = normalizeEntityName(name);
|
|
9410
|
-
return
|
|
9629
|
+
return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
9411
9630
|
}
|
|
9412
9631
|
async function resolveAlias(client, name) {
|
|
9413
9632
|
const normalized = normalizeEntityName(name);
|
|
@@ -9657,7 +9876,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9657
9876
|
const targetAlias = await resolveAlias(client, r.target);
|
|
9658
9877
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
9659
9878
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
9660
|
-
const relId =
|
|
9879
|
+
const relId = crypto7.randomUUID().slice(0, 16);
|
|
9661
9880
|
try {
|
|
9662
9881
|
await client.execute({
|
|
9663
9882
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -9720,7 +9939,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9720
9939
|
}
|
|
9721
9940
|
}
|
|
9722
9941
|
for (const h of extraction.hyperedges) {
|
|
9723
|
-
const hId =
|
|
9942
|
+
const hId = crypto7.randomUUID().slice(0, 16);
|
|
9724
9943
|
try {
|
|
9725
9944
|
await client.execute({
|
|
9726
9945
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -9784,7 +10003,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
9784
10003
|
totalEntities += stored.entitiesStored;
|
|
9785
10004
|
totalRelationships += stored.relationshipsStored;
|
|
9786
10005
|
}
|
|
9787
|
-
const contentHash =
|
|
10006
|
+
const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
9788
10007
|
await client.execute({
|
|
9789
10008
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
9790
10009
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -9901,8 +10120,8 @@ __export(wiki_sync_exports, {
|
|
|
9901
10120
|
listWorkspaces: () => listWorkspaces,
|
|
9902
10121
|
syncMemories: () => syncMemories
|
|
9903
10122
|
});
|
|
9904
|
-
async function wikiRequest(config,
|
|
9905
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
10123
|
+
async function wikiRequest(config, path25, method = "GET", body) {
|
|
10124
|
+
const url = `${config.wikiUrl}/api/v1${path25}`;
|
|
9906
10125
|
const headers = {
|
|
9907
10126
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
9908
10127
|
"Content-Type": "application/json"
|
|
@@ -9914,7 +10133,7 @@ async function wikiRequest(config, path24, method = "GET", body) {
|
|
|
9914
10133
|
signal: AbortSignal.timeout(3e4)
|
|
9915
10134
|
});
|
|
9916
10135
|
if (!response.ok) {
|
|
9917
|
-
throw new Error(`Wiki API ${method} ${
|
|
10136
|
+
throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
|
|
9918
10137
|
}
|
|
9919
10138
|
return response.json();
|
|
9920
10139
|
}
|
|
@@ -10026,8 +10245,8 @@ __export(token_spend_exports, {
|
|
|
10026
10245
|
import { readdir } from "fs/promises";
|
|
10027
10246
|
import { createReadStream } from "fs";
|
|
10028
10247
|
import { createInterface } from "readline";
|
|
10029
|
-
import
|
|
10030
|
-
import
|
|
10248
|
+
import path21 from "path";
|
|
10249
|
+
import os13 from "os";
|
|
10031
10250
|
function getPricing(model) {
|
|
10032
10251
|
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
10033
10252
|
const stripped = model.replace(/-\d{8}$/, "");
|
|
@@ -10039,29 +10258,33 @@ function getPricing(model) {
|
|
|
10039
10258
|
return DEFAULT_PRICING;
|
|
10040
10259
|
}
|
|
10041
10260
|
async function getAgentSpend(period = "7d") {
|
|
10261
|
+
const cached = _spendCache.get(period);
|
|
10262
|
+
if (cached && Date.now() < cached.expires) {
|
|
10263
|
+
return cached.result;
|
|
10264
|
+
}
|
|
10042
10265
|
const cutoff = periodToCutoff(period);
|
|
10043
10266
|
const client = getClient();
|
|
10044
|
-
const
|
|
10267
|
+
const dbResult = await client.execute({
|
|
10045
10268
|
sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
|
|
10046
10269
|
args: [cutoff]
|
|
10047
10270
|
});
|
|
10048
|
-
if (
|
|
10271
|
+
if (dbResult.rows.length === 0) return [];
|
|
10049
10272
|
const sessionAgent = /* @__PURE__ */ new Map();
|
|
10050
|
-
for (const row of
|
|
10273
|
+
for (const row of dbResult.rows) {
|
|
10051
10274
|
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
10052
10275
|
}
|
|
10053
|
-
const claudeDir =
|
|
10276
|
+
const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
|
|
10054
10277
|
let projectDirs = [];
|
|
10055
10278
|
try {
|
|
10056
10279
|
const entries = await readdir(claudeDir);
|
|
10057
|
-
projectDirs = entries.map((e) =>
|
|
10280
|
+
projectDirs = entries.map((e) => path21.join(claudeDir, e));
|
|
10058
10281
|
} catch {
|
|
10059
10282
|
return [];
|
|
10060
10283
|
}
|
|
10061
10284
|
const agentTotals = /* @__PURE__ */ new Map();
|
|
10062
10285
|
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
10063
10286
|
for (const dir of projectDirs) {
|
|
10064
|
-
const jsonlPath =
|
|
10287
|
+
const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
|
|
10065
10288
|
try {
|
|
10066
10289
|
const usage = await extractSessionUsage(jsonlPath);
|
|
10067
10290
|
if (usage.input === 0 && usage.output === 0) continue;
|
|
@@ -10085,7 +10308,7 @@ async function getAgentSpend(period = "7d") {
|
|
|
10085
10308
|
}
|
|
10086
10309
|
}
|
|
10087
10310
|
}
|
|
10088
|
-
|
|
10311
|
+
const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
|
|
10089
10312
|
agentId,
|
|
10090
10313
|
inputTokens: t.input,
|
|
10091
10314
|
outputTokens: t.output,
|
|
@@ -10095,6 +10318,8 @@ async function getAgentSpend(period = "7d") {
|
|
|
10095
10318
|
sessions: t.sessions.size,
|
|
10096
10319
|
period
|
|
10097
10320
|
})).sort((a, b) => b.costUSD - a.costUSD);
|
|
10321
|
+
_spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
|
|
10322
|
+
return result;
|
|
10098
10323
|
}
|
|
10099
10324
|
async function extractSessionUsage(jsonlPath) {
|
|
10100
10325
|
let input = 0;
|
|
@@ -10141,7 +10366,7 @@ function periodToCutoff(period) {
|
|
|
10141
10366
|
const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
|
|
10142
10367
|
return new Date(Date.now() - ms).toISOString();
|
|
10143
10368
|
}
|
|
10144
|
-
var MODEL_PRICING, DEFAULT_PRICING;
|
|
10369
|
+
var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
|
|
10145
10370
|
var init_token_spend = __esm({
|
|
10146
10371
|
"src/lib/token-spend.ts"() {
|
|
10147
10372
|
"use strict";
|
|
@@ -10171,6 +10396,8 @@ var init_token_spend = __esm({
|
|
|
10171
10396
|
"claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
|
|
10172
10397
|
};
|
|
10173
10398
|
DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
|
|
10399
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10400
|
+
_spendCache = /* @__PURE__ */ new Map();
|
|
10174
10401
|
}
|
|
10175
10402
|
});
|
|
10176
10403
|
|
|
@@ -10182,11 +10409,11 @@ __export(update_check_exports, {
|
|
|
10182
10409
|
getRemoteVersion: () => getRemoteVersion
|
|
10183
10410
|
});
|
|
10184
10411
|
import { execSync as execSync11 } from "child_process";
|
|
10185
|
-
import { readFileSync as
|
|
10186
|
-
import
|
|
10412
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
10413
|
+
import path22 from "path";
|
|
10187
10414
|
function getLocalVersion(packageRoot) {
|
|
10188
|
-
const pkgPath =
|
|
10189
|
-
const pkg = JSON.parse(
|
|
10415
|
+
const pkgPath = path22.join(packageRoot, "package.json");
|
|
10416
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
10190
10417
|
return pkg.version;
|
|
10191
10418
|
}
|
|
10192
10419
|
function getRemoteVersion() {
|
|
@@ -10229,16 +10456,16 @@ __export(ws_auth_exports, {
|
|
|
10229
10456
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
10230
10457
|
hashAuthToken: () => hashAuthToken
|
|
10231
10458
|
});
|
|
10232
|
-
import
|
|
10459
|
+
import crypto8 from "crypto";
|
|
10233
10460
|
function deriveWsAuthToken(masterKey) {
|
|
10234
|
-
return Buffer.from(
|
|
10461
|
+
return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
10235
10462
|
}
|
|
10236
10463
|
function deriveOrgId(masterKey) {
|
|
10237
|
-
const raw = Buffer.from(
|
|
10238
|
-
return
|
|
10464
|
+
const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
10465
|
+
return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
10239
10466
|
}
|
|
10240
10467
|
function hashAuthToken(token) {
|
|
10241
|
-
return
|
|
10468
|
+
return crypto8.createHash("sha256").update(token).digest("hex");
|
|
10242
10469
|
}
|
|
10243
10470
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
10244
10471
|
var init_ws_auth = __esm({
|
|
@@ -10256,14 +10483,14 @@ __export(device_registry_exports, {
|
|
|
10256
10483
|
resolveTargetDevice: () => resolveTargetDevice,
|
|
10257
10484
|
setFriendlyName: () => setFriendlyName
|
|
10258
10485
|
});
|
|
10259
|
-
import
|
|
10260
|
-
import
|
|
10261
|
-
import { readFileSync as
|
|
10262
|
-
import
|
|
10486
|
+
import crypto9 from "crypto";
|
|
10487
|
+
import os14 from "os";
|
|
10488
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
|
|
10489
|
+
import path23 from "path";
|
|
10263
10490
|
function getDeviceInfo() {
|
|
10264
|
-
if (
|
|
10491
|
+
if (existsSync19(DEVICE_JSON_PATH)) {
|
|
10265
10492
|
try {
|
|
10266
|
-
const raw =
|
|
10493
|
+
const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
|
|
10267
10494
|
const data = JSON.parse(raw);
|
|
10268
10495
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
10269
10496
|
return data;
|
|
@@ -10271,20 +10498,20 @@ function getDeviceInfo() {
|
|
|
10271
10498
|
} catch {
|
|
10272
10499
|
}
|
|
10273
10500
|
}
|
|
10274
|
-
const hostname =
|
|
10501
|
+
const hostname = os14.hostname();
|
|
10275
10502
|
const info = {
|
|
10276
|
-
deviceId:
|
|
10503
|
+
deviceId: crypto9.randomUUID(),
|
|
10277
10504
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
10278
10505
|
hostname
|
|
10279
10506
|
};
|
|
10280
|
-
mkdirSync8(
|
|
10281
|
-
|
|
10507
|
+
mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
10508
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10282
10509
|
return info;
|
|
10283
10510
|
}
|
|
10284
10511
|
function setFriendlyName(name) {
|
|
10285
10512
|
const info = getDeviceInfo();
|
|
10286
10513
|
info.friendlyName = name;
|
|
10287
|
-
|
|
10514
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10288
10515
|
}
|
|
10289
10516
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
10290
10517
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -10318,7 +10545,7 @@ var init_device_registry = __esm({
|
|
|
10318
10545
|
"src/lib/device-registry.ts"() {
|
|
10319
10546
|
"use strict";
|
|
10320
10547
|
init_config();
|
|
10321
|
-
DEVICE_JSON_PATH =
|
|
10548
|
+
DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
|
|
10322
10549
|
}
|
|
10323
10550
|
});
|
|
10324
10551
|
|
|
@@ -10542,10 +10769,10 @@ __export(messaging_exports, {
|
|
|
10542
10769
|
sendMessage: () => sendMessage,
|
|
10543
10770
|
setWsClientSend: () => setWsClientSend
|
|
10544
10771
|
});
|
|
10545
|
-
import
|
|
10772
|
+
import crypto10 from "crypto";
|
|
10546
10773
|
function generateUlid() {
|
|
10547
10774
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
10548
|
-
const random =
|
|
10775
|
+
const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
|
|
10549
10776
|
return (timestamp + random).toUpperCase();
|
|
10550
10777
|
}
|
|
10551
10778
|
function rowToMessage(row) {
|
|
@@ -10556,6 +10783,7 @@ function rowToMessage(row) {
|
|
|
10556
10783
|
targetAgent: row.target_agent,
|
|
10557
10784
|
targetProject: row.target_project ?? null,
|
|
10558
10785
|
targetDevice: row.target_device,
|
|
10786
|
+
sessionScope: row.session_scope ?? null,
|
|
10559
10787
|
content: row.content,
|
|
10560
10788
|
priority: row.priority ?? "normal",
|
|
10561
10789
|
status: row.status ?? "pending",
|
|
@@ -10573,15 +10801,17 @@ async function sendMessage(input) {
|
|
|
10573
10801
|
const id = generateUlid();
|
|
10574
10802
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10575
10803
|
const targetDevice = input.targetDevice ?? "local";
|
|
10804
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
10576
10805
|
await client.execute({
|
|
10577
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
10578
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10806
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
10807
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10579
10808
|
args: [
|
|
10580
10809
|
id,
|
|
10581
10810
|
input.fromAgent,
|
|
10582
10811
|
input.targetAgent,
|
|
10583
10812
|
input.targetProject ?? null,
|
|
10584
10813
|
targetDevice,
|
|
10814
|
+
sessionScope,
|
|
10585
10815
|
input.content,
|
|
10586
10816
|
input.priority ?? "normal",
|
|
10587
10817
|
now
|
|
@@ -10595,9 +10825,10 @@ async function sendMessage(input) {
|
|
|
10595
10825
|
}
|
|
10596
10826
|
} catch {
|
|
10597
10827
|
}
|
|
10828
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
10598
10829
|
const result = await client.execute({
|
|
10599
|
-
sql:
|
|
10600
|
-
args: [id]
|
|
10830
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
10831
|
+
args: [id, ...sentScope.args]
|
|
10601
10832
|
});
|
|
10602
10833
|
return rowToMessage(result.rows[0]);
|
|
10603
10834
|
}
|
|
@@ -10621,6 +10852,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
10621
10852
|
fromAgent: msg.fromAgent,
|
|
10622
10853
|
targetAgent: msg.targetAgent,
|
|
10623
10854
|
targetProject: msg.targetProject,
|
|
10855
|
+
sessionScope: msg.sessionScope,
|
|
10624
10856
|
content: msg.content,
|
|
10625
10857
|
priority: msg.priority,
|
|
10626
10858
|
createdAt: msg.createdAt
|
|
@@ -10664,7 +10896,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
10664
10896
|
} catch {
|
|
10665
10897
|
const newRetryCount = msg.retryCount + 1;
|
|
10666
10898
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
10667
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
10899
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
10668
10900
|
} else {
|
|
10669
10901
|
await client.execute({
|
|
10670
10902
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -10674,85 +10906,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
10674
10906
|
return false;
|
|
10675
10907
|
}
|
|
10676
10908
|
}
|
|
10677
|
-
async function getPendingMessages(targetAgent) {
|
|
10909
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
10678
10910
|
const client = getClient();
|
|
10911
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10679
10912
|
const result = await client.execute({
|
|
10680
10913
|
sql: `SELECT * FROM messages
|
|
10681
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
10914
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
10682
10915
|
ORDER BY id`,
|
|
10683
|
-
args: [targetAgent]
|
|
10916
|
+
args: [targetAgent, ...scope.args]
|
|
10684
10917
|
});
|
|
10685
10918
|
return result.rows.map((row) => rowToMessage(row));
|
|
10686
10919
|
}
|
|
10687
|
-
async function markRead(messageId) {
|
|
10920
|
+
async function markRead(messageId, sessionScope) {
|
|
10688
10921
|
const client = getClient();
|
|
10922
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10689
10923
|
await client.execute({
|
|
10690
|
-
sql:
|
|
10691
|
-
|
|
10924
|
+
sql: `UPDATE messages SET status = 'read'
|
|
10925
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
10926
|
+
args: [messageId, ...scope.args]
|
|
10692
10927
|
});
|
|
10693
10928
|
}
|
|
10694
|
-
async function markAcknowledged(messageId) {
|
|
10929
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
10695
10930
|
const client = getClient();
|
|
10931
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10696
10932
|
await client.execute({
|
|
10697
|
-
sql:
|
|
10698
|
-
|
|
10933
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
10934
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
10935
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10699
10936
|
});
|
|
10700
10937
|
}
|
|
10701
|
-
async function markProcessed(messageId) {
|
|
10938
|
+
async function markProcessed(messageId, sessionScope) {
|
|
10702
10939
|
const client = getClient();
|
|
10940
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10703
10941
|
await client.execute({
|
|
10704
|
-
sql:
|
|
10705
|
-
|
|
10942
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
10943
|
+
WHERE id = ?${scope.sql}`,
|
|
10944
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10706
10945
|
});
|
|
10707
10946
|
}
|
|
10708
|
-
async function getMessageStatus(messageId) {
|
|
10947
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
10709
10948
|
const client = getClient();
|
|
10949
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10710
10950
|
const result = await client.execute({
|
|
10711
|
-
sql:
|
|
10712
|
-
args: [messageId]
|
|
10951
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
10952
|
+
args: [messageId, ...scope.args]
|
|
10713
10953
|
});
|
|
10714
10954
|
return result.rows[0]?.status ?? null;
|
|
10715
10955
|
}
|
|
10716
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
10956
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
10717
10957
|
const client = getClient();
|
|
10958
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10718
10959
|
const result = await client.execute({
|
|
10719
10960
|
sql: `SELECT * FROM messages
|
|
10720
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
10961
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
10721
10962
|
ORDER BY id`,
|
|
10722
|
-
args: [targetAgent]
|
|
10963
|
+
args: [targetAgent, ...scope.args]
|
|
10723
10964
|
});
|
|
10724
10965
|
return result.rows.map((row) => rowToMessage(row));
|
|
10725
10966
|
}
|
|
10726
|
-
async function getReadMessages(targetAgent) {
|
|
10967
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
10727
10968
|
const client = getClient();
|
|
10969
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10728
10970
|
const result = await client.execute({
|
|
10729
|
-
sql:
|
|
10730
|
-
|
|
10971
|
+
sql: `SELECT * FROM messages
|
|
10972
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
10973
|
+
ORDER BY id`,
|
|
10974
|
+
args: [targetAgent, ...scope.args]
|
|
10731
10975
|
});
|
|
10732
10976
|
return result.rows.map((row) => rowToMessage(row));
|
|
10733
10977
|
}
|
|
10734
|
-
async function markFailed(messageId, reason) {
|
|
10978
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
10735
10979
|
const client = getClient();
|
|
10980
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10736
10981
|
await client.execute({
|
|
10737
|
-
sql:
|
|
10738
|
-
|
|
10982
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
10983
|
+
WHERE id = ?${scope.sql}`,
|
|
10984
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
10739
10985
|
});
|
|
10740
10986
|
}
|
|
10741
|
-
async function getFailedMessages() {
|
|
10987
|
+
async function getFailedMessages(sessionScope) {
|
|
10742
10988
|
const client = getClient();
|
|
10989
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10743
10990
|
const result = await client.execute({
|
|
10744
|
-
sql:
|
|
10745
|
-
args: []
|
|
10991
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
10992
|
+
args: [...scope.args]
|
|
10746
10993
|
});
|
|
10747
10994
|
return result.rows.map((row) => rowToMessage(row));
|
|
10748
10995
|
}
|
|
10749
|
-
async function retryPendingMessages() {
|
|
10996
|
+
async function retryPendingMessages(sessionScope) {
|
|
10750
10997
|
const client = getClient();
|
|
10998
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10751
10999
|
const result = await client.execute({
|
|
10752
11000
|
sql: `SELECT * FROM messages
|
|
10753
|
-
WHERE status = 'pending' AND retry_count <
|
|
11001
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
10754
11002
|
ORDER BY id`,
|
|
10755
|
-
args: [MAX_RETRIES3]
|
|
11003
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
10756
11004
|
});
|
|
10757
11005
|
let delivered = 0;
|
|
10758
11006
|
for (const row of result.rows) {
|
|
@@ -10771,6 +11019,7 @@ var init_messaging = __esm({
|
|
|
10771
11019
|
"use strict";
|
|
10772
11020
|
init_database();
|
|
10773
11021
|
init_tmux_routing();
|
|
11022
|
+
init_task_scope();
|
|
10774
11023
|
MAX_RETRIES3 = 10;
|
|
10775
11024
|
_wsClientSend = null;
|
|
10776
11025
|
}
|
|
@@ -10780,19 +11029,23 @@ var init_messaging = __esm({
|
|
|
10780
11029
|
init_config();
|
|
10781
11030
|
init_memory();
|
|
10782
11031
|
init_daemon_protocol();
|
|
11032
|
+
init_daemon_auth();
|
|
10783
11033
|
init_daemon_orchestration();
|
|
11034
|
+
import os15 from "os";
|
|
10784
11035
|
import net2 from "net";
|
|
10785
|
-
import { writeFileSync as
|
|
10786
|
-
import
|
|
11036
|
+
import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
|
|
11037
|
+
import path24 from "path";
|
|
10787
11038
|
import { getLlama } from "node-llama-cpp";
|
|
10788
|
-
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
10789
|
-
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
11039
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
|
|
11040
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
|
|
10790
11041
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
10791
11042
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
10792
11043
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
11044
|
+
var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
|
|
10793
11045
|
var _context = null;
|
|
10794
11046
|
var _model = null;
|
|
10795
11047
|
var _llama = null;
|
|
11048
|
+
var _daemonToken = "";
|
|
10796
11049
|
var MAX_QUEUE_SIZE = 1e3;
|
|
10797
11050
|
var highQueue = [];
|
|
10798
11051
|
var lowQueue = [];
|
|
@@ -10812,8 +11065,8 @@ function enqueue(queue, entry) {
|
|
|
10812
11065
|
queue.push(entry);
|
|
10813
11066
|
}
|
|
10814
11067
|
async function loadModel() {
|
|
10815
|
-
const modelPath =
|
|
10816
|
-
if (!
|
|
11068
|
+
const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
|
|
11069
|
+
if (!existsSync20(modelPath)) {
|
|
10817
11070
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
10818
11071
|
`);
|
|
10819
11072
|
process.exit(1);
|
|
@@ -10837,6 +11090,7 @@ async function processQueue() {
|
|
|
10837
11090
|
for (const text of entry.request.texts) {
|
|
10838
11091
|
const embedding = await _context.getEmbeddingFor(text);
|
|
10839
11092
|
const vector = Array.from(embedding.vector);
|
|
11093
|
+
embedding.vector = null;
|
|
10840
11094
|
if (vector.length !== EMBEDDING_DIM) {
|
|
10841
11095
|
throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
|
|
10842
11096
|
}
|
|
@@ -10882,6 +11136,11 @@ function checkIdle() {
|
|
|
10882
11136
|
}
|
|
10883
11137
|
async function shutdown() {
|
|
10884
11138
|
resetIdleTimer();
|
|
11139
|
+
try {
|
|
11140
|
+
const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
11141
|
+
disposeShards2();
|
|
11142
|
+
} catch {
|
|
11143
|
+
}
|
|
10885
11144
|
if (_context) {
|
|
10886
11145
|
try {
|
|
10887
11146
|
await _context.dispose();
|
|
@@ -10920,6 +11179,7 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10920
11179
|
}
|
|
10921
11180
|
}
|
|
10922
11181
|
const dbConnected = _storeInitialized;
|
|
11182
|
+
const mem = process.memoryUsage();
|
|
10923
11183
|
sendResponse(socket, {
|
|
10924
11184
|
id: requestId,
|
|
10925
11185
|
...healthy && testOk ? {
|
|
@@ -10927,7 +11187,13 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10927
11187
|
status: "ok",
|
|
10928
11188
|
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
10929
11189
|
requests_served: _requestsServed,
|
|
10930
|
-
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
|
|
11190
|
+
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed },
|
|
11191
|
+
memory: {
|
|
11192
|
+
rss_mb: Math.round(mem.rss / 1024 / 1024),
|
|
11193
|
+
heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
|
|
11194
|
+
external_mb: Math.round(mem.external / 1024 / 1024),
|
|
11195
|
+
array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
|
|
11196
|
+
}
|
|
10931
11197
|
}
|
|
10932
11198
|
} : { error: "unhealthy: model not loaded or test embed failed" }
|
|
10933
11199
|
});
|
|
@@ -10981,13 +11247,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
|
|
|
10981
11247
|
});
|
|
10982
11248
|
}
|
|
10983
11249
|
}
|
|
11250
|
+
var _ingestCount = 0;
|
|
11251
|
+
async function handleIngest(req) {
|
|
11252
|
+
try {
|
|
11253
|
+
if (!await ensureStoreForPolling()) return;
|
|
11254
|
+
if (!req.rawText || req.rawText.length < 50) return;
|
|
11255
|
+
let vectorBlob = null;
|
|
11256
|
+
if (_context) {
|
|
11257
|
+
try {
|
|
11258
|
+
const embedding = await _context.getEmbeddingFor(req.rawText);
|
|
11259
|
+
const vector = Array.from(embedding.vector);
|
|
11260
|
+
embedding.vector = null;
|
|
11261
|
+
if (vector.length === EMBEDDING_DIM) {
|
|
11262
|
+
const { vectorToBlob: vectorToBlob2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
11263
|
+
vectorBlob = vectorToBlob2(vector);
|
|
11264
|
+
}
|
|
11265
|
+
} catch {
|
|
11266
|
+
}
|
|
11267
|
+
}
|
|
11268
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
11269
|
+
const client = getClient2();
|
|
11270
|
+
const { randomUUID: randomUUID5 } = await import("crypto");
|
|
11271
|
+
const id = randomUUID5();
|
|
11272
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11273
|
+
await client.execute({
|
|
11274
|
+
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)
|
|
11275
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
|
|
11276
|
+
args: [
|
|
11277
|
+
id,
|
|
11278
|
+
req.agentId,
|
|
11279
|
+
req.agentRole,
|
|
11280
|
+
req.sessionId,
|
|
11281
|
+
now,
|
|
11282
|
+
req.toolName,
|
|
11283
|
+
req.projectName,
|
|
11284
|
+
req.hasError ? 1 : 0,
|
|
11285
|
+
req.rawText,
|
|
11286
|
+
vectorBlob,
|
|
11287
|
+
req.taskId ?? null,
|
|
11288
|
+
req.confidence ?? 0.7,
|
|
11289
|
+
req.draft ? 1 : 0,
|
|
11290
|
+
req.trajectory ? JSON.stringify(req.trajectory) : null
|
|
11291
|
+
]
|
|
11292
|
+
});
|
|
11293
|
+
_ingestCount++;
|
|
11294
|
+
} catch (err) {
|
|
11295
|
+
process.stderr.write(`[exed] Ingest error: ${err instanceof Error ? err.message : String(err)}
|
|
11296
|
+
`);
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
10984
11299
|
function startServer() {
|
|
10985
|
-
mkdirSync9(
|
|
11300
|
+
mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
|
|
11301
|
+
try {
|
|
11302
|
+
chmodSync2(path24.dirname(SOCKET_PATH2), 448);
|
|
11303
|
+
} catch {
|
|
11304
|
+
}
|
|
11305
|
+
_daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
|
|
10986
11306
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
10987
|
-
const oldPath =
|
|
11307
|
+
const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
|
|
10988
11308
|
try {
|
|
10989
11309
|
if (oldFile.endsWith(".pid")) {
|
|
10990
|
-
const pid = parseInt(
|
|
11310
|
+
const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
|
|
10991
11311
|
if (pid > 0) try {
|
|
10992
11312
|
process.kill(pid, "SIGKILL");
|
|
10993
11313
|
} catch {
|
|
@@ -11019,6 +11339,10 @@ function startServer() {
|
|
|
11019
11339
|
if (!line) continue;
|
|
11020
11340
|
try {
|
|
11021
11341
|
const request = JSON.parse(line);
|
|
11342
|
+
if (!request.token || request.token !== _daemonToken) {
|
|
11343
|
+
sendResponse(socket, { id: request.id ?? "unauthorized", error: "Unauthorized daemon request" });
|
|
11344
|
+
continue;
|
|
11345
|
+
}
|
|
11022
11346
|
if (request.type === "health") {
|
|
11023
11347
|
void handleHealthCheck(socket, request.id ?? "health");
|
|
11024
11348
|
continue;
|
|
@@ -11039,6 +11363,10 @@ function startServer() {
|
|
|
11039
11363
|
void handleDbBatch(socket, request.id, request.statements, request.mode);
|
|
11040
11364
|
continue;
|
|
11041
11365
|
}
|
|
11366
|
+
if (request.type === "ingest") {
|
|
11367
|
+
void handleIngest(request);
|
|
11368
|
+
continue;
|
|
11369
|
+
}
|
|
11042
11370
|
if (!request.id || !Array.isArray(request.texts)) {
|
|
11043
11371
|
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
|
|
11044
11372
|
continue;
|
|
@@ -11076,7 +11404,15 @@ function startServer() {
|
|
|
11076
11404
|
server.listen(SOCKET_PATH2, () => {
|
|
11077
11405
|
process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
|
|
11078
11406
|
`);
|
|
11079
|
-
|
|
11407
|
+
try {
|
|
11408
|
+
chmodSync2(SOCKET_PATH2, 384);
|
|
11409
|
+
} catch {
|
|
11410
|
+
}
|
|
11411
|
+
writeFileSync11(PID_PATH2, String(process.pid));
|
|
11412
|
+
try {
|
|
11413
|
+
chmodSync2(PID_PATH2, 384);
|
|
11414
|
+
} catch {
|
|
11415
|
+
}
|
|
11080
11416
|
checkIdle();
|
|
11081
11417
|
});
|
|
11082
11418
|
}
|
|
@@ -11369,7 +11705,7 @@ function startWikiSync() {
|
|
|
11369
11705
|
});
|
|
11370
11706
|
}
|
|
11371
11707
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
11372
|
-
var AGENT_STATS_PATH =
|
|
11708
|
+
var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
|
|
11373
11709
|
async function writeAgentStats() {
|
|
11374
11710
|
if (!await ensureStoreForPolling()) return;
|
|
11375
11711
|
try {
|
|
@@ -11427,7 +11763,7 @@ async function writeAgentStats() {
|
|
|
11427
11763
|
pid: process.pid
|
|
11428
11764
|
}
|
|
11429
11765
|
};
|
|
11430
|
-
|
|
11766
|
+
writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
11431
11767
|
} catch (err) {
|
|
11432
11768
|
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
11433
11769
|
`);
|
|
@@ -11499,12 +11835,12 @@ function startIntercomQueueDrain() {
|
|
|
11499
11835
|
const hasInProgressTask = (session) => {
|
|
11500
11836
|
try {
|
|
11501
11837
|
const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
|
|
11502
|
-
const
|
|
11503
|
-
const { existsSync:
|
|
11504
|
-
const
|
|
11838
|
+
const path25 = __require("path");
|
|
11839
|
+
const { existsSync: existsSync21 } = __require("fs");
|
|
11840
|
+
const os16 = __require("os");
|
|
11505
11841
|
const agent = ban(session.split("-")[0] ?? session);
|
|
11506
|
-
const markerPath =
|
|
11507
|
-
return
|
|
11842
|
+
const markerPath = path25.join(os16.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
|
|
11843
|
+
return existsSync21(markerPath);
|
|
11508
11844
|
} catch {
|
|
11509
11845
|
return false;
|
|
11510
11846
|
}
|
|
@@ -11593,12 +11929,44 @@ function startAutoWake() {
|
|
|
11593
11929
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
11594
11930
|
`);
|
|
11595
11931
|
}
|
|
11932
|
+
var TOTAL_MEM_GB = os15.totalmem() / 1024 ** 3;
|
|
11933
|
+
var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : 1024 ** 3);
|
|
11934
|
+
var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : 2 * 1024 ** 3);
|
|
11935
|
+
var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
11936
|
+
var _rssWarned = false;
|
|
11937
|
+
function startRssWatchdog() {
|
|
11938
|
+
const tick = () => {
|
|
11939
|
+
const rss = process.memoryUsage.rss();
|
|
11940
|
+
if (rss > RSS_RESTART_BYTES) {
|
|
11941
|
+
process.stderr.write(
|
|
11942
|
+
`[exed] RSS CRITICAL: ${(rss / 1024 / 1024).toFixed(0)} MB exceeds ${(RSS_RESTART_BYTES / 1024 ** 3).toFixed(1)} GB limit \u2014 restarting.
|
|
11943
|
+
`
|
|
11944
|
+
);
|
|
11945
|
+
void shutdown();
|
|
11946
|
+
return;
|
|
11947
|
+
}
|
|
11948
|
+
if (rss > RSS_WARN_BYTES && !_rssWarned) {
|
|
11949
|
+
_rssWarned = true;
|
|
11950
|
+
const heap = process.memoryUsage();
|
|
11951
|
+
process.stderr.write(
|
|
11952
|
+
`[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)
|
|
11953
|
+
`
|
|
11954
|
+
);
|
|
11955
|
+
} else if (rss < RSS_WARN_BYTES && _rssWarned) {
|
|
11956
|
+
_rssWarned = false;
|
|
11957
|
+
}
|
|
11958
|
+
};
|
|
11959
|
+
const timer = setInterval(tick, RSS_CHECK_INTERVAL_MS);
|
|
11960
|
+
timer.unref();
|
|
11961
|
+
process.stderr.write(`[exed] RSS watchdog started (warn: ${(RSS_WARN_BYTES / 1024 ** 3).toFixed(1)} GB, restart: ${(RSS_RESTART_BYTES / 1024 ** 3).toFixed(1)} GB)
|
|
11962
|
+
`);
|
|
11963
|
+
}
|
|
11596
11964
|
process.on("SIGINT", () => void shutdown());
|
|
11597
11965
|
process.on("SIGTERM", () => void shutdown());
|
|
11598
11966
|
function checkExistingDaemon() {
|
|
11599
11967
|
try {
|
|
11600
|
-
if (!
|
|
11601
|
-
const pid = parseInt(
|
|
11968
|
+
if (!existsSync20(PID_PATH2)) return false;
|
|
11969
|
+
const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
|
|
11602
11970
|
if (!pid || isNaN(pid)) return false;
|
|
11603
11971
|
process.kill(pid, 0);
|
|
11604
11972
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -11686,6 +12054,7 @@ try {
|
|
|
11686
12054
|
startIntercomQueueDrain();
|
|
11687
12055
|
startConfidenceDecay();
|
|
11688
12056
|
startAutoUpdateCheck();
|
|
12057
|
+
startRssWatchdog();
|
|
11689
12058
|
try {
|
|
11690
12059
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
11691
12060
|
const config = await loadConfig2();
|