@askexenow/exe-os 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +222 -49
- package/dist/bin/backfill-responses.js +221 -48
- package/dist/bin/backfill-vectors.js +225 -52
- package/dist/bin/cleanup-stale-review-tasks.js +150 -28
- package/dist/bin/cli.js +1295 -856
- package/dist/bin/exe-agent-config.js +36 -8
- package/dist/bin/exe-agent.js +14 -4
- package/dist/bin/exe-assign.js +221 -48
- package/dist/bin/exe-boot.js +778 -427
- package/dist/bin/exe-call.js +41 -13
- package/dist/bin/exe-cloud.js +163 -58
- package/dist/bin/exe-dispatch.js +276 -139
- package/dist/bin/exe-doctor.js +145 -27
- package/dist/bin/exe-export-behaviors.js +141 -23
- package/dist/bin/exe-forget.js +137 -19
- package/dist/bin/exe-gateway.js +677 -388
- package/dist/bin/exe-heartbeat.js +227 -108
- package/dist/bin/exe-kill.js +138 -20
- package/dist/bin/exe-launch-agent.js +172 -39
- package/dist/bin/exe-link.js +291 -100
- package/dist/bin/exe-new-employee.js +214 -106
- package/dist/bin/exe-pending-messages.js +395 -33
- package/dist/bin/exe-pending-notifications.js +684 -99
- package/dist/bin/exe-pending-reviews.js +420 -74
- package/dist/bin/exe-rename.js +147 -49
- package/dist/bin/exe-review.js +138 -20
- package/dist/bin/exe-search.js +240 -69
- package/dist/bin/exe-session-cleanup.js +440 -250
- package/dist/bin/exe-settings.js +61 -17
- package/dist/bin/exe-start-codex.js +158 -39
- package/dist/bin/exe-start-opencode.js +157 -38
- package/dist/bin/exe-status.js +151 -29
- package/dist/bin/exe-team.js +138 -20
- package/dist/bin/git-sweep.js +404 -212
- package/dist/bin/graph-backfill.js +137 -19
- package/dist/bin/graph-export.js +140 -22
- package/dist/bin/install.js +90 -61
- package/dist/bin/scan-tasks.js +412 -220
- package/dist/bin/setup.js +564 -293
- package/dist/bin/shard-migrate.js +139 -21
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +137 -19
- package/dist/gateway/index.js +533 -320
- package/dist/hooks/bug-report-worker.js +344 -193
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +402 -210
- package/dist/hooks/error-recall.js +245 -74
- package/dist/hooks/exe-heartbeat-hook.js +16 -6
- package/dist/hooks/ingest-worker.js +3423 -3157
- package/dist/hooks/ingest.js +832 -97
- package/dist/hooks/instructions-loaded.js +227 -54
- package/dist/hooks/notification.js +216 -43
- package/dist/hooks/post-compact.js +239 -62
- package/dist/hooks/pre-compact.js +408 -216
- package/dist/hooks/pre-tool-use.js +268 -90
- package/dist/hooks/prompt-ingest-worker.js +352 -102
- package/dist/hooks/prompt-submit.js +541 -328
- package/dist/hooks/response-ingest-worker.js +372 -122
- package/dist/hooks/session-end.js +443 -240
- package/dist/hooks/session-start.js +313 -127
- package/dist/hooks/stop.js +293 -98
- package/dist/hooks/subagent-stop.js +239 -62
- package/dist/hooks/summary-worker.js +568 -236
- package/dist/index.js +538 -324
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +284 -105
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +16 -6
- package/dist/lib/database.js +123 -25
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +123 -25
- package/dist/lib/device-registry.js +133 -35
- package/dist/lib/embedder.js +107 -32
- package/dist/lib/employee-templates.js +14 -4
- package/dist/lib/employees.js +41 -13
- package/dist/lib/exe-daemon-client.js +88 -22
- package/dist/lib/exe-daemon.js +935 -587
- package/dist/lib/hybrid-search.js +240 -69
- package/dist/lib/identity.js +18 -8
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +116 -56
- package/dist/lib/reminders.js +14 -4
- package/dist/lib/schedules.js +137 -19
- package/dist/lib/skill-learning.js +33 -6
- package/dist/lib/store.js +137 -19
- package/dist/lib/task-router.js +14 -4
- package/dist/lib/tasks.js +280 -234
- package/dist/lib/tmux-routing.js +172 -125
- package/dist/lib/token-spend.js +26 -8
- package/dist/mcp/server.js +1326 -609
- package/dist/mcp/tools/complete-reminder.js +14 -4
- package/dist/mcp/tools/create-reminder.js +14 -4
- package/dist/mcp/tools/create-task.js +306 -248
- package/dist/mcp/tools/deactivate-behavior.js +16 -6
- package/dist/mcp/tools/list-reminders.js +14 -4
- package/dist/mcp/tools/list-tasks.js +123 -107
- package/dist/mcp/tools/send-message.js +75 -29
- package/dist/mcp/tools/update-task.js +1848 -199
- package/dist/runtime/index.js +441 -248
- package/dist/tui/App.js +761 -424
- package/package.json +1 -1
package/dist/lib/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({
|
|
@@ -3742,12 +3871,12 @@ var init_state_bus = __esm({
|
|
|
3742
3871
|
});
|
|
3743
3872
|
|
|
3744
3873
|
// src/lib/tasks-crud.ts
|
|
3745
|
-
import
|
|
3746
|
-
import
|
|
3747
|
-
import
|
|
3874
|
+
import crypto4 from "crypto";
|
|
3875
|
+
import path12 from "path";
|
|
3876
|
+
import os9 from "os";
|
|
3748
3877
|
import { execSync as execSync5 } from "child_process";
|
|
3749
3878
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
3750
|
-
import { existsSync as
|
|
3879
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3751
3880
|
async function writeCheckpoint(input) {
|
|
3752
3881
|
const client = getClient();
|
|
3753
3882
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3863,7 +3992,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3863
3992
|
}
|
|
3864
3993
|
async function createTaskCore(input) {
|
|
3865
3994
|
const client = getClient();
|
|
3866
|
-
const id =
|
|
3995
|
+
const id = crypto4.randomUUID();
|
|
3867
3996
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3868
3997
|
const slug = slugify(input.title);
|
|
3869
3998
|
let earlySessionScope = null;
|
|
@@ -3922,8 +4051,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3922
4051
|
}
|
|
3923
4052
|
if (input.baseDir) {
|
|
3924
4053
|
try {
|
|
3925
|
-
await mkdir3(
|
|
3926
|
-
await mkdir3(
|
|
4054
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4055
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3927
4056
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3928
4057
|
await ensureGitignoreExe(input.baseDir);
|
|
3929
4058
|
} catch {
|
|
@@ -3959,13 +4088,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3959
4088
|
});
|
|
3960
4089
|
if (input.baseDir) {
|
|
3961
4090
|
try {
|
|
3962
|
-
const EXE_OS_DIR =
|
|
3963
|
-
const mdPath =
|
|
3964
|
-
const mdDir =
|
|
3965
|
-
if (!
|
|
4091
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
4092
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
4093
|
+
const mdDir = path12.dirname(mdPath);
|
|
4094
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3966
4095
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3967
4096
|
const mdContent = `# ${input.title}
|
|
3968
4097
|
|
|
4098
|
+
## MANDATORY: When done
|
|
4099
|
+
|
|
4100
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4101
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4102
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4103
|
+
|
|
3969
4104
|
**ID:** ${id}
|
|
3970
4105
|
**Status:** ${initialStatus}
|
|
3971
4106
|
**Priority:** ${input.priority}
|
|
@@ -3979,12 +4114,6 @@ ${laneWarning}` : laneWarning;
|
|
|
3979
4114
|
## Context
|
|
3980
4115
|
|
|
3981
4116
|
${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
4117
|
`;
|
|
3989
4118
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3990
4119
|
} catch (err) {
|
|
@@ -4233,7 +4362,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4233
4362
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4234
4363
|
} catch {
|
|
4235
4364
|
}
|
|
4236
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4365
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4237
4366
|
try {
|
|
4238
4367
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4239
4368
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4262,9 +4391,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4262
4391
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4263
4392
|
}
|
|
4264
4393
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4265
|
-
const archPath =
|
|
4394
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4266
4395
|
try {
|
|
4267
|
-
if (
|
|
4396
|
+
if (existsSync12(archPath)) return;
|
|
4268
4397
|
const template = [
|
|
4269
4398
|
`# ${projectName} \u2014 System Architecture`,
|
|
4270
4399
|
"",
|
|
@@ -4297,10 +4426,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4297
4426
|
}
|
|
4298
4427
|
}
|
|
4299
4428
|
async function ensureGitignoreExe(baseDir) {
|
|
4300
|
-
const gitignorePath =
|
|
4429
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
4301
4430
|
try {
|
|
4302
|
-
if (
|
|
4303
|
-
const content =
|
|
4431
|
+
if (existsSync12(gitignorePath)) {
|
|
4432
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
4304
4433
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4305
4434
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4306
4435
|
} else {
|
|
@@ -4343,8 +4472,8 @@ __export(tasks_review_exports, {
|
|
|
4343
4472
|
isStale: () => isStale,
|
|
4344
4473
|
listPendingReviews: () => listPendingReviews
|
|
4345
4474
|
});
|
|
4346
|
-
import
|
|
4347
|
-
import { existsSync as
|
|
4475
|
+
import path13 from "path";
|
|
4476
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
4348
4477
|
function formatAge(isoTimestamp) {
|
|
4349
4478
|
if (!isoTimestamp) return "";
|
|
4350
4479
|
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
@@ -4362,54 +4491,38 @@ function isStale(isoTimestamp) {
|
|
|
4362
4491
|
}
|
|
4363
4492
|
async function countPendingReviews(sessionScope) {
|
|
4364
4493
|
const client = getClient();
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
args: [sessionScope]
|
|
4369
|
-
});
|
|
4370
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4371
|
-
}
|
|
4494
|
+
const scope = strictSessionScopeFilter(
|
|
4495
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4496
|
+
);
|
|
4372
4497
|
const result = await client.execute({
|
|
4373
|
-
sql:
|
|
4374
|
-
|
|
4498
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4499
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4500
|
+
args: [...scope.args]
|
|
4375
4501
|
});
|
|
4376
4502
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4377
4503
|
}
|
|
4378
4504
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4379
4505
|
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
|
-
}
|
|
4506
|
+
const scope = strictSessionScopeFilter(
|
|
4507
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4508
|
+
);
|
|
4389
4509
|
const result = await client.execute({
|
|
4390
4510
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4391
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4392
|
-
args: [sinceIso]
|
|
4511
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4512
|
+
args: [sinceIso, ...scope.args]
|
|
4393
4513
|
});
|
|
4394
4514
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4395
4515
|
}
|
|
4396
4516
|
async function listPendingReviews(limit, sessionScope) {
|
|
4397
4517
|
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
|
-
}
|
|
4518
|
+
const scope = strictSessionScopeFilter(
|
|
4519
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4520
|
+
);
|
|
4408
4521
|
const result = await client.execute({
|
|
4409
4522
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4410
|
-
WHERE status = 'needs_review'
|
|
4523
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4411
4524
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4412
|
-
args: [limit]
|
|
4525
|
+
args: [...scope.args, limit]
|
|
4413
4526
|
});
|
|
4414
4527
|
return result.rows;
|
|
4415
4528
|
}
|
|
@@ -4421,7 +4534,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4421
4534
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4422
4535
|
AND assigned_by = 'system'
|
|
4423
4536
|
AND title LIKE 'Review:%'
|
|
4424
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4537
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4425
4538
|
args: [now]
|
|
4426
4539
|
});
|
|
4427
4540
|
const r1b = await client.execute({
|
|
@@ -4629,11 +4742,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4629
4742
|
);
|
|
4630
4743
|
}
|
|
4631
4744
|
try {
|
|
4632
|
-
const cacheDir =
|
|
4633
|
-
if (
|
|
4745
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4746
|
+
if (existsSync13(cacheDir)) {
|
|
4634
4747
|
for (const f of readdirSync2(cacheDir)) {
|
|
4635
4748
|
if (f.startsWith("review-notified-")) {
|
|
4636
|
-
unlinkSync4(
|
|
4749
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
4637
4750
|
}
|
|
4638
4751
|
}
|
|
4639
4752
|
}
|
|
@@ -4650,11 +4763,12 @@ var init_tasks_review = __esm({
|
|
|
4650
4763
|
init_tmux_routing();
|
|
4651
4764
|
init_session_key();
|
|
4652
4765
|
init_state_bus();
|
|
4766
|
+
init_task_scope();
|
|
4653
4767
|
}
|
|
4654
4768
|
});
|
|
4655
4769
|
|
|
4656
4770
|
// src/lib/tasks-chain.ts
|
|
4657
|
-
import
|
|
4771
|
+
import path14 from "path";
|
|
4658
4772
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
4659
4773
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4660
4774
|
const client = getClient();
|
|
@@ -4671,7 +4785,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4671
4785
|
});
|
|
4672
4786
|
for (const ur of unblockedRows.rows) {
|
|
4673
4787
|
try {
|
|
4674
|
-
const ubFile =
|
|
4788
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
4675
4789
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
4676
4790
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4677
4791
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4706,7 +4820,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4706
4820
|
const scScope = sessionScopeFilter();
|
|
4707
4821
|
const remaining = await client.execute({
|
|
4708
4822
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4709
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4823
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4710
4824
|
args: [parentTaskId, ...scScope.args]
|
|
4711
4825
|
});
|
|
4712
4826
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4740,7 +4854,7 @@ var init_tasks_chain = __esm({
|
|
|
4740
4854
|
|
|
4741
4855
|
// src/lib/project-name.ts
|
|
4742
4856
|
import { execSync as execSync6 } from "child_process";
|
|
4743
|
-
import
|
|
4857
|
+
import path15 from "path";
|
|
4744
4858
|
function getProjectName(cwd) {
|
|
4745
4859
|
const dir = cwd ?? process.cwd();
|
|
4746
4860
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4753,7 +4867,7 @@ function getProjectName(cwd) {
|
|
|
4753
4867
|
timeout: 2e3,
|
|
4754
4868
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4755
4869
|
}).trim();
|
|
4756
|
-
repoRoot =
|
|
4870
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
4757
4871
|
} catch {
|
|
4758
4872
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4759
4873
|
cwd: dir,
|
|
@@ -4762,11 +4876,11 @@ function getProjectName(cwd) {
|
|
|
4762
4876
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4763
4877
|
}).trim();
|
|
4764
4878
|
}
|
|
4765
|
-
_cached2 =
|
|
4879
|
+
_cached2 = path15.basename(repoRoot);
|
|
4766
4880
|
_cachedCwd = dir;
|
|
4767
4881
|
return _cached2;
|
|
4768
4882
|
} catch {
|
|
4769
|
-
_cached2 =
|
|
4883
|
+
_cached2 = path15.basename(dir);
|
|
4770
4884
|
_cachedCwd = dir;
|
|
4771
4885
|
return _cached2;
|
|
4772
4886
|
}
|
|
@@ -4909,10 +5023,10 @@ var init_tasks_notify = __esm({
|
|
|
4909
5023
|
});
|
|
4910
5024
|
|
|
4911
5025
|
// src/lib/behaviors.ts
|
|
4912
|
-
import
|
|
5026
|
+
import crypto5 from "crypto";
|
|
4913
5027
|
async function storeBehavior(opts) {
|
|
4914
5028
|
const client = getClient();
|
|
4915
|
-
const id =
|
|
5029
|
+
const id = crypto5.randomUUID();
|
|
4916
5030
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4917
5031
|
await client.execute({
|
|
4918
5032
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4941,7 +5055,7 @@ __export(skill_learning_exports, {
|
|
|
4941
5055
|
storeTrajectory: () => storeTrajectory,
|
|
4942
5056
|
sweepTrajectories: () => sweepTrajectories
|
|
4943
5057
|
});
|
|
4944
|
-
import
|
|
5058
|
+
import crypto6 from "crypto";
|
|
4945
5059
|
async function extractTrajectory(taskId, agentId) {
|
|
4946
5060
|
const client = getClient();
|
|
4947
5061
|
const result = await client.execute({
|
|
@@ -4970,11 +5084,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4970
5084
|
return signature;
|
|
4971
5085
|
}
|
|
4972
5086
|
function hashSignature(signature) {
|
|
4973
|
-
return
|
|
5087
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4974
5088
|
}
|
|
4975
5089
|
async function storeTrajectory(opts) {
|
|
4976
5090
|
const client = getClient();
|
|
4977
|
-
const id =
|
|
5091
|
+
const id = crypto6.randomUUID();
|
|
4978
5092
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4979
5093
|
const signatureHash = hashSignature(opts.signature);
|
|
4980
5094
|
await client.execute({
|
|
@@ -5239,8 +5353,8 @@ __export(tasks_exports, {
|
|
|
5239
5353
|
updateTaskStatus: () => updateTaskStatus,
|
|
5240
5354
|
writeCheckpoint: () => writeCheckpoint
|
|
5241
5355
|
});
|
|
5242
|
-
import
|
|
5243
|
-
import { writeFileSync as
|
|
5356
|
+
import path16 from "path";
|
|
5357
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5244
5358
|
async function createTask(input) {
|
|
5245
5359
|
const result = await createTaskCore(input);
|
|
5246
5360
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5259,12 +5373,12 @@ async function updateTask(input) {
|
|
|
5259
5373
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5260
5374
|
try {
|
|
5261
5375
|
const agent = String(row.assigned_to);
|
|
5262
|
-
const cacheDir =
|
|
5263
|
-
const cachePath =
|
|
5376
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5377
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
5264
5378
|
if (input.status === "in_progress") {
|
|
5265
5379
|
mkdirSync5(cacheDir, { recursive: true });
|
|
5266
|
-
|
|
5267
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5380
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5381
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5268
5382
|
try {
|
|
5269
5383
|
unlinkSync5(cachePath);
|
|
5270
5384
|
} catch {
|
|
@@ -5272,10 +5386,10 @@ async function updateTask(input) {
|
|
|
5272
5386
|
}
|
|
5273
5387
|
} catch {
|
|
5274
5388
|
}
|
|
5275
|
-
if (input.status === "done") {
|
|
5389
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5276
5390
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5277
5391
|
}
|
|
5278
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5392
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5279
5393
|
try {
|
|
5280
5394
|
const client = getClient();
|
|
5281
5395
|
const taskTitle = String(row.title);
|
|
@@ -5291,7 +5405,7 @@ async function updateTask(input) {
|
|
|
5291
5405
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5292
5406
|
try {
|
|
5293
5407
|
const draftClient = getClient();
|
|
5294
|
-
if (input.status === "done") {
|
|
5408
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5295
5409
|
await draftClient.execute({
|
|
5296
5410
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5297
5411
|
args: [assignedAgent]
|
|
@@ -5308,7 +5422,7 @@ async function updateTask(input) {
|
|
|
5308
5422
|
try {
|
|
5309
5423
|
const client = getClient();
|
|
5310
5424
|
const cascaded = await client.execute({
|
|
5311
|
-
sql: `UPDATE tasks SET status = '
|
|
5425
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5312
5426
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5313
5427
|
args: [now, taskId]
|
|
5314
5428
|
});
|
|
@@ -5321,14 +5435,14 @@ async function updateTask(input) {
|
|
|
5321
5435
|
} catch {
|
|
5322
5436
|
}
|
|
5323
5437
|
}
|
|
5324
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
5438
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5325
5439
|
if (isTerminal) {
|
|
5326
5440
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5327
5441
|
if (!isCoordinator) {
|
|
5328
5442
|
notifyTaskDone();
|
|
5329
5443
|
}
|
|
5330
5444
|
await markTaskNotificationsRead(taskFile);
|
|
5331
|
-
if (input.status === "done") {
|
|
5445
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5332
5446
|
try {
|
|
5333
5447
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5334
5448
|
} catch {
|
|
@@ -5348,7 +5462,7 @@ async function updateTask(input) {
|
|
|
5348
5462
|
}
|
|
5349
5463
|
}
|
|
5350
5464
|
}
|
|
5351
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5465
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5352
5466
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5353
5467
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5354
5468
|
taskId,
|
|
@@ -5720,6 +5834,7 @@ __export(tmux_routing_exports, {
|
|
|
5720
5834
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5721
5835
|
isExeSession: () => isExeSession,
|
|
5722
5836
|
isSessionBusy: () => isSessionBusy,
|
|
5837
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5723
5838
|
notifyParentExe: () => notifyParentExe,
|
|
5724
5839
|
parseParentExe: () => parseParentExe,
|
|
5725
5840
|
registerParentExe: () => registerParentExe,
|
|
@@ -5730,13 +5845,13 @@ __export(tmux_routing_exports, {
|
|
|
5730
5845
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5731
5846
|
});
|
|
5732
5847
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5733
|
-
import { readFileSync as
|
|
5734
|
-
import
|
|
5735
|
-
import
|
|
5848
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5849
|
+
import path17 from "path";
|
|
5850
|
+
import os10 from "os";
|
|
5736
5851
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5737
5852
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5738
5853
|
function spawnLockPath(sessionName) {
|
|
5739
|
-
return
|
|
5854
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5740
5855
|
}
|
|
5741
5856
|
function isProcessAlive(pid) {
|
|
5742
5857
|
try {
|
|
@@ -5747,13 +5862,13 @@ function isProcessAlive(pid) {
|
|
|
5747
5862
|
}
|
|
5748
5863
|
}
|
|
5749
5864
|
function acquireSpawnLock2(sessionName) {
|
|
5750
|
-
if (!
|
|
5865
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
5751
5866
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
5752
5867
|
}
|
|
5753
5868
|
const lockFile = spawnLockPath(sessionName);
|
|
5754
|
-
if (
|
|
5869
|
+
if (existsSync14(lockFile)) {
|
|
5755
5870
|
try {
|
|
5756
|
-
const lock = JSON.parse(
|
|
5871
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5757
5872
|
const age = Date.now() - lock.timestamp;
|
|
5758
5873
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5759
5874
|
return false;
|
|
@@ -5761,7 +5876,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5761
5876
|
} catch {
|
|
5762
5877
|
}
|
|
5763
5878
|
}
|
|
5764
|
-
|
|
5879
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5765
5880
|
return true;
|
|
5766
5881
|
}
|
|
5767
5882
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5773,13 +5888,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5773
5888
|
function resolveBehaviorsExporterScript() {
|
|
5774
5889
|
try {
|
|
5775
5890
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5776
|
-
const scriptPath =
|
|
5777
|
-
|
|
5891
|
+
const scriptPath = path17.join(
|
|
5892
|
+
path17.dirname(thisFile),
|
|
5778
5893
|
"..",
|
|
5779
5894
|
"bin",
|
|
5780
5895
|
"exe-export-behaviors.js"
|
|
5781
5896
|
);
|
|
5782
|
-
return
|
|
5897
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
5783
5898
|
} catch {
|
|
5784
5899
|
return null;
|
|
5785
5900
|
}
|
|
@@ -5845,12 +5960,12 @@ function extractRootExe(name) {
|
|
|
5845
5960
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5846
5961
|
}
|
|
5847
5962
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5848
|
-
if (!
|
|
5963
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
5849
5964
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5850
5965
|
}
|
|
5851
5966
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5852
|
-
const filePath =
|
|
5853
|
-
|
|
5967
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5968
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5854
5969
|
parentExe: rootExe,
|
|
5855
5970
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5856
5971
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5858,7 +5973,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5858
5973
|
}
|
|
5859
5974
|
function getParentExe(sessionKey) {
|
|
5860
5975
|
try {
|
|
5861
|
-
const data = JSON.parse(
|
|
5976
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5862
5977
|
return data.parentExe || null;
|
|
5863
5978
|
} catch {
|
|
5864
5979
|
return null;
|
|
@@ -5866,8 +5981,8 @@ function getParentExe(sessionKey) {
|
|
|
5866
5981
|
}
|
|
5867
5982
|
function getDispatchedBy(sessionKey) {
|
|
5868
5983
|
try {
|
|
5869
|
-
const data = JSON.parse(
|
|
5870
|
-
|
|
5984
|
+
const data = JSON.parse(readFileSync12(
|
|
5985
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5871
5986
|
"utf8"
|
|
5872
5987
|
));
|
|
5873
5988
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5937,8 +6052,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5937
6052
|
}
|
|
5938
6053
|
function readDebounceState() {
|
|
5939
6054
|
try {
|
|
5940
|
-
if (!
|
|
5941
|
-
const raw = JSON.parse(
|
|
6055
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
6056
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5942
6057
|
const state = {};
|
|
5943
6058
|
for (const [key, val] of Object.entries(raw)) {
|
|
5944
6059
|
if (typeof val === "number") {
|
|
@@ -5954,8 +6069,8 @@ function readDebounceState() {
|
|
|
5954
6069
|
}
|
|
5955
6070
|
function writeDebounceState(state) {
|
|
5956
6071
|
try {
|
|
5957
|
-
if (!
|
|
5958
|
-
|
|
6072
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
6073
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5959
6074
|
} catch {
|
|
5960
6075
|
}
|
|
5961
6076
|
}
|
|
@@ -6053,8 +6168,8 @@ function sendIntercom(targetSession) {
|
|
|
6053
6168
|
try {
|
|
6054
6169
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6055
6170
|
const agent = baseAgentName(rawAgent);
|
|
6056
|
-
const markerPath =
|
|
6057
|
-
if (
|
|
6171
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6172
|
+
if (existsSync14(markerPath)) {
|
|
6058
6173
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
6059
6174
|
return "debounced";
|
|
6060
6175
|
}
|
|
@@ -6063,8 +6178,8 @@ function sendIntercom(targetSession) {
|
|
|
6063
6178
|
try {
|
|
6064
6179
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6065
6180
|
const agent = baseAgentName(rawAgent);
|
|
6066
|
-
const taskDir =
|
|
6067
|
-
if (
|
|
6181
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
6182
|
+
if (existsSync14(taskDir)) {
|
|
6068
6183
|
const files = readdirSync3(taskDir).filter(
|
|
6069
6184
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
6070
6185
|
);
|
|
@@ -6124,6 +6239,21 @@ function notifyParentExe(sessionKey) {
|
|
|
6124
6239
|
}
|
|
6125
6240
|
return true;
|
|
6126
6241
|
}
|
|
6242
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
6243
|
+
const transport = getTransport();
|
|
6244
|
+
try {
|
|
6245
|
+
const sessions = transport.listSessions();
|
|
6246
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
6247
|
+
execSync7(
|
|
6248
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
6249
|
+
{ timeout: 3e3 }
|
|
6250
|
+
);
|
|
6251
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
6252
|
+
return true;
|
|
6253
|
+
} catch {
|
|
6254
|
+
return false;
|
|
6255
|
+
}
|
|
6256
|
+
}
|
|
6127
6257
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6128
6258
|
if (isCoordinatorName(employeeName)) {
|
|
6129
6259
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6197,26 +6327,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6197
6327
|
const transport = getTransport();
|
|
6198
6328
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6199
6329
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6200
|
-
const logDir =
|
|
6201
|
-
const logFile =
|
|
6202
|
-
if (!
|
|
6330
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
6331
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
6332
|
+
if (!existsSync14(logDir)) {
|
|
6203
6333
|
mkdirSync6(logDir, { recursive: true });
|
|
6204
6334
|
}
|
|
6205
6335
|
transport.kill(sessionName);
|
|
6206
6336
|
let cleanupSuffix = "";
|
|
6207
6337
|
try {
|
|
6208
6338
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6209
|
-
const cleanupScript =
|
|
6210
|
-
if (
|
|
6339
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
6340
|
+
if (existsSync14(cleanupScript)) {
|
|
6211
6341
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6212
6342
|
}
|
|
6213
6343
|
} catch {
|
|
6214
6344
|
}
|
|
6215
6345
|
try {
|
|
6216
|
-
const claudeJsonPath =
|
|
6346
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
6217
6347
|
let claudeJson = {};
|
|
6218
6348
|
try {
|
|
6219
|
-
claudeJson = JSON.parse(
|
|
6349
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6220
6350
|
} catch {
|
|
6221
6351
|
}
|
|
6222
6352
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6224,17 +6354,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6224
6354
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6225
6355
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6226
6356
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6227
|
-
|
|
6357
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6228
6358
|
} catch {
|
|
6229
6359
|
}
|
|
6230
6360
|
try {
|
|
6231
|
-
const settingsDir =
|
|
6361
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
6232
6362
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6233
|
-
const projSettingsDir =
|
|
6234
|
-
const settingsPath =
|
|
6363
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
6364
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
6235
6365
|
let settings = {};
|
|
6236
6366
|
try {
|
|
6237
|
-
settings = JSON.parse(
|
|
6367
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6238
6368
|
} catch {
|
|
6239
6369
|
}
|
|
6240
6370
|
const perms = settings.permissions ?? {};
|
|
@@ -6263,7 +6393,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6263
6393
|
perms.allow = allow;
|
|
6264
6394
|
settings.permissions = perms;
|
|
6265
6395
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
6266
|
-
|
|
6396
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6267
6397
|
}
|
|
6268
6398
|
} catch {
|
|
6269
6399
|
}
|
|
@@ -6278,8 +6408,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6278
6408
|
let behaviorsFlag = "";
|
|
6279
6409
|
let legacyFallbackWarned = false;
|
|
6280
6410
|
if (!useExeAgent && !useBinSymlink) {
|
|
6281
|
-
const identityPath =
|
|
6282
|
-
|
|
6411
|
+
const identityPath = path17.join(
|
|
6412
|
+
os10.homedir(),
|
|
6283
6413
|
".exe-os",
|
|
6284
6414
|
"identity",
|
|
6285
6415
|
`${employeeName}.md`
|
|
@@ -6288,13 +6418,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6288
6418
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6289
6419
|
if (hasAgentFlag) {
|
|
6290
6420
|
identityFlag = ` --agent ${employeeName}`;
|
|
6291
|
-
} else if (
|
|
6421
|
+
} else if (existsSync14(identityPath)) {
|
|
6292
6422
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6293
6423
|
legacyFallbackWarned = true;
|
|
6294
6424
|
}
|
|
6295
6425
|
const behaviorsFile = exportBehaviorsSync(
|
|
6296
6426
|
employeeName,
|
|
6297
|
-
|
|
6427
|
+
path17.basename(spawnCwd),
|
|
6298
6428
|
sessionName
|
|
6299
6429
|
);
|
|
6300
6430
|
if (behaviorsFile) {
|
|
@@ -6309,16 +6439,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6309
6439
|
}
|
|
6310
6440
|
let sessionContextFlag = "";
|
|
6311
6441
|
try {
|
|
6312
|
-
const ctxDir =
|
|
6442
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
6313
6443
|
mkdirSync6(ctxDir, { recursive: true });
|
|
6314
|
-
const ctxFile =
|
|
6444
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6315
6445
|
const ctxContent = [
|
|
6316
6446
|
`## Session Context`,
|
|
6317
6447
|
`You are running in tmux session: ${sessionName}.`,
|
|
6318
6448
|
`Your parent coordinator session is ${exeSession}.`,
|
|
6319
6449
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
6320
6450
|
].join("\n");
|
|
6321
|
-
|
|
6451
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6322
6452
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6323
6453
|
} catch {
|
|
6324
6454
|
}
|
|
@@ -6395,8 +6525,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6395
6525
|
transport.pipeLog(sessionName, logFile);
|
|
6396
6526
|
try {
|
|
6397
6527
|
const mySession = getMySession();
|
|
6398
|
-
const dispatchInfo =
|
|
6399
|
-
|
|
6528
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6529
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
6400
6530
|
dispatchedBy: mySession,
|
|
6401
6531
|
rootExe: exeSession,
|
|
6402
6532
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -6470,15 +6600,15 @@ var init_tmux_routing = __esm({
|
|
|
6470
6600
|
init_intercom_queue();
|
|
6471
6601
|
init_plan_limits();
|
|
6472
6602
|
init_employees();
|
|
6473
|
-
SPAWN_LOCK_DIR =
|
|
6474
|
-
SESSION_CACHE =
|
|
6603
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6604
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
6475
6605
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6476
6606
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
6477
6607
|
VERIFY_PANE_LINES = 200;
|
|
6478
6608
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
6479
6609
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
6480
|
-
INTERCOM_LOG2 =
|
|
6481
|
-
DEBOUNCE_FILE =
|
|
6610
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6611
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6482
6612
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6483
6613
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
6484
6614
|
}
|
|
@@ -6762,9 +6892,9 @@ __export(agent_signals_exports, {
|
|
|
6762
6892
|
hasOpenTasks: () => hasOpenTasks,
|
|
6763
6893
|
hasUnreadInbox: () => hasUnreadInbox
|
|
6764
6894
|
});
|
|
6765
|
-
import { readFileSync as
|
|
6766
|
-
import
|
|
6767
|
-
import
|
|
6895
|
+
import { readFileSync as readFileSync13, existsSync as existsSync15 } from "fs";
|
|
6896
|
+
import os11 from "os";
|
|
6897
|
+
import path18 from "path";
|
|
6768
6898
|
async function hasOpenTasks(client, agentId) {
|
|
6769
6899
|
try {
|
|
6770
6900
|
const scope = sessionScopeFilter(null);
|
|
@@ -6806,10 +6936,10 @@ async function hasUnreadInbox(client, agentId) {
|
|
|
6806
6936
|
return CONSERVATIVE_ON_ERROR;
|
|
6807
6937
|
}
|
|
6808
6938
|
}
|
|
6809
|
-
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog =
|
|
6810
|
-
if (!
|
|
6939
|
+
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path18.join(os11.homedir(), ".exe-os", "intercom.log")) {
|
|
6940
|
+
if (!existsSync15(intercomLog)) return false;
|
|
6811
6941
|
try {
|
|
6812
|
-
const raw =
|
|
6942
|
+
const raw = readFileSync13(intercomLog, "utf8");
|
|
6813
6943
|
const lines = raw.split("\n");
|
|
6814
6944
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6815
6945
|
const line = lines[i];
|
|
@@ -6882,7 +7012,7 @@ __export(daemon_orchestration_exports, {
|
|
|
6882
7012
|
shouldNudgeEmployee: () => shouldNudgeEmployee
|
|
6883
7013
|
});
|
|
6884
7014
|
import { execSync as execSync9 } from "child_process";
|
|
6885
|
-
import { existsSync as
|
|
7015
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
6886
7016
|
import { homedir } from "os";
|
|
6887
7017
|
import { join } from "path";
|
|
6888
7018
|
function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
|
|
@@ -7073,8 +7203,8 @@ async function pollReviewNudge(deps, state) {
|
|
|
7073
7203
|
function loadNudgeState() {
|
|
7074
7204
|
const state = { lastNudge: /* @__PURE__ */ new Map() };
|
|
7075
7205
|
try {
|
|
7076
|
-
if (!
|
|
7077
|
-
const raw = JSON.parse(
|
|
7206
|
+
if (!existsSync16(NUDGE_STATE_PATH)) return state;
|
|
7207
|
+
const raw = JSON.parse(readFileSync14(NUDGE_STATE_PATH, "utf8"));
|
|
7078
7208
|
if (Array.isArray(raw)) {
|
|
7079
7209
|
for (const [key, val] of raw) {
|
|
7080
7210
|
if (key && typeof val?.at === "number" && typeof val?.count === "number") {
|
|
@@ -7088,7 +7218,7 @@ function loadNudgeState() {
|
|
|
7088
7218
|
}
|
|
7089
7219
|
function saveNudgeState(state) {
|
|
7090
7220
|
const entries = Array.from(state.lastNudge.entries());
|
|
7091
|
-
|
|
7221
|
+
writeFileSync9(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
|
|
7092
7222
|
}
|
|
7093
7223
|
function createReviewNudgeRealDeps(getClient2) {
|
|
7094
7224
|
return {
|
|
@@ -7426,157 +7556,38 @@ var init_daemon_orchestration = __esm({
|
|
|
7426
7556
|
}
|
|
7427
7557
|
});
|
|
7428
7558
|
|
|
7429
|
-
// src/lib/
|
|
7430
|
-
var
|
|
7431
|
-
__export(
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7559
|
+
// src/lib/shard-manager.ts
|
|
7560
|
+
var shard_manager_exports = {};
|
|
7561
|
+
__export(shard_manager_exports, {
|
|
7562
|
+
disposeShards: () => disposeShards,
|
|
7563
|
+
ensureShardSchema: () => ensureShardSchema,
|
|
7564
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
7565
|
+
getReadyShardClient: () => getReadyShardClient,
|
|
7566
|
+
getShardClient: () => getShardClient,
|
|
7567
|
+
getShardsDir: () => getShardsDir,
|
|
7568
|
+
initShardManager: () => initShardManager,
|
|
7569
|
+
isShardingEnabled: () => isShardingEnabled,
|
|
7570
|
+
listShards: () => listShards,
|
|
7571
|
+
shardExists: () => shardExists
|
|
7437
7572
|
});
|
|
7438
|
-
import
|
|
7439
|
-
import { existsSync as
|
|
7440
|
-
import
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7573
|
+
import path19 from "path";
|
|
7574
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
7575
|
+
import { createClient as createClient2 } from "@libsql/client";
|
|
7576
|
+
function initShardManager(encryptionKey) {
|
|
7577
|
+
_encryptionKey = encryptionKey;
|
|
7578
|
+
if (!existsSync17(SHARDS_DIR)) {
|
|
7579
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
7580
|
+
}
|
|
7581
|
+
_shardingEnabled = true;
|
|
7582
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
7583
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
7584
|
+
_evictionTimer.unref();
|
|
7444
7585
|
}
|
|
7445
|
-
function
|
|
7446
|
-
return
|
|
7586
|
+
function isShardingEnabled() {
|
|
7587
|
+
return _shardingEnabled;
|
|
7447
7588
|
}
|
|
7448
|
-
|
|
7449
|
-
|
|
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
|
-
// src/lib/shard-manager.ts
|
|
7553
|
-
var shard_manager_exports = {};
|
|
7554
|
-
__export(shard_manager_exports, {
|
|
7555
|
-
disposeShards: () => disposeShards,
|
|
7556
|
-
ensureShardSchema: () => ensureShardSchema,
|
|
7557
|
-
getReadyShardClient: () => getReadyShardClient,
|
|
7558
|
-
getShardClient: () => getShardClient,
|
|
7559
|
-
getShardsDir: () => getShardsDir,
|
|
7560
|
-
initShardManager: () => initShardManager,
|
|
7561
|
-
isShardingEnabled: () => isShardingEnabled,
|
|
7562
|
-
listShards: () => listShards,
|
|
7563
|
-
shardExists: () => shardExists
|
|
7564
|
-
});
|
|
7565
|
-
import path19 from "path";
|
|
7566
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
7567
|
-
import { createClient as createClient2 } from "@libsql/client";
|
|
7568
|
-
function initShardManager(encryptionKey) {
|
|
7569
|
-
_encryptionKey = encryptionKey;
|
|
7570
|
-
if (!existsSync16(SHARDS_DIR)) {
|
|
7571
|
-
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
7572
|
-
}
|
|
7573
|
-
_shardingEnabled = true;
|
|
7574
|
-
}
|
|
7575
|
-
function isShardingEnabled() {
|
|
7576
|
-
return _shardingEnabled;
|
|
7577
|
-
}
|
|
7578
|
-
function getShardsDir() {
|
|
7579
|
-
return SHARDS_DIR;
|
|
7589
|
+
function getShardsDir() {
|
|
7590
|
+
return SHARDS_DIR;
|
|
7580
7591
|
}
|
|
7581
7592
|
function getShardClient(projectName) {
|
|
7582
7593
|
if (!_encryptionKey) {
|
|
@@ -7587,21 +7598,28 @@ function getShardClient(projectName) {
|
|
|
7587
7598
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
7588
7599
|
}
|
|
7589
7600
|
const cached = _shards.get(safeName);
|
|
7590
|
-
if (cached)
|
|
7601
|
+
if (cached) {
|
|
7602
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
7603
|
+
return cached;
|
|
7604
|
+
}
|
|
7605
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
7606
|
+
evictLRU();
|
|
7607
|
+
}
|
|
7591
7608
|
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
7592
7609
|
const client = createClient2({
|
|
7593
7610
|
url: `file:${dbPath}`,
|
|
7594
7611
|
encryptionKey: _encryptionKey
|
|
7595
7612
|
});
|
|
7596
7613
|
_shards.set(safeName, client);
|
|
7614
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
7597
7615
|
return client;
|
|
7598
7616
|
}
|
|
7599
7617
|
function shardExists(projectName) {
|
|
7600
7618
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7601
|
-
return
|
|
7619
|
+
return existsSync17(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
7602
7620
|
}
|
|
7603
7621
|
function listShards() {
|
|
7604
|
-
if (!
|
|
7622
|
+
if (!existsSync17(SHARDS_DIR)) return [];
|
|
7605
7623
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
7606
7624
|
}
|
|
7607
7625
|
async function ensureShardSchema(client) {
|
|
@@ -7653,6 +7671,8 @@ async function ensureShardSchema(client) {
|
|
|
7653
7671
|
for (const col of [
|
|
7654
7672
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
7655
7673
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
7674
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
7675
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
7656
7676
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
7657
7677
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
7658
7678
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -7790,26 +7810,197 @@ async function getReadyShardClient(projectName) {
|
|
|
7790
7810
|
await ensureShardSchema(client);
|
|
7791
7811
|
return client;
|
|
7792
7812
|
}
|
|
7813
|
+
function evictLRU() {
|
|
7814
|
+
let oldest = null;
|
|
7815
|
+
let oldestTime = Infinity;
|
|
7816
|
+
for (const [name, time] of _shardLastAccess) {
|
|
7817
|
+
if (time < oldestTime) {
|
|
7818
|
+
oldestTime = time;
|
|
7819
|
+
oldest = name;
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
if (oldest) {
|
|
7823
|
+
const client = _shards.get(oldest);
|
|
7824
|
+
if (client) {
|
|
7825
|
+
client.close();
|
|
7826
|
+
}
|
|
7827
|
+
_shards.delete(oldest);
|
|
7828
|
+
_shardLastAccess.delete(oldest);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
function evictIdleShards() {
|
|
7832
|
+
const now = Date.now();
|
|
7833
|
+
const toEvict = [];
|
|
7834
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
7835
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
7836
|
+
toEvict.push(name);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
for (const name of toEvict) {
|
|
7840
|
+
const client = _shards.get(name);
|
|
7841
|
+
if (client) {
|
|
7842
|
+
client.close();
|
|
7843
|
+
}
|
|
7844
|
+
_shards.delete(name);
|
|
7845
|
+
_shardLastAccess.delete(name);
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
function getOpenShardCount() {
|
|
7849
|
+
return _shards.size;
|
|
7850
|
+
}
|
|
7793
7851
|
function disposeShards() {
|
|
7852
|
+
if (_evictionTimer) {
|
|
7853
|
+
clearInterval(_evictionTimer);
|
|
7854
|
+
_evictionTimer = null;
|
|
7855
|
+
}
|
|
7794
7856
|
for (const [, client] of _shards) {
|
|
7795
7857
|
client.close();
|
|
7796
7858
|
}
|
|
7797
7859
|
_shards.clear();
|
|
7860
|
+
_shardLastAccess.clear();
|
|
7798
7861
|
_shardingEnabled = false;
|
|
7799
7862
|
_encryptionKey = null;
|
|
7800
7863
|
}
|
|
7801
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
7864
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
7802
7865
|
var init_shard_manager = __esm({
|
|
7803
7866
|
"src/lib/shard-manager.ts"() {
|
|
7804
7867
|
"use strict";
|
|
7805
7868
|
init_config();
|
|
7806
7869
|
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
7870
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
7871
|
+
MAX_OPEN_SHARDS = 10;
|
|
7872
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
7807
7873
|
_shards = /* @__PURE__ */ new Map();
|
|
7874
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
7875
|
+
_evictionTimer = null;
|
|
7808
7876
|
_encryptionKey = null;
|
|
7809
7877
|
_shardingEnabled = false;
|
|
7810
7878
|
}
|
|
7811
7879
|
});
|
|
7812
7880
|
|
|
7881
|
+
// src/lib/keychain.ts
|
|
7882
|
+
var keychain_exports = {};
|
|
7883
|
+
__export(keychain_exports, {
|
|
7884
|
+
deleteMasterKey: () => deleteMasterKey,
|
|
7885
|
+
exportMnemonic: () => exportMnemonic,
|
|
7886
|
+
getMasterKey: () => getMasterKey,
|
|
7887
|
+
importMnemonic: () => importMnemonic,
|
|
7888
|
+
setMasterKey: () => setMasterKey
|
|
7889
|
+
});
|
|
7890
|
+
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7891
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7892
|
+
import path20 from "path";
|
|
7893
|
+
import os12 from "os";
|
|
7894
|
+
function getKeyDir() {
|
|
7895
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
|
|
7896
|
+
}
|
|
7897
|
+
function getKeyPath() {
|
|
7898
|
+
return path20.join(getKeyDir(), "master.key");
|
|
7899
|
+
}
|
|
7900
|
+
async function tryKeytar() {
|
|
7901
|
+
try {
|
|
7902
|
+
return await import("keytar");
|
|
7903
|
+
} catch {
|
|
7904
|
+
return null;
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
async function getMasterKey() {
|
|
7908
|
+
const keytar = await tryKeytar();
|
|
7909
|
+
if (keytar) {
|
|
7910
|
+
try {
|
|
7911
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
7912
|
+
if (stored) {
|
|
7913
|
+
return Buffer.from(stored, "base64");
|
|
7914
|
+
}
|
|
7915
|
+
} catch {
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
const keyPath = getKeyPath();
|
|
7919
|
+
if (!existsSync18(keyPath)) {
|
|
7920
|
+
process.stderr.write(
|
|
7921
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7922
|
+
`
|
|
7923
|
+
);
|
|
7924
|
+
return null;
|
|
7925
|
+
}
|
|
7926
|
+
try {
|
|
7927
|
+
const content = await readFile4(keyPath, "utf-8");
|
|
7928
|
+
return Buffer.from(content.trim(), "base64");
|
|
7929
|
+
} catch (err) {
|
|
7930
|
+
process.stderr.write(
|
|
7931
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
7932
|
+
`
|
|
7933
|
+
);
|
|
7934
|
+
return null;
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
async function setMasterKey(key) {
|
|
7938
|
+
const b64 = key.toString("base64");
|
|
7939
|
+
const keytar = await tryKeytar();
|
|
7940
|
+
if (keytar) {
|
|
7941
|
+
try {
|
|
7942
|
+
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
7943
|
+
return;
|
|
7944
|
+
} catch {
|
|
7945
|
+
}
|
|
7946
|
+
}
|
|
7947
|
+
const dir = getKeyDir();
|
|
7948
|
+
await mkdir4(dir, { recursive: true });
|
|
7949
|
+
const keyPath = getKeyPath();
|
|
7950
|
+
await writeFile5(keyPath, b64 + "\n", "utf-8");
|
|
7951
|
+
await chmod2(keyPath, 384);
|
|
7952
|
+
}
|
|
7953
|
+
async function deleteMasterKey() {
|
|
7954
|
+
const keytar = await tryKeytar();
|
|
7955
|
+
if (keytar) {
|
|
7956
|
+
try {
|
|
7957
|
+
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
7958
|
+
} catch {
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7961
|
+
const keyPath = getKeyPath();
|
|
7962
|
+
if (existsSync18(keyPath)) {
|
|
7963
|
+
await unlink(keyPath);
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
async function loadBip39() {
|
|
7967
|
+
try {
|
|
7968
|
+
return await import("bip39");
|
|
7969
|
+
} catch {
|
|
7970
|
+
throw new Error(
|
|
7971
|
+
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
7972
|
+
);
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
async function exportMnemonic(key) {
|
|
7976
|
+
if (key.length !== 32) {
|
|
7977
|
+
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
7978
|
+
}
|
|
7979
|
+
const { entropyToMnemonic } = await loadBip39();
|
|
7980
|
+
return entropyToMnemonic(key.toString("hex"));
|
|
7981
|
+
}
|
|
7982
|
+
async function importMnemonic(mnemonic) {
|
|
7983
|
+
const trimmed = mnemonic.trim();
|
|
7984
|
+
const words = trimmed.split(/\s+/);
|
|
7985
|
+
if (words.length !== 24) {
|
|
7986
|
+
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
7987
|
+
}
|
|
7988
|
+
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
7989
|
+
if (!validateMnemonic(trimmed)) {
|
|
7990
|
+
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
7991
|
+
}
|
|
7992
|
+
const entropy = mnemonicToEntropy(trimmed);
|
|
7993
|
+
return Buffer.from(entropy, "hex");
|
|
7994
|
+
}
|
|
7995
|
+
var SERVICE, ACCOUNT;
|
|
7996
|
+
var init_keychain = __esm({
|
|
7997
|
+
"src/lib/keychain.ts"() {
|
|
7998
|
+
"use strict";
|
|
7999
|
+
SERVICE = "exe-mem";
|
|
8000
|
+
ACCOUNT = "master-key";
|
|
8001
|
+
}
|
|
8002
|
+
});
|
|
8003
|
+
|
|
7813
8004
|
// src/lib/platform-procedures.ts
|
|
7814
8005
|
var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
|
|
7815
8006
|
var init_platform_procedures = __esm({
|
|
@@ -8582,15 +8773,15 @@ async function pollPendingReviews(deps, state) {
|
|
|
8582
8773
|
return [];
|
|
8583
8774
|
}
|
|
8584
8775
|
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
8776
|
const sent = [];
|
|
8593
8777
|
for (const exeSession of sessions) {
|
|
8778
|
+
let reviewCount = 0;
|
|
8779
|
+
try {
|
|
8780
|
+
reviewCount = await deps.countPendingReviews(exeSession);
|
|
8781
|
+
} catch {
|
|
8782
|
+
continue;
|
|
8783
|
+
}
|
|
8784
|
+
if (reviewCount === 0) continue;
|
|
8594
8785
|
const lastSent = state.lastIntercomSent.get(exeSession) ?? 0;
|
|
8595
8786
|
if (Date.now() - lastSent < state.intervalMs) continue;
|
|
8596
8787
|
try {
|
|
@@ -8600,15 +8791,17 @@ async function pollPendingReviews(deps, state) {
|
|
|
8600
8791
|
} catch {
|
|
8601
8792
|
}
|
|
8602
8793
|
}
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8794
|
+
for (const exeSession of sessions) {
|
|
8795
|
+
try {
|
|
8796
|
+
const orphans = await deps.findOrphanedDoneTasks(exeSession);
|
|
8797
|
+
for (const orphan of orphans) {
|
|
8798
|
+
try {
|
|
8799
|
+
await deps.createReviewForOrphan(orphan);
|
|
8800
|
+
} catch {
|
|
8801
|
+
}
|
|
8609
8802
|
}
|
|
8803
|
+
} catch {
|
|
8610
8804
|
}
|
|
8611
|
-
} catch {
|
|
8612
8805
|
}
|
|
8613
8806
|
if (deps.findStaleTasks && deps.sendNudge) {
|
|
8614
8807
|
try {
|
|
@@ -8626,50 +8819,56 @@ async function pollPendingReviews(deps, state) {
|
|
|
8626
8819
|
}
|
|
8627
8820
|
}
|
|
8628
8821
|
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
|
-
`
|
|
8822
|
+
for (const exeSession of sessions) {
|
|
8823
|
+
try {
|
|
8824
|
+
const urgent = await deps.findUrgentUnread(exeSession);
|
|
8825
|
+
for (const msg of urgent) {
|
|
8826
|
+
try {
|
|
8827
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8828
|
+
(s) => s.startsWith(`${msg.target_agent}-`) && s.endsWith(`-${exeSession}`)
|
|
8642
8829
|
);
|
|
8830
|
+
for (const sess of employeeSessions) {
|
|
8831
|
+
deps.sendIntercom(sess);
|
|
8832
|
+
}
|
|
8833
|
+
const ageMs = Date.now() - new Date(msg.created_at).getTime();
|
|
8834
|
+
if (ageMs > 5 * 60 * 1e3) {
|
|
8835
|
+
process.stderr.write(
|
|
8836
|
+
`[exed] WARNING: Urgent message to ${msg.target_agent} unread for ${Math.round(ageMs / 6e4)}min: "${msg.content.slice(0, 80)}"
|
|
8837
|
+
`
|
|
8838
|
+
);
|
|
8839
|
+
}
|
|
8840
|
+
} catch {
|
|
8643
8841
|
}
|
|
8644
|
-
} catch {
|
|
8645
8842
|
}
|
|
8843
|
+
} catch {
|
|
8646
8844
|
}
|
|
8647
|
-
} catch {
|
|
8648
8845
|
}
|
|
8649
8846
|
}
|
|
8650
8847
|
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
|
-
`
|
|
8848
|
+
for (const exeSession of sessions) {
|
|
8849
|
+
try {
|
|
8850
|
+
const unstarted = await deps.findUnstartedTasks(exeSession);
|
|
8851
|
+
for (const task of unstarted) {
|
|
8852
|
+
try {
|
|
8853
|
+
const employeeSessions = deps.listTmuxSessions().filter(
|
|
8854
|
+
(s) => (s.startsWith(`${task.assigned_to}-`) || s.startsWith(`${task.assigned_to}1-`)) && s.endsWith(`-${exeSession}`)
|
|
8667
8855
|
);
|
|
8856
|
+
for (const sess of employeeSessions) {
|
|
8857
|
+
deps.sendIntercom(sess);
|
|
8858
|
+
}
|
|
8859
|
+
const ageMs = Date.now() - new Date(task.created_at).getTime();
|
|
8860
|
+
const UNSTARTED_WARNING_MS = 15 * 60 * 1e3;
|
|
8861
|
+
if (ageMs > UNSTARTED_WARNING_MS) {
|
|
8862
|
+
process.stderr.write(
|
|
8863
|
+
`[exed] WARNING: Task "${task.title}" assigned to ${task.assigned_to} unstarted for ${Math.round(ageMs / 6e4)}min
|
|
8864
|
+
`
|
|
8865
|
+
);
|
|
8866
|
+
}
|
|
8867
|
+
} catch {
|
|
8668
8868
|
}
|
|
8669
|
-
} catch {
|
|
8670
8869
|
}
|
|
8870
|
+
} catch {
|
|
8671
8871
|
}
|
|
8672
|
-
} catch {
|
|
8673
8872
|
}
|
|
8674
8873
|
}
|
|
8675
8874
|
return sent;
|
|
@@ -8682,9 +8881,9 @@ function createRealDeps(getClient2) {
|
|
|
8682
8881
|
timeout: 3e3
|
|
8683
8882
|
}).trim().split("\n").filter(Boolean);
|
|
8684
8883
|
},
|
|
8685
|
-
countPendingReviews: async () => {
|
|
8884
|
+
countPendingReviews: async (sessionScope) => {
|
|
8686
8885
|
const client = getClient2();
|
|
8687
|
-
const rpScope =
|
|
8886
|
+
const rpScope = strictSessionScopeFilter(sessionScope);
|
|
8688
8887
|
const result = await client.execute({
|
|
8689
8888
|
sql: `SELECT COUNT(*) as count FROM tasks
|
|
8690
8889
|
WHERE status = 'needs_review'${rpScope.sql}`,
|
|
@@ -8696,10 +8895,10 @@ function createRealDeps(getClient2) {
|
|
|
8696
8895
|
const { sendIntercom: centralSend } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
|
|
8697
8896
|
centralSend(session);
|
|
8698
8897
|
},
|
|
8699
|
-
findOrphanedDoneTasks: async () => {
|
|
8898
|
+
findOrphanedDoneTasks: async (sessionScope) => {
|
|
8700
8899
|
const client = getClient2();
|
|
8701
8900
|
const coordinatorName = getCoordinatorName();
|
|
8702
|
-
const odScope =
|
|
8901
|
+
const odScope = strictSessionScopeFilter(sessionScope, "t");
|
|
8703
8902
|
const result = await client.execute({
|
|
8704
8903
|
sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
|
|
8705
8904
|
t.project_name, t.task_file, t.result, t.status
|
|
@@ -8724,24 +8923,25 @@ function createRealDeps(getClient2) {
|
|
|
8724
8923
|
process.stderr.write(`[exed] Created missing review for: ${task.title} (${task.assigned_to})
|
|
8725
8924
|
`);
|
|
8726
8925
|
},
|
|
8727
|
-
findUrgentUnread: async () => {
|
|
8926
|
+
findUrgentUnread: async (sessionScope) => {
|
|
8728
8927
|
const client = getClient2();
|
|
8928
|
+
const msgScope = strictSessionScopeFilter(sessionScope);
|
|
8729
8929
|
const result = await client.execute({
|
|
8730
8930
|
sql: `SELECT id, target_agent, content, created_at
|
|
8731
8931
|
FROM messages
|
|
8732
8932
|
WHERE priority = 'urgent'
|
|
8733
8933
|
AND status IN ('pending', 'delivered')
|
|
8734
|
-
AND created_at <= datetime('now', '-2 minutes')
|
|
8934
|
+
AND created_at <= datetime('now', '-2 minutes')${msgScope.sql}
|
|
8735
8935
|
ORDER BY created_at ASC
|
|
8736
8936
|
LIMIT 10`,
|
|
8737
|
-
args: []
|
|
8937
|
+
args: [...msgScope.args]
|
|
8738
8938
|
});
|
|
8739
8939
|
return result.rows;
|
|
8740
8940
|
},
|
|
8741
|
-
findUnstartedTasks: async () => {
|
|
8941
|
+
findUnstartedTasks: async (sessionScope) => {
|
|
8742
8942
|
const client = getClient2();
|
|
8743
8943
|
const coordinatorName = getCoordinatorName();
|
|
8744
|
-
const usScope =
|
|
8944
|
+
const usScope = strictSessionScopeFilter(sessionScope);
|
|
8745
8945
|
const result = await client.execute({
|
|
8746
8946
|
sql: `SELECT id, title, assigned_to, created_at
|
|
8747
8947
|
FROM tasks
|
|
@@ -9176,10 +9376,10 @@ async function disposeEmbedder() {
|
|
|
9176
9376
|
async function embedDirect(text) {
|
|
9177
9377
|
const llamaCpp = await import("node-llama-cpp");
|
|
9178
9378
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9179
|
-
const { existsSync:
|
|
9180
|
-
const
|
|
9181
|
-
const modelPath =
|
|
9182
|
-
if (!
|
|
9379
|
+
const { existsSync: existsSync21 } = await import("fs");
|
|
9380
|
+
const path25 = await import("path");
|
|
9381
|
+
const modelPath = path25.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
9382
|
+
if (!existsSync21(modelPath)) {
|
|
9183
9383
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
9184
9384
|
}
|
|
9185
9385
|
const llama = await llamaCpp.getLlama();
|
|
@@ -9401,13 +9601,13 @@ __export(graph_rag_exports, {
|
|
|
9401
9601
|
resolveAlias: () => resolveAlias,
|
|
9402
9602
|
storeExtraction: () => storeExtraction
|
|
9403
9603
|
});
|
|
9404
|
-
import
|
|
9604
|
+
import crypto7 from "crypto";
|
|
9405
9605
|
function normalizeEntityName(name) {
|
|
9406
9606
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
9407
9607
|
}
|
|
9408
9608
|
function entityId(name, type) {
|
|
9409
9609
|
const normalized = normalizeEntityName(name);
|
|
9410
|
-
return
|
|
9610
|
+
return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
9411
9611
|
}
|
|
9412
9612
|
async function resolveAlias(client, name) {
|
|
9413
9613
|
const normalized = normalizeEntityName(name);
|
|
@@ -9657,7 +9857,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9657
9857
|
const targetAlias = await resolveAlias(client, r.target);
|
|
9658
9858
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
9659
9859
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
9660
|
-
const relId =
|
|
9860
|
+
const relId = crypto7.randomUUID().slice(0, 16);
|
|
9661
9861
|
try {
|
|
9662
9862
|
await client.execute({
|
|
9663
9863
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -9720,7 +9920,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9720
9920
|
}
|
|
9721
9921
|
}
|
|
9722
9922
|
for (const h of extraction.hyperedges) {
|
|
9723
|
-
const hId =
|
|
9923
|
+
const hId = crypto7.randomUUID().slice(0, 16);
|
|
9724
9924
|
try {
|
|
9725
9925
|
await client.execute({
|
|
9726
9926
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -9784,7 +9984,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
9784
9984
|
totalEntities += stored.entitiesStored;
|
|
9785
9985
|
totalRelationships += stored.relationshipsStored;
|
|
9786
9986
|
}
|
|
9787
|
-
const contentHash =
|
|
9987
|
+
const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
9788
9988
|
await client.execute({
|
|
9789
9989
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
9790
9990
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -9901,8 +10101,8 @@ __export(wiki_sync_exports, {
|
|
|
9901
10101
|
listWorkspaces: () => listWorkspaces,
|
|
9902
10102
|
syncMemories: () => syncMemories
|
|
9903
10103
|
});
|
|
9904
|
-
async function wikiRequest(config,
|
|
9905
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
10104
|
+
async function wikiRequest(config, path25, method = "GET", body) {
|
|
10105
|
+
const url = `${config.wikiUrl}/api/v1${path25}`;
|
|
9906
10106
|
const headers = {
|
|
9907
10107
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
9908
10108
|
"Content-Type": "application/json"
|
|
@@ -9914,7 +10114,7 @@ async function wikiRequest(config, path24, method = "GET", body) {
|
|
|
9914
10114
|
signal: AbortSignal.timeout(3e4)
|
|
9915
10115
|
});
|
|
9916
10116
|
if (!response.ok) {
|
|
9917
|
-
throw new Error(`Wiki API ${method} ${
|
|
10117
|
+
throw new Error(`Wiki API ${method} ${path25}: ${response.status} ${response.statusText}`);
|
|
9918
10118
|
}
|
|
9919
10119
|
return response.json();
|
|
9920
10120
|
}
|
|
@@ -10026,8 +10226,8 @@ __export(token_spend_exports, {
|
|
|
10026
10226
|
import { readdir } from "fs/promises";
|
|
10027
10227
|
import { createReadStream } from "fs";
|
|
10028
10228
|
import { createInterface } from "readline";
|
|
10029
|
-
import
|
|
10030
|
-
import
|
|
10229
|
+
import path21 from "path";
|
|
10230
|
+
import os13 from "os";
|
|
10031
10231
|
function getPricing(model) {
|
|
10032
10232
|
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
10033
10233
|
const stripped = model.replace(/-\d{8}$/, "");
|
|
@@ -10039,29 +10239,33 @@ function getPricing(model) {
|
|
|
10039
10239
|
return DEFAULT_PRICING;
|
|
10040
10240
|
}
|
|
10041
10241
|
async function getAgentSpend(period = "7d") {
|
|
10242
|
+
const cached = _spendCache.get(period);
|
|
10243
|
+
if (cached && Date.now() < cached.expires) {
|
|
10244
|
+
return cached.result;
|
|
10245
|
+
}
|
|
10042
10246
|
const cutoff = periodToCutoff(period);
|
|
10043
10247
|
const client = getClient();
|
|
10044
|
-
const
|
|
10248
|
+
const dbResult = await client.execute({
|
|
10045
10249
|
sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
|
|
10046
10250
|
args: [cutoff]
|
|
10047
10251
|
});
|
|
10048
|
-
if (
|
|
10252
|
+
if (dbResult.rows.length === 0) return [];
|
|
10049
10253
|
const sessionAgent = /* @__PURE__ */ new Map();
|
|
10050
|
-
for (const row of
|
|
10254
|
+
for (const row of dbResult.rows) {
|
|
10051
10255
|
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
10052
10256
|
}
|
|
10053
|
-
const claudeDir =
|
|
10257
|
+
const claudeDir = path21.join(os13.homedir(), ".claude", "projects");
|
|
10054
10258
|
let projectDirs = [];
|
|
10055
10259
|
try {
|
|
10056
10260
|
const entries = await readdir(claudeDir);
|
|
10057
|
-
projectDirs = entries.map((e) =>
|
|
10261
|
+
projectDirs = entries.map((e) => path21.join(claudeDir, e));
|
|
10058
10262
|
} catch {
|
|
10059
10263
|
return [];
|
|
10060
10264
|
}
|
|
10061
10265
|
const agentTotals = /* @__PURE__ */ new Map();
|
|
10062
10266
|
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
10063
10267
|
for (const dir of projectDirs) {
|
|
10064
|
-
const jsonlPath =
|
|
10268
|
+
const jsonlPath = path21.join(dir, `${sessionUuid}.jsonl`);
|
|
10065
10269
|
try {
|
|
10066
10270
|
const usage = await extractSessionUsage(jsonlPath);
|
|
10067
10271
|
if (usage.input === 0 && usage.output === 0) continue;
|
|
@@ -10085,7 +10289,7 @@ async function getAgentSpend(period = "7d") {
|
|
|
10085
10289
|
}
|
|
10086
10290
|
}
|
|
10087
10291
|
}
|
|
10088
|
-
|
|
10292
|
+
const result = Array.from(agentTotals.entries()).map(([agentId, t]) => ({
|
|
10089
10293
|
agentId,
|
|
10090
10294
|
inputTokens: t.input,
|
|
10091
10295
|
outputTokens: t.output,
|
|
@@ -10095,6 +10299,8 @@ async function getAgentSpend(period = "7d") {
|
|
|
10095
10299
|
sessions: t.sessions.size,
|
|
10096
10300
|
period
|
|
10097
10301
|
})).sort((a, b) => b.costUSD - a.costUSD);
|
|
10302
|
+
_spendCache.set(period, { result, expires: Date.now() + CACHE_TTL_MS });
|
|
10303
|
+
return result;
|
|
10098
10304
|
}
|
|
10099
10305
|
async function extractSessionUsage(jsonlPath) {
|
|
10100
10306
|
let input = 0;
|
|
@@ -10141,7 +10347,7 @@ function periodToCutoff(period) {
|
|
|
10141
10347
|
const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
|
|
10142
10348
|
return new Date(Date.now() - ms).toISOString();
|
|
10143
10349
|
}
|
|
10144
|
-
var MODEL_PRICING, DEFAULT_PRICING;
|
|
10350
|
+
var MODEL_PRICING, DEFAULT_PRICING, CACHE_TTL_MS, _spendCache;
|
|
10145
10351
|
var init_token_spend = __esm({
|
|
10146
10352
|
"src/lib/token-spend.ts"() {
|
|
10147
10353
|
"use strict";
|
|
@@ -10171,6 +10377,8 @@ var init_token_spend = __esm({
|
|
|
10171
10377
|
"claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
|
|
10172
10378
|
};
|
|
10173
10379
|
DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
|
|
10380
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10381
|
+
_spendCache = /* @__PURE__ */ new Map();
|
|
10174
10382
|
}
|
|
10175
10383
|
});
|
|
10176
10384
|
|
|
@@ -10182,11 +10390,11 @@ __export(update_check_exports, {
|
|
|
10182
10390
|
getRemoteVersion: () => getRemoteVersion
|
|
10183
10391
|
});
|
|
10184
10392
|
import { execSync as execSync11 } from "child_process";
|
|
10185
|
-
import { readFileSync as
|
|
10186
|
-
import
|
|
10393
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
10394
|
+
import path22 from "path";
|
|
10187
10395
|
function getLocalVersion(packageRoot) {
|
|
10188
|
-
const pkgPath =
|
|
10189
|
-
const pkg = JSON.parse(
|
|
10396
|
+
const pkgPath = path22.join(packageRoot, "package.json");
|
|
10397
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
10190
10398
|
return pkg.version;
|
|
10191
10399
|
}
|
|
10192
10400
|
function getRemoteVersion() {
|
|
@@ -10229,16 +10437,16 @@ __export(ws_auth_exports, {
|
|
|
10229
10437
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
10230
10438
|
hashAuthToken: () => hashAuthToken
|
|
10231
10439
|
});
|
|
10232
|
-
import
|
|
10440
|
+
import crypto8 from "crypto";
|
|
10233
10441
|
function deriveWsAuthToken(masterKey) {
|
|
10234
|
-
return Buffer.from(
|
|
10442
|
+
return Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
10235
10443
|
}
|
|
10236
10444
|
function deriveOrgId(masterKey) {
|
|
10237
|
-
const raw = Buffer.from(
|
|
10238
|
-
return
|
|
10445
|
+
const raw = Buffer.from(crypto8.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
10446
|
+
return crypto8.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
10239
10447
|
}
|
|
10240
10448
|
function hashAuthToken(token) {
|
|
10241
|
-
return
|
|
10449
|
+
return crypto8.createHash("sha256").update(token).digest("hex");
|
|
10242
10450
|
}
|
|
10243
10451
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
10244
10452
|
var init_ws_auth = __esm({
|
|
@@ -10256,14 +10464,14 @@ __export(device_registry_exports, {
|
|
|
10256
10464
|
resolveTargetDevice: () => resolveTargetDevice,
|
|
10257
10465
|
setFriendlyName: () => setFriendlyName
|
|
10258
10466
|
});
|
|
10259
|
-
import
|
|
10260
|
-
import
|
|
10261
|
-
import { readFileSync as
|
|
10262
|
-
import
|
|
10467
|
+
import crypto9 from "crypto";
|
|
10468
|
+
import os14 from "os";
|
|
10469
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync19 } from "fs";
|
|
10470
|
+
import path23 from "path";
|
|
10263
10471
|
function getDeviceInfo() {
|
|
10264
|
-
if (
|
|
10472
|
+
if (existsSync19(DEVICE_JSON_PATH)) {
|
|
10265
10473
|
try {
|
|
10266
|
-
const raw =
|
|
10474
|
+
const raw = readFileSync16(DEVICE_JSON_PATH, "utf8");
|
|
10267
10475
|
const data = JSON.parse(raw);
|
|
10268
10476
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
10269
10477
|
return data;
|
|
@@ -10271,20 +10479,20 @@ function getDeviceInfo() {
|
|
|
10271
10479
|
} catch {
|
|
10272
10480
|
}
|
|
10273
10481
|
}
|
|
10274
|
-
const hostname =
|
|
10482
|
+
const hostname = os14.hostname();
|
|
10275
10483
|
const info = {
|
|
10276
|
-
deviceId:
|
|
10484
|
+
deviceId: crypto9.randomUUID(),
|
|
10277
10485
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
10278
10486
|
hostname
|
|
10279
10487
|
};
|
|
10280
|
-
mkdirSync8(
|
|
10281
|
-
|
|
10488
|
+
mkdirSync8(path23.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
10489
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10282
10490
|
return info;
|
|
10283
10491
|
}
|
|
10284
10492
|
function setFriendlyName(name) {
|
|
10285
10493
|
const info = getDeviceInfo();
|
|
10286
10494
|
info.friendlyName = name;
|
|
10287
|
-
|
|
10495
|
+
writeFileSync10(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10288
10496
|
}
|
|
10289
10497
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
10290
10498
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -10318,7 +10526,7 @@ var init_device_registry = __esm({
|
|
|
10318
10526
|
"src/lib/device-registry.ts"() {
|
|
10319
10527
|
"use strict";
|
|
10320
10528
|
init_config();
|
|
10321
|
-
DEVICE_JSON_PATH =
|
|
10529
|
+
DEVICE_JSON_PATH = path23.join(EXE_AI_DIR, "device.json");
|
|
10322
10530
|
}
|
|
10323
10531
|
});
|
|
10324
10532
|
|
|
@@ -10542,10 +10750,10 @@ __export(messaging_exports, {
|
|
|
10542
10750
|
sendMessage: () => sendMessage,
|
|
10543
10751
|
setWsClientSend: () => setWsClientSend
|
|
10544
10752
|
});
|
|
10545
|
-
import
|
|
10753
|
+
import crypto10 from "crypto";
|
|
10546
10754
|
function generateUlid() {
|
|
10547
10755
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
10548
|
-
const random =
|
|
10756
|
+
const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
|
|
10549
10757
|
return (timestamp + random).toUpperCase();
|
|
10550
10758
|
}
|
|
10551
10759
|
function rowToMessage(row) {
|
|
@@ -10556,6 +10764,7 @@ function rowToMessage(row) {
|
|
|
10556
10764
|
targetAgent: row.target_agent,
|
|
10557
10765
|
targetProject: row.target_project ?? null,
|
|
10558
10766
|
targetDevice: row.target_device,
|
|
10767
|
+
sessionScope: row.session_scope ?? null,
|
|
10559
10768
|
content: row.content,
|
|
10560
10769
|
priority: row.priority ?? "normal",
|
|
10561
10770
|
status: row.status ?? "pending",
|
|
@@ -10573,15 +10782,17 @@ async function sendMessage(input) {
|
|
|
10573
10782
|
const id = generateUlid();
|
|
10574
10783
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10575
10784
|
const targetDevice = input.targetDevice ?? "local";
|
|
10785
|
+
const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
|
|
10576
10786
|
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', ?)`,
|
|
10787
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
10788
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
10579
10789
|
args: [
|
|
10580
10790
|
id,
|
|
10581
10791
|
input.fromAgent,
|
|
10582
10792
|
input.targetAgent,
|
|
10583
10793
|
input.targetProject ?? null,
|
|
10584
10794
|
targetDevice,
|
|
10795
|
+
sessionScope,
|
|
10585
10796
|
input.content,
|
|
10586
10797
|
input.priority ?? "normal",
|
|
10587
10798
|
now
|
|
@@ -10595,9 +10806,10 @@ async function sendMessage(input) {
|
|
|
10595
10806
|
}
|
|
10596
10807
|
} catch {
|
|
10597
10808
|
}
|
|
10809
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
10598
10810
|
const result = await client.execute({
|
|
10599
|
-
sql:
|
|
10600
|
-
args: [id]
|
|
10811
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
10812
|
+
args: [id, ...sentScope.args]
|
|
10601
10813
|
});
|
|
10602
10814
|
return rowToMessage(result.rows[0]);
|
|
10603
10815
|
}
|
|
@@ -10621,6 +10833,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
10621
10833
|
fromAgent: msg.fromAgent,
|
|
10622
10834
|
targetAgent: msg.targetAgent,
|
|
10623
10835
|
targetProject: msg.targetProject,
|
|
10836
|
+
sessionScope: msg.sessionScope,
|
|
10624
10837
|
content: msg.content,
|
|
10625
10838
|
priority: msg.priority,
|
|
10626
10839
|
createdAt: msg.createdAt
|
|
@@ -10664,7 +10877,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
10664
10877
|
} catch {
|
|
10665
10878
|
const newRetryCount = msg.retryCount + 1;
|
|
10666
10879
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
10667
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
10880
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
10668
10881
|
} else {
|
|
10669
10882
|
await client.execute({
|
|
10670
10883
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -10674,85 +10887,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
10674
10887
|
return false;
|
|
10675
10888
|
}
|
|
10676
10889
|
}
|
|
10677
|
-
async function getPendingMessages(targetAgent) {
|
|
10890
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
10678
10891
|
const client = getClient();
|
|
10892
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10679
10893
|
const result = await client.execute({
|
|
10680
10894
|
sql: `SELECT * FROM messages
|
|
10681
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
10895
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
10682
10896
|
ORDER BY id`,
|
|
10683
|
-
args: [targetAgent]
|
|
10897
|
+
args: [targetAgent, ...scope.args]
|
|
10684
10898
|
});
|
|
10685
10899
|
return result.rows.map((row) => rowToMessage(row));
|
|
10686
10900
|
}
|
|
10687
|
-
async function markRead(messageId) {
|
|
10901
|
+
async function markRead(messageId, sessionScope) {
|
|
10688
10902
|
const client = getClient();
|
|
10903
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10689
10904
|
await client.execute({
|
|
10690
|
-
sql:
|
|
10691
|
-
|
|
10905
|
+
sql: `UPDATE messages SET status = 'read'
|
|
10906
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
10907
|
+
args: [messageId, ...scope.args]
|
|
10692
10908
|
});
|
|
10693
10909
|
}
|
|
10694
|
-
async function markAcknowledged(messageId) {
|
|
10910
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
10695
10911
|
const client = getClient();
|
|
10912
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10696
10913
|
await client.execute({
|
|
10697
|
-
sql:
|
|
10698
|
-
|
|
10914
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
10915
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
10916
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10699
10917
|
});
|
|
10700
10918
|
}
|
|
10701
|
-
async function markProcessed(messageId) {
|
|
10919
|
+
async function markProcessed(messageId, sessionScope) {
|
|
10702
10920
|
const client = getClient();
|
|
10921
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10703
10922
|
await client.execute({
|
|
10704
|
-
sql:
|
|
10705
|
-
|
|
10923
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
10924
|
+
WHERE id = ?${scope.sql}`,
|
|
10925
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
10706
10926
|
});
|
|
10707
10927
|
}
|
|
10708
|
-
async function getMessageStatus(messageId) {
|
|
10928
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
10709
10929
|
const client = getClient();
|
|
10930
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10710
10931
|
const result = await client.execute({
|
|
10711
|
-
sql:
|
|
10712
|
-
args: [messageId]
|
|
10932
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
10933
|
+
args: [messageId, ...scope.args]
|
|
10713
10934
|
});
|
|
10714
10935
|
return result.rows[0]?.status ?? null;
|
|
10715
10936
|
}
|
|
10716
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
10937
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
10717
10938
|
const client = getClient();
|
|
10939
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10718
10940
|
const result = await client.execute({
|
|
10719
10941
|
sql: `SELECT * FROM messages
|
|
10720
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
10942
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
10721
10943
|
ORDER BY id`,
|
|
10722
|
-
args: [targetAgent]
|
|
10944
|
+
args: [targetAgent, ...scope.args]
|
|
10723
10945
|
});
|
|
10724
10946
|
return result.rows.map((row) => rowToMessage(row));
|
|
10725
10947
|
}
|
|
10726
|
-
async function getReadMessages(targetAgent) {
|
|
10948
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
10727
10949
|
const client = getClient();
|
|
10950
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10728
10951
|
const result = await client.execute({
|
|
10729
|
-
sql:
|
|
10730
|
-
|
|
10952
|
+
sql: `SELECT * FROM messages
|
|
10953
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
10954
|
+
ORDER BY id`,
|
|
10955
|
+
args: [targetAgent, ...scope.args]
|
|
10731
10956
|
});
|
|
10732
10957
|
return result.rows.map((row) => rowToMessage(row));
|
|
10733
10958
|
}
|
|
10734
|
-
async function markFailed(messageId, reason) {
|
|
10959
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
10735
10960
|
const client = getClient();
|
|
10961
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10736
10962
|
await client.execute({
|
|
10737
|
-
sql:
|
|
10738
|
-
|
|
10963
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
10964
|
+
WHERE id = ?${scope.sql}`,
|
|
10965
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
10739
10966
|
});
|
|
10740
10967
|
}
|
|
10741
|
-
async function getFailedMessages() {
|
|
10968
|
+
async function getFailedMessages(sessionScope) {
|
|
10742
10969
|
const client = getClient();
|
|
10970
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10743
10971
|
const result = await client.execute({
|
|
10744
|
-
sql:
|
|
10745
|
-
args: []
|
|
10972
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
10973
|
+
args: [...scope.args]
|
|
10746
10974
|
});
|
|
10747
10975
|
return result.rows.map((row) => rowToMessage(row));
|
|
10748
10976
|
}
|
|
10749
|
-
async function retryPendingMessages() {
|
|
10977
|
+
async function retryPendingMessages(sessionScope) {
|
|
10750
10978
|
const client = getClient();
|
|
10979
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
10751
10980
|
const result = await client.execute({
|
|
10752
10981
|
sql: `SELECT * FROM messages
|
|
10753
|
-
WHERE status = 'pending' AND retry_count <
|
|
10982
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
10754
10983
|
ORDER BY id`,
|
|
10755
|
-
args: [MAX_RETRIES3]
|
|
10984
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
10756
10985
|
});
|
|
10757
10986
|
let delivered = 0;
|
|
10758
10987
|
for (const row of result.rows) {
|
|
@@ -10771,6 +11000,7 @@ var init_messaging = __esm({
|
|
|
10771
11000
|
"use strict";
|
|
10772
11001
|
init_database();
|
|
10773
11002
|
init_tmux_routing();
|
|
11003
|
+
init_task_scope();
|
|
10774
11004
|
MAX_RETRIES3 = 10;
|
|
10775
11005
|
_wsClientSend = null;
|
|
10776
11006
|
}
|
|
@@ -10780,19 +11010,22 @@ var init_messaging = __esm({
|
|
|
10780
11010
|
init_config();
|
|
10781
11011
|
init_memory();
|
|
10782
11012
|
init_daemon_protocol();
|
|
11013
|
+
init_daemon_auth();
|
|
10783
11014
|
init_daemon_orchestration();
|
|
10784
11015
|
import net2 from "net";
|
|
10785
|
-
import { writeFileSync as
|
|
10786
|
-
import
|
|
11016
|
+
import { writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, mkdirSync as mkdirSync9, existsSync as existsSync20, readFileSync as readFileSync17, chmodSync as chmodSync2 } from "fs";
|
|
11017
|
+
import path24 from "path";
|
|
10787
11018
|
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 ??
|
|
11019
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path24.join(EXE_AI_DIR, "exed.sock");
|
|
11020
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path24.join(EXE_AI_DIR, "exed.pid");
|
|
10790
11021
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
10791
11022
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
10792
11023
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
11024
|
+
var DAEMON_TOKEN_ENV2 = "EXE_DAEMON_TOKEN";
|
|
10793
11025
|
var _context = null;
|
|
10794
11026
|
var _model = null;
|
|
10795
11027
|
var _llama = null;
|
|
11028
|
+
var _daemonToken = "";
|
|
10796
11029
|
var MAX_QUEUE_SIZE = 1e3;
|
|
10797
11030
|
var highQueue = [];
|
|
10798
11031
|
var lowQueue = [];
|
|
@@ -10812,8 +11045,8 @@ function enqueue(queue, entry) {
|
|
|
10812
11045
|
queue.push(entry);
|
|
10813
11046
|
}
|
|
10814
11047
|
async function loadModel() {
|
|
10815
|
-
const modelPath =
|
|
10816
|
-
if (!
|
|
11048
|
+
const modelPath = path24.join(MODELS_DIR, MODEL_FILE);
|
|
11049
|
+
if (!existsSync20(modelPath)) {
|
|
10817
11050
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
10818
11051
|
`);
|
|
10819
11052
|
process.exit(1);
|
|
@@ -10837,6 +11070,7 @@ async function processQueue() {
|
|
|
10837
11070
|
for (const text of entry.request.texts) {
|
|
10838
11071
|
const embedding = await _context.getEmbeddingFor(text);
|
|
10839
11072
|
const vector = Array.from(embedding.vector);
|
|
11073
|
+
embedding.vector = null;
|
|
10840
11074
|
if (vector.length !== EMBEDDING_DIM) {
|
|
10841
11075
|
throw new Error(`Dimension mismatch: got ${vector.length}, expected ${EMBEDDING_DIM}`);
|
|
10842
11076
|
}
|
|
@@ -10882,6 +11116,11 @@ function checkIdle() {
|
|
|
10882
11116
|
}
|
|
10883
11117
|
async function shutdown() {
|
|
10884
11118
|
resetIdleTimer();
|
|
11119
|
+
try {
|
|
11120
|
+
const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
11121
|
+
disposeShards2();
|
|
11122
|
+
} catch {
|
|
11123
|
+
}
|
|
10885
11124
|
if (_context) {
|
|
10886
11125
|
try {
|
|
10887
11126
|
await _context.dispose();
|
|
@@ -10920,6 +11159,7 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10920
11159
|
}
|
|
10921
11160
|
}
|
|
10922
11161
|
const dbConnected = _storeInitialized;
|
|
11162
|
+
const mem = process.memoryUsage();
|
|
10923
11163
|
sendResponse(socket, {
|
|
10924
11164
|
id: requestId,
|
|
10925
11165
|
...healthy && testOk ? {
|
|
@@ -10927,7 +11167,13 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
10927
11167
|
status: "ok",
|
|
10928
11168
|
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
10929
11169
|
requests_served: _requestsServed,
|
|
10930
|
-
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
|
|
11170
|
+
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed },
|
|
11171
|
+
memory: {
|
|
11172
|
+
rss_mb: Math.round(mem.rss / 1024 / 1024),
|
|
11173
|
+
heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
|
|
11174
|
+
external_mb: Math.round(mem.external / 1024 / 1024),
|
|
11175
|
+
array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
|
|
11176
|
+
}
|
|
10931
11177
|
}
|
|
10932
11178
|
} : { error: "unhealthy: model not loaded or test embed failed" }
|
|
10933
11179
|
});
|
|
@@ -10981,13 +11227,67 @@ async function handleDbBatch(socket, requestId, statements, mode) {
|
|
|
10981
11227
|
});
|
|
10982
11228
|
}
|
|
10983
11229
|
}
|
|
11230
|
+
var _ingestCount = 0;
|
|
11231
|
+
async function handleIngest(req) {
|
|
11232
|
+
try {
|
|
11233
|
+
if (!await ensureStoreForPolling()) return;
|
|
11234
|
+
if (!req.rawText || req.rawText.length < 50) return;
|
|
11235
|
+
let vectorBlob = null;
|
|
11236
|
+
if (_context) {
|
|
11237
|
+
try {
|
|
11238
|
+
const embedding = await _context.getEmbeddingFor(req.rawText);
|
|
11239
|
+
const vector = Array.from(embedding.vector);
|
|
11240
|
+
embedding.vector = null;
|
|
11241
|
+
if (vector.length === EMBEDDING_DIM) {
|
|
11242
|
+
const { vectorToBlob: vectorToBlob2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
11243
|
+
vectorBlob = vectorToBlob2(vector);
|
|
11244
|
+
}
|
|
11245
|
+
} catch {
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
11249
|
+
const client = getClient2();
|
|
11250
|
+
const { randomUUID: randomUUID5 } = await import("crypto");
|
|
11251
|
+
const id = randomUUID5();
|
|
11252
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11253
|
+
await client.execute({
|
|
11254
|
+
sql: `INSERT INTO memories (id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, vector, task_id, confidence, draft, memory_type, trajectory)
|
|
11255
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
|
|
11256
|
+
args: [
|
|
11257
|
+
id,
|
|
11258
|
+
req.agentId,
|
|
11259
|
+
req.agentRole,
|
|
11260
|
+
req.sessionId,
|
|
11261
|
+
now,
|
|
11262
|
+
req.toolName,
|
|
11263
|
+
req.projectName,
|
|
11264
|
+
req.hasError ? 1 : 0,
|
|
11265
|
+
req.rawText,
|
|
11266
|
+
vectorBlob,
|
|
11267
|
+
req.taskId ?? null,
|
|
11268
|
+
req.confidence ?? 0.7,
|
|
11269
|
+
req.draft ? 1 : 0,
|
|
11270
|
+
req.trajectory ? JSON.stringify(req.trajectory) : null
|
|
11271
|
+
]
|
|
11272
|
+
});
|
|
11273
|
+
_ingestCount++;
|
|
11274
|
+
} catch (err) {
|
|
11275
|
+
process.stderr.write(`[exed] Ingest error: ${err instanceof Error ? err.message : String(err)}
|
|
11276
|
+
`);
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
10984
11279
|
function startServer() {
|
|
10985
|
-
mkdirSync9(
|
|
11280
|
+
mkdirSync9(path24.dirname(SOCKET_PATH2), { recursive: true });
|
|
11281
|
+
try {
|
|
11282
|
+
chmodSync2(path24.dirname(SOCKET_PATH2), 448);
|
|
11283
|
+
} catch {
|
|
11284
|
+
}
|
|
11285
|
+
_daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
|
|
10986
11286
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
10987
|
-
const oldPath =
|
|
11287
|
+
const oldPath = path24.join(path24.dirname(SOCKET_PATH2), oldFile);
|
|
10988
11288
|
try {
|
|
10989
11289
|
if (oldFile.endsWith(".pid")) {
|
|
10990
|
-
const pid = parseInt(
|
|
11290
|
+
const pid = parseInt(readFileSync17(oldPath, "utf8").trim(), 10);
|
|
10991
11291
|
if (pid > 0) try {
|
|
10992
11292
|
process.kill(pid, "SIGKILL");
|
|
10993
11293
|
} catch {
|
|
@@ -11019,6 +11319,10 @@ function startServer() {
|
|
|
11019
11319
|
if (!line) continue;
|
|
11020
11320
|
try {
|
|
11021
11321
|
const request = JSON.parse(line);
|
|
11322
|
+
if (!request.token || request.token !== _daemonToken) {
|
|
11323
|
+
sendResponse(socket, { id: request.id ?? "unauthorized", error: "Unauthorized daemon request" });
|
|
11324
|
+
continue;
|
|
11325
|
+
}
|
|
11022
11326
|
if (request.type === "health") {
|
|
11023
11327
|
void handleHealthCheck(socket, request.id ?? "health");
|
|
11024
11328
|
continue;
|
|
@@ -11039,6 +11343,10 @@ function startServer() {
|
|
|
11039
11343
|
void handleDbBatch(socket, request.id, request.statements, request.mode);
|
|
11040
11344
|
continue;
|
|
11041
11345
|
}
|
|
11346
|
+
if (request.type === "ingest") {
|
|
11347
|
+
void handleIngest(request);
|
|
11348
|
+
continue;
|
|
11349
|
+
}
|
|
11042
11350
|
if (!request.id || !Array.isArray(request.texts)) {
|
|
11043
11351
|
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
|
|
11044
11352
|
continue;
|
|
@@ -11076,7 +11384,15 @@ function startServer() {
|
|
|
11076
11384
|
server.listen(SOCKET_PATH2, () => {
|
|
11077
11385
|
process.stderr.write(`[exed] Listening on ${SOCKET_PATH2}
|
|
11078
11386
|
`);
|
|
11079
|
-
|
|
11387
|
+
try {
|
|
11388
|
+
chmodSync2(SOCKET_PATH2, 384);
|
|
11389
|
+
} catch {
|
|
11390
|
+
}
|
|
11391
|
+
writeFileSync11(PID_PATH2, String(process.pid));
|
|
11392
|
+
try {
|
|
11393
|
+
chmodSync2(PID_PATH2, 384);
|
|
11394
|
+
} catch {
|
|
11395
|
+
}
|
|
11080
11396
|
checkIdle();
|
|
11081
11397
|
});
|
|
11082
11398
|
}
|
|
@@ -11369,7 +11685,7 @@ function startWikiSync() {
|
|
|
11369
11685
|
});
|
|
11370
11686
|
}
|
|
11371
11687
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
11372
|
-
var AGENT_STATS_PATH =
|
|
11688
|
+
var AGENT_STATS_PATH = path24.join(EXE_AI_DIR, "agent-stats.json");
|
|
11373
11689
|
async function writeAgentStats() {
|
|
11374
11690
|
if (!await ensureStoreForPolling()) return;
|
|
11375
11691
|
try {
|
|
@@ -11427,7 +11743,7 @@ async function writeAgentStats() {
|
|
|
11427
11743
|
pid: process.pid
|
|
11428
11744
|
}
|
|
11429
11745
|
};
|
|
11430
|
-
|
|
11746
|
+
writeFileSync11(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
11431
11747
|
} catch (err) {
|
|
11432
11748
|
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
11433
11749
|
`);
|
|
@@ -11499,12 +11815,12 @@ function startIntercomQueueDrain() {
|
|
|
11499
11815
|
const hasInProgressTask = (session) => {
|
|
11500
11816
|
try {
|
|
11501
11817
|
const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
|
|
11502
|
-
const
|
|
11503
|
-
const { existsSync:
|
|
11504
|
-
const
|
|
11818
|
+
const path25 = __require("path");
|
|
11819
|
+
const { existsSync: existsSync21 } = __require("fs");
|
|
11820
|
+
const os15 = __require("os");
|
|
11505
11821
|
const agent = ban(session.split("-")[0] ?? session);
|
|
11506
|
-
const markerPath =
|
|
11507
|
-
return
|
|
11822
|
+
const markerPath = path25.join(os15.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
|
|
11823
|
+
return existsSync21(markerPath);
|
|
11508
11824
|
} catch {
|
|
11509
11825
|
return false;
|
|
11510
11826
|
}
|
|
@@ -11593,12 +11909,43 @@ function startAutoWake() {
|
|
|
11593
11909
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
11594
11910
|
`);
|
|
11595
11911
|
}
|
|
11912
|
+
var RSS_WARN_BYTES = 1024 * 1024 * 1024;
|
|
11913
|
+
var RSS_RESTART_BYTES = 2048 * 1024 * 1024;
|
|
11914
|
+
var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
11915
|
+
var _rssWarned = false;
|
|
11916
|
+
function startRssWatchdog() {
|
|
11917
|
+
const tick = () => {
|
|
11918
|
+
const rss = process.memoryUsage.rss();
|
|
11919
|
+
if (rss > RSS_RESTART_BYTES) {
|
|
11920
|
+
process.stderr.write(
|
|
11921
|
+
`[exed] RSS CRITICAL: ${(rss / 1024 / 1024).toFixed(0)} MB exceeds 2 GB limit \u2014 restarting.
|
|
11922
|
+
`
|
|
11923
|
+
);
|
|
11924
|
+
void shutdown();
|
|
11925
|
+
return;
|
|
11926
|
+
}
|
|
11927
|
+
if (rss > RSS_WARN_BYTES && !_rssWarned) {
|
|
11928
|
+
_rssWarned = true;
|
|
11929
|
+
const heap = process.memoryUsage();
|
|
11930
|
+
process.stderr.write(
|
|
11931
|
+
`[exed] RSS WARNING: ${(rss / 1024 / 1024).toFixed(0)} MB (heap used: ${(heap.heapUsed / 1024 / 1024).toFixed(0)} MB, external: ${(heap.external / 1024 / 1024).toFixed(0)} MB, arrayBuffers: ${(heap.arrayBuffers / 1024 / 1024).toFixed(0)} MB)
|
|
11932
|
+
`
|
|
11933
|
+
);
|
|
11934
|
+
} else if (rss < RSS_WARN_BYTES && _rssWarned) {
|
|
11935
|
+
_rssWarned = false;
|
|
11936
|
+
}
|
|
11937
|
+
};
|
|
11938
|
+
const timer = setInterval(tick, RSS_CHECK_INTERVAL_MS);
|
|
11939
|
+
timer.unref();
|
|
11940
|
+
process.stderr.write(`[exed] RSS watchdog started (warn: 1 GB, restart: 2 GB)
|
|
11941
|
+
`);
|
|
11942
|
+
}
|
|
11596
11943
|
process.on("SIGINT", () => void shutdown());
|
|
11597
11944
|
process.on("SIGTERM", () => void shutdown());
|
|
11598
11945
|
function checkExistingDaemon() {
|
|
11599
11946
|
try {
|
|
11600
|
-
if (!
|
|
11601
|
-
const pid = parseInt(
|
|
11947
|
+
if (!existsSync20(PID_PATH2)) return false;
|
|
11948
|
+
const pid = parseInt(readFileSync17(PID_PATH2, "utf8").trim(), 10);
|
|
11602
11949
|
if (!pid || isNaN(pid)) return false;
|
|
11603
11950
|
process.kill(pid, 0);
|
|
11604
11951
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -11686,6 +12033,7 @@ try {
|
|
|
11686
12033
|
startIntercomQueueDrain();
|
|
11687
12034
|
startConfidenceDecay();
|
|
11688
12035
|
startAutoUpdateCheck();
|
|
12036
|
+
startRssWatchdog();
|
|
11689
12037
|
try {
|
|
11690
12038
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
11691
12039
|
const config = await loadConfig2();
|