@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
|
@@ -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");
|
|
@@ -329,6 +367,120 @@ var init_db_retry = __esm({
|
|
|
329
367
|
}
|
|
330
368
|
});
|
|
331
369
|
|
|
370
|
+
// src/lib/runtime-table.ts
|
|
371
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
372
|
+
var init_runtime_table = __esm({
|
|
373
|
+
"src/lib/runtime-table.ts"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
RUNTIME_TABLE = {
|
|
376
|
+
codex: {
|
|
377
|
+
binary: "codex",
|
|
378
|
+
launchMode: "interactive",
|
|
379
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
380
|
+
inlineFlag: "--no-alt-screen",
|
|
381
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
382
|
+
defaultModel: "gpt-5.4"
|
|
383
|
+
},
|
|
384
|
+
opencode: {
|
|
385
|
+
binary: "opencode",
|
|
386
|
+
launchMode: "exec",
|
|
387
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
388
|
+
inlineFlag: "",
|
|
389
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
390
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
DEFAULT_RUNTIME = "claude";
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// src/lib/agent-config.ts
|
|
398
|
+
var agent_config_exports = {};
|
|
399
|
+
__export(agent_config_exports, {
|
|
400
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
401
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
402
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
403
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
404
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
405
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
406
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
407
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
408
|
+
setAgentRuntime: () => setAgentRuntime
|
|
409
|
+
});
|
|
410
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
411
|
+
import path2 from "path";
|
|
412
|
+
function loadAgentConfig() {
|
|
413
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
414
|
+
try {
|
|
415
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
416
|
+
} catch {
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function saveAgentConfig(config) {
|
|
421
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
422
|
+
ensurePrivateDirSync(dir);
|
|
423
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
424
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
425
|
+
}
|
|
426
|
+
function getAgentRuntime(agentId) {
|
|
427
|
+
const config = loadAgentConfig();
|
|
428
|
+
const entry = config[agentId];
|
|
429
|
+
if (entry) return entry;
|
|
430
|
+
const orgDefault = config["default"];
|
|
431
|
+
if (orgDefault) return orgDefault;
|
|
432
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
433
|
+
}
|
|
434
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
435
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
436
|
+
if (!knownModels) {
|
|
437
|
+
return {
|
|
438
|
+
ok: false,
|
|
439
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (!knownModels.includes(model)) {
|
|
443
|
+
return {
|
|
444
|
+
ok: false,
|
|
445
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const config = loadAgentConfig();
|
|
449
|
+
config[agentId] = { runtime, model };
|
|
450
|
+
saveAgentConfig(config);
|
|
451
|
+
return { ok: true };
|
|
452
|
+
}
|
|
453
|
+
function clearAgentRuntime(agentId) {
|
|
454
|
+
const config = loadAgentConfig();
|
|
455
|
+
delete config[agentId];
|
|
456
|
+
saveAgentConfig(config);
|
|
457
|
+
}
|
|
458
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
459
|
+
var init_agent_config = __esm({
|
|
460
|
+
"src/lib/agent-config.ts"() {
|
|
461
|
+
"use strict";
|
|
462
|
+
init_config();
|
|
463
|
+
init_runtime_table();
|
|
464
|
+
init_secure_files();
|
|
465
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
466
|
+
KNOWN_RUNTIMES = {
|
|
467
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
468
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
469
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
470
|
+
};
|
|
471
|
+
RUNTIME_LABELS = {
|
|
472
|
+
claude: "Claude Code (Anthropic)",
|
|
473
|
+
codex: "Codex (OpenAI)",
|
|
474
|
+
opencode: "OpenCode (open source)"
|
|
475
|
+
};
|
|
476
|
+
DEFAULT_MODELS = {
|
|
477
|
+
claude: "claude-opus-4",
|
|
478
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
479
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
332
484
|
// src/lib/employees.ts
|
|
333
485
|
var employees_exports = {};
|
|
334
486
|
__export(employees_exports, {
|
|
@@ -344,6 +496,7 @@ __export(employees_exports, {
|
|
|
344
496
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
345
497
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
346
498
|
hasRole: () => hasRole,
|
|
499
|
+
hireEmployee: () => hireEmployee,
|
|
347
500
|
isCoordinatorName: () => isCoordinatorName,
|
|
348
501
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
349
502
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -356,9 +509,9 @@ __export(employees_exports, {
|
|
|
356
509
|
validateEmployeeName: () => validateEmployeeName
|
|
357
510
|
});
|
|
358
511
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
359
|
-
import { existsSync as
|
|
512
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
360
513
|
import { execSync } from "child_process";
|
|
361
|
-
import
|
|
514
|
+
import path3 from "path";
|
|
362
515
|
import os2 from "os";
|
|
363
516
|
function normalizeRole(role) {
|
|
364
517
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -395,7 +548,7 @@ function validateEmployeeName(name) {
|
|
|
395
548
|
return { valid: true };
|
|
396
549
|
}
|
|
397
550
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
398
|
-
if (!
|
|
551
|
+
if (!existsSync4(employeesPath)) {
|
|
399
552
|
return [];
|
|
400
553
|
}
|
|
401
554
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -406,13 +559,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
406
559
|
}
|
|
407
560
|
}
|
|
408
561
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
409
|
-
await mkdir2(
|
|
562
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
410
563
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
411
564
|
}
|
|
412
565
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
413
|
-
if (!
|
|
566
|
+
if (!existsSync4(employeesPath)) return [];
|
|
414
567
|
try {
|
|
415
|
-
return JSON.parse(
|
|
568
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
416
569
|
} catch {
|
|
417
570
|
return [];
|
|
418
571
|
}
|
|
@@ -454,6 +607,52 @@ function addEmployee(employees, employee) {
|
|
|
454
607
|
}
|
|
455
608
|
return [...employees, normalized];
|
|
456
609
|
}
|
|
610
|
+
function appendToCoordinatorTeam(employee) {
|
|
611
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
612
|
+
if (!coordinator) return;
|
|
613
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
614
|
+
if (!existsSync4(idPath)) return;
|
|
615
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
616
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
617
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
618
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
619
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
620
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
621
|
+
const entry = `
|
|
622
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
623
|
+
`;
|
|
624
|
+
let updated;
|
|
625
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
626
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
627
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
628
|
+
} else {
|
|
629
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
630
|
+
}
|
|
631
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
632
|
+
}
|
|
633
|
+
function capitalize(s) {
|
|
634
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
635
|
+
}
|
|
636
|
+
async function hireEmployee(employee) {
|
|
637
|
+
const employees = await loadEmployees();
|
|
638
|
+
const updated = addEmployee(employees, employee);
|
|
639
|
+
await saveEmployees(updated);
|
|
640
|
+
try {
|
|
641
|
+
appendToCoordinatorTeam(employee);
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
646
|
+
const config = loadAgentConfig2();
|
|
647
|
+
const name = employee.name.toLowerCase();
|
|
648
|
+
if (!config[name] && config["default"]) {
|
|
649
|
+
config[name] = { ...config["default"] };
|
|
650
|
+
saveAgentConfig2(config);
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
return updated;
|
|
655
|
+
}
|
|
457
656
|
async function normalizeRosterCase(rosterPath) {
|
|
458
657
|
const employees = await loadEmployees(rosterPath);
|
|
459
658
|
let changed = false;
|
|
@@ -463,14 +662,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
463
662
|
emp.name = emp.name.toLowerCase();
|
|
464
663
|
changed = true;
|
|
465
664
|
try {
|
|
466
|
-
const identityDir =
|
|
467
|
-
const oldPath =
|
|
468
|
-
const newPath =
|
|
469
|
-
if (
|
|
665
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
666
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
667
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
668
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
470
669
|
renameSync2(oldPath, newPath);
|
|
471
|
-
} else if (
|
|
472
|
-
const content =
|
|
473
|
-
|
|
670
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
671
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
672
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
474
673
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
475
674
|
unlinkSync(oldPath);
|
|
476
675
|
}
|
|
@@ -500,7 +699,7 @@ function registerBinSymlinks(name) {
|
|
|
500
699
|
errors.push("Could not find 'exe-os' in PATH");
|
|
501
700
|
return { created, skipped, errors };
|
|
502
701
|
}
|
|
503
|
-
const binDir =
|
|
702
|
+
const binDir = path3.dirname(exeBinPath);
|
|
504
703
|
let target;
|
|
505
704
|
try {
|
|
506
705
|
target = readlinkSync(exeBinPath);
|
|
@@ -510,8 +709,8 @@ function registerBinSymlinks(name) {
|
|
|
510
709
|
}
|
|
511
710
|
for (const suffix of ["", "-opencode"]) {
|
|
512
711
|
const linkName = `${name}${suffix}`;
|
|
513
|
-
const linkPath =
|
|
514
|
-
if (
|
|
712
|
+
const linkPath = path3.join(binDir, linkName);
|
|
713
|
+
if (existsSync4(linkPath)) {
|
|
515
714
|
skipped.push(linkName);
|
|
516
715
|
continue;
|
|
517
716
|
}
|
|
@@ -524,21 +723,619 @@ function registerBinSymlinks(name) {
|
|
|
524
723
|
}
|
|
525
724
|
return { created, skipped, errors };
|
|
526
725
|
}
|
|
527
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
726
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
528
727
|
var init_employees = __esm({
|
|
529
728
|
"src/lib/employees.ts"() {
|
|
530
729
|
"use strict";
|
|
531
730
|
init_config();
|
|
532
|
-
EMPLOYEES_PATH =
|
|
731
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
533
732
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
534
733
|
COORDINATOR_ROLE = "COO";
|
|
535
734
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
735
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
736
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// src/lib/database-adapter.ts
|
|
741
|
+
import os3 from "os";
|
|
742
|
+
import path4 from "path";
|
|
743
|
+
import { createRequire } from "module";
|
|
744
|
+
import { pathToFileURL } from "url";
|
|
745
|
+
function quotedIdentifier(identifier) {
|
|
746
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
747
|
+
}
|
|
748
|
+
function unqualifiedTableName(name) {
|
|
749
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
750
|
+
const parts = raw.split(".");
|
|
751
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
752
|
+
}
|
|
753
|
+
function stripTrailingSemicolon(sql) {
|
|
754
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
755
|
+
}
|
|
756
|
+
function appendClause(sql, clause) {
|
|
757
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
758
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
759
|
+
if (!returningMatch) {
|
|
760
|
+
return `${trimmed}${clause}`;
|
|
761
|
+
}
|
|
762
|
+
const idx = returningMatch.index;
|
|
763
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
764
|
+
}
|
|
765
|
+
function normalizeStatement(stmt) {
|
|
766
|
+
if (typeof stmt === "string") {
|
|
767
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
768
|
+
}
|
|
769
|
+
const sql = stmt.sql;
|
|
770
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
771
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
772
|
+
}
|
|
773
|
+
return { kind: "named", sql, args: stmt.args };
|
|
774
|
+
}
|
|
775
|
+
function rewriteBooleanLiterals(sql) {
|
|
776
|
+
let out = sql;
|
|
777
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
778
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
779
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
780
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
781
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
782
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
783
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
784
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
}
|
|
788
|
+
function rewriteInsertOrIgnore(sql) {
|
|
789
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
790
|
+
return sql;
|
|
791
|
+
}
|
|
792
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
793
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
794
|
+
}
|
|
795
|
+
function rewriteInsertOrReplace(sql) {
|
|
796
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
797
|
+
if (!match) {
|
|
798
|
+
return sql;
|
|
799
|
+
}
|
|
800
|
+
const rawTable = match[1];
|
|
801
|
+
const rawColumns = match[2];
|
|
802
|
+
const remainder = match[3];
|
|
803
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
804
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
805
|
+
if (!conflictKeys?.length) {
|
|
806
|
+
return sql;
|
|
807
|
+
}
|
|
808
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
809
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
810
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
811
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
812
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
813
|
+
}
|
|
814
|
+
function rewriteSql(sql) {
|
|
815
|
+
let out = sql;
|
|
816
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
817
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
818
|
+
out = rewriteBooleanLiterals(out);
|
|
819
|
+
out = rewriteInsertOrReplace(out);
|
|
820
|
+
out = rewriteInsertOrIgnore(out);
|
|
821
|
+
return stripTrailingSemicolon(out);
|
|
822
|
+
}
|
|
823
|
+
function toBoolean(value) {
|
|
824
|
+
if (value === null || value === void 0) return value;
|
|
825
|
+
if (typeof value === "boolean") return value;
|
|
826
|
+
if (typeof value === "number") return value !== 0;
|
|
827
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
828
|
+
if (typeof value === "string") {
|
|
829
|
+
const normalized = value.trim().toLowerCase();
|
|
830
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
831
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
832
|
+
}
|
|
833
|
+
return Boolean(value);
|
|
834
|
+
}
|
|
835
|
+
function countQuestionMarks(sql, end) {
|
|
836
|
+
let count = 0;
|
|
837
|
+
let inSingle = false;
|
|
838
|
+
let inDouble = false;
|
|
839
|
+
let inLineComment = false;
|
|
840
|
+
let inBlockComment = false;
|
|
841
|
+
for (let i = 0; i < end; i++) {
|
|
842
|
+
const ch = sql[i];
|
|
843
|
+
const next = sql[i + 1];
|
|
844
|
+
if (inLineComment) {
|
|
845
|
+
if (ch === "\n") inLineComment = false;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (inBlockComment) {
|
|
849
|
+
if (ch === "*" && next === "/") {
|
|
850
|
+
inBlockComment = false;
|
|
851
|
+
i += 1;
|
|
852
|
+
}
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
856
|
+
inLineComment = true;
|
|
857
|
+
i += 1;
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
861
|
+
inBlockComment = true;
|
|
862
|
+
i += 1;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
866
|
+
inSingle = !inSingle;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
870
|
+
inDouble = !inDouble;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
874
|
+
count += 1;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return count;
|
|
878
|
+
}
|
|
879
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
880
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
881
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
882
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
883
|
+
for (const match of sql.matchAll(pattern)) {
|
|
884
|
+
const matchText = match[0];
|
|
885
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
886
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return indexes;
|
|
890
|
+
}
|
|
891
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
892
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
893
|
+
if (!match) return;
|
|
894
|
+
const rawTable = match[1];
|
|
895
|
+
const rawColumns = match[2];
|
|
896
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
897
|
+
if (!boolColumns?.size) return;
|
|
898
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
899
|
+
for (const [index, column] of columns.entries()) {
|
|
900
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
901
|
+
args[index] = toBoolean(args[index]);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
906
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
907
|
+
if (!match) return;
|
|
908
|
+
const rawTable = match[1];
|
|
909
|
+
const setClause = match[2];
|
|
910
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
911
|
+
if (!boolColumns?.size) return;
|
|
912
|
+
const assignments = setClause.split(",");
|
|
913
|
+
let placeholderIndex = 0;
|
|
914
|
+
for (const assignment of assignments) {
|
|
915
|
+
if (!assignment.includes("?")) continue;
|
|
916
|
+
placeholderIndex += 1;
|
|
917
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
918
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
919
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function coerceBooleanArgs(sql, args) {
|
|
924
|
+
const nextArgs = [...args];
|
|
925
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
926
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
927
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
928
|
+
for (const index of placeholderIndexes) {
|
|
929
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
930
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return nextArgs;
|
|
934
|
+
}
|
|
935
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
936
|
+
let out = "";
|
|
937
|
+
let placeholder = 0;
|
|
938
|
+
let inSingle = false;
|
|
939
|
+
let inDouble = false;
|
|
940
|
+
let inLineComment = false;
|
|
941
|
+
let inBlockComment = false;
|
|
942
|
+
for (let i = 0; i < sql.length; i++) {
|
|
943
|
+
const ch = sql[i];
|
|
944
|
+
const next = sql[i + 1];
|
|
945
|
+
if (inLineComment) {
|
|
946
|
+
out += ch;
|
|
947
|
+
if (ch === "\n") inLineComment = false;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (inBlockComment) {
|
|
951
|
+
out += ch;
|
|
952
|
+
if (ch === "*" && next === "/") {
|
|
953
|
+
out += next;
|
|
954
|
+
inBlockComment = false;
|
|
955
|
+
i += 1;
|
|
956
|
+
}
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
960
|
+
out += ch + next;
|
|
961
|
+
inLineComment = true;
|
|
962
|
+
i += 1;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
966
|
+
out += ch + next;
|
|
967
|
+
inBlockComment = true;
|
|
968
|
+
i += 1;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
972
|
+
inSingle = !inSingle;
|
|
973
|
+
out += ch;
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
977
|
+
inDouble = !inDouble;
|
|
978
|
+
out += ch;
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
982
|
+
placeholder += 1;
|
|
983
|
+
out += `$${placeholder}`;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
out += ch;
|
|
987
|
+
}
|
|
988
|
+
return out;
|
|
989
|
+
}
|
|
990
|
+
function translateStatementForPostgres(stmt) {
|
|
991
|
+
const normalized = normalizeStatement(stmt);
|
|
992
|
+
if (normalized.kind === "named") {
|
|
993
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
994
|
+
}
|
|
995
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
996
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
997
|
+
return {
|
|
998
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
999
|
+
args: coercedArgs
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
function shouldBypassPostgres(stmt) {
|
|
1003
|
+
const normalized = normalizeStatement(stmt);
|
|
1004
|
+
if (normalized.kind === "named") {
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1008
|
+
}
|
|
1009
|
+
function shouldFallbackOnError(error) {
|
|
1010
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1011
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1012
|
+
}
|
|
1013
|
+
function isReadQuery(sql) {
|
|
1014
|
+
const trimmed = sql.trimStart();
|
|
1015
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1016
|
+
}
|
|
1017
|
+
function buildRow(row, columns) {
|
|
1018
|
+
const values = columns.map((column) => row[column]);
|
|
1019
|
+
return Object.assign(values, row);
|
|
1020
|
+
}
|
|
1021
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1022
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1023
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1024
|
+
return {
|
|
1025
|
+
columns,
|
|
1026
|
+
columnTypes: columns.map(() => ""),
|
|
1027
|
+
rows: resultRows,
|
|
1028
|
+
rowsAffected,
|
|
1029
|
+
lastInsertRowid: void 0,
|
|
1030
|
+
toJSON() {
|
|
1031
|
+
return {
|
|
1032
|
+
columns,
|
|
1033
|
+
columnTypes: columns.map(() => ""),
|
|
1034
|
+
rows,
|
|
1035
|
+
rowsAffected,
|
|
1036
|
+
lastInsertRowid: void 0
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
async function loadPrismaClient() {
|
|
1042
|
+
if (!prismaClientPromise) {
|
|
1043
|
+
prismaClientPromise = (async () => {
|
|
1044
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1045
|
+
if (explicitPath) {
|
|
1046
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1047
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1048
|
+
if (!PrismaClient2) {
|
|
1049
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1050
|
+
}
|
|
1051
|
+
return new PrismaClient2();
|
|
1052
|
+
}
|
|
1053
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1054
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
1055
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1056
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1057
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1058
|
+
if (!PrismaClient) {
|
|
1059
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1060
|
+
}
|
|
1061
|
+
return new PrismaClient();
|
|
1062
|
+
})();
|
|
1063
|
+
}
|
|
1064
|
+
return prismaClientPromise;
|
|
1065
|
+
}
|
|
1066
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1067
|
+
if (!compatibilityBootstrapPromise) {
|
|
1068
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1069
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1070
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1071
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1072
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1073
|
+
relation
|
|
1074
|
+
);
|
|
1075
|
+
if (!rows[0]?.regclass) {
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
await prisma.$executeRawUnsafe(
|
|
1079
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
})();
|
|
1083
|
+
}
|
|
1084
|
+
return compatibilityBootstrapPromise;
|
|
1085
|
+
}
|
|
1086
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1087
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1088
|
+
if (isReadQuery(translated.sql)) {
|
|
1089
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1090
|
+
translated.sql,
|
|
1091
|
+
...translated.args
|
|
1092
|
+
);
|
|
1093
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1094
|
+
}
|
|
1095
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1096
|
+
return buildResultSet([], rowsAffected);
|
|
1097
|
+
}
|
|
1098
|
+
function splitSqlStatements(sql) {
|
|
1099
|
+
const parts = [];
|
|
1100
|
+
let current = "";
|
|
1101
|
+
let inSingle = false;
|
|
1102
|
+
let inDouble = false;
|
|
1103
|
+
let inLineComment = false;
|
|
1104
|
+
let inBlockComment = false;
|
|
1105
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1106
|
+
const ch = sql[i];
|
|
1107
|
+
const next = sql[i + 1];
|
|
1108
|
+
if (inLineComment) {
|
|
1109
|
+
current += ch;
|
|
1110
|
+
if (ch === "\n") inLineComment = false;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (inBlockComment) {
|
|
1114
|
+
current += ch;
|
|
1115
|
+
if (ch === "*" && next === "/") {
|
|
1116
|
+
current += next;
|
|
1117
|
+
inBlockComment = false;
|
|
1118
|
+
i += 1;
|
|
1119
|
+
}
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1123
|
+
current += ch + next;
|
|
1124
|
+
inLineComment = true;
|
|
1125
|
+
i += 1;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1129
|
+
current += ch + next;
|
|
1130
|
+
inBlockComment = true;
|
|
1131
|
+
i += 1;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1135
|
+
inSingle = !inSingle;
|
|
1136
|
+
current += ch;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1140
|
+
inDouble = !inDouble;
|
|
1141
|
+
current += ch;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1145
|
+
if (current.trim()) {
|
|
1146
|
+
parts.push(current.trim());
|
|
1147
|
+
}
|
|
1148
|
+
current = "";
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
current += ch;
|
|
1152
|
+
}
|
|
1153
|
+
if (current.trim()) {
|
|
1154
|
+
parts.push(current.trim());
|
|
1155
|
+
}
|
|
1156
|
+
return parts;
|
|
1157
|
+
}
|
|
1158
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1159
|
+
const prisma = await loadPrismaClient();
|
|
1160
|
+
await ensureCompatibilityViews(prisma);
|
|
1161
|
+
let closed = false;
|
|
1162
|
+
let adapter;
|
|
1163
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1164
|
+
if (!fallbackClient) {
|
|
1165
|
+
if (error) throw error;
|
|
1166
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1167
|
+
}
|
|
1168
|
+
if (error) {
|
|
1169
|
+
process.stderr.write(
|
|
1170
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1171
|
+
`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return fallbackClient.execute(stmt);
|
|
1175
|
+
};
|
|
1176
|
+
adapter = {
|
|
1177
|
+
async execute(stmt) {
|
|
1178
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1179
|
+
return fallbackExecute(stmt);
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
if (shouldFallbackOnError(error)) {
|
|
1185
|
+
return fallbackExecute(stmt, error);
|
|
1186
|
+
}
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
async batch(stmts, mode) {
|
|
1191
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1192
|
+
if (!fallbackClient) {
|
|
1193
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1194
|
+
}
|
|
1195
|
+
return fallbackClient.batch(stmts, mode);
|
|
1196
|
+
}
|
|
1197
|
+
try {
|
|
1198
|
+
if (prisma.$transaction) {
|
|
1199
|
+
return await prisma.$transaction(async (tx) => {
|
|
1200
|
+
const results2 = [];
|
|
1201
|
+
for (const stmt of stmts) {
|
|
1202
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1203
|
+
}
|
|
1204
|
+
return results2;
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
const results = [];
|
|
1208
|
+
for (const stmt of stmts) {
|
|
1209
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1210
|
+
}
|
|
1211
|
+
return results;
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1214
|
+
process.stderr.write(
|
|
1215
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1216
|
+
`
|
|
1217
|
+
);
|
|
1218
|
+
return fallbackClient.batch(stmts, mode);
|
|
1219
|
+
}
|
|
1220
|
+
throw error;
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
async migrate(stmts) {
|
|
1224
|
+
if (fallbackClient) {
|
|
1225
|
+
return fallbackClient.migrate(stmts);
|
|
1226
|
+
}
|
|
1227
|
+
return adapter.batch(stmts, "deferred");
|
|
1228
|
+
},
|
|
1229
|
+
async transaction(mode) {
|
|
1230
|
+
if (!fallbackClient) {
|
|
1231
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1232
|
+
}
|
|
1233
|
+
return fallbackClient.transaction(mode);
|
|
1234
|
+
},
|
|
1235
|
+
async executeMultiple(sql) {
|
|
1236
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1237
|
+
return fallbackClient.executeMultiple(sql);
|
|
1238
|
+
}
|
|
1239
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1240
|
+
await adapter.execute(statement);
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
async sync() {
|
|
1244
|
+
if (fallbackClient) {
|
|
1245
|
+
return fallbackClient.sync();
|
|
1246
|
+
}
|
|
1247
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1248
|
+
},
|
|
1249
|
+
close() {
|
|
1250
|
+
closed = true;
|
|
1251
|
+
prismaClientPromise = null;
|
|
1252
|
+
compatibilityBootstrapPromise = null;
|
|
1253
|
+
void prisma.$disconnect?.();
|
|
1254
|
+
},
|
|
1255
|
+
get closed() {
|
|
1256
|
+
return closed;
|
|
1257
|
+
},
|
|
1258
|
+
get protocol() {
|
|
1259
|
+
return "prisma-postgres";
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
return adapter;
|
|
1263
|
+
}
|
|
1264
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1265
|
+
var init_database_adapter = __esm({
|
|
1266
|
+
"src/lib/database-adapter.ts"() {
|
|
1267
|
+
"use strict";
|
|
1268
|
+
VIEW_MAPPINGS = [
|
|
1269
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1270
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1271
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1272
|
+
{ view: "entities", source: "memory.entities" },
|
|
1273
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1274
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1275
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1276
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1277
|
+
{ view: "messages", source: "memory.messages" },
|
|
1278
|
+
{ view: "users", source: "wiki.users" },
|
|
1279
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1280
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1281
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1282
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1283
|
+
];
|
|
1284
|
+
UPSERT_KEYS = {
|
|
1285
|
+
memories: ["id"],
|
|
1286
|
+
tasks: ["id"],
|
|
1287
|
+
behaviors: ["id"],
|
|
1288
|
+
entities: ["id"],
|
|
1289
|
+
relationships: ["id"],
|
|
1290
|
+
entity_aliases: ["alias"],
|
|
1291
|
+
notifications: ["id"],
|
|
1292
|
+
messages: ["id"],
|
|
1293
|
+
users: ["id"],
|
|
1294
|
+
workspaces: ["id"],
|
|
1295
|
+
workspace_users: ["id"],
|
|
1296
|
+
documents: ["id"],
|
|
1297
|
+
chats: ["id"]
|
|
1298
|
+
};
|
|
1299
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1300
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1301
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1302
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1303
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1304
|
+
};
|
|
1305
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1306
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1307
|
+
);
|
|
1308
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1309
|
+
/\bPRAGMA\b/i,
|
|
1310
|
+
/\bsqlite_master\b/i,
|
|
1311
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1312
|
+
/\bMATCH\b/i,
|
|
1313
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1314
|
+
/\bjson_extract\s*\(/i,
|
|
1315
|
+
/\bjulianday\s*\(/i,
|
|
1316
|
+
/\bstrftime\s*\(/i,
|
|
1317
|
+
/\blast_insert_rowid\s*\(/i
|
|
1318
|
+
];
|
|
1319
|
+
prismaClientPromise = null;
|
|
1320
|
+
compatibilityBootstrapPromise = null;
|
|
536
1321
|
}
|
|
537
1322
|
});
|
|
538
1323
|
|
|
539
1324
|
// src/lib/database.ts
|
|
540
1325
|
import { createClient } from "@libsql/client";
|
|
541
1326
|
async function initDatabase(config) {
|
|
1327
|
+
if (_walCheckpointTimer) {
|
|
1328
|
+
clearInterval(_walCheckpointTimer);
|
|
1329
|
+
_walCheckpointTimer = null;
|
|
1330
|
+
}
|
|
1331
|
+
if (_daemonClient) {
|
|
1332
|
+
_daemonClient.close();
|
|
1333
|
+
_daemonClient = null;
|
|
1334
|
+
}
|
|
1335
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1336
|
+
_adapterClient.close();
|
|
1337
|
+
}
|
|
1338
|
+
_adapterClient = null;
|
|
542
1339
|
if (_client) {
|
|
543
1340
|
_client.close();
|
|
544
1341
|
_client = null;
|
|
@@ -552,6 +1349,7 @@ async function initDatabase(config) {
|
|
|
552
1349
|
}
|
|
553
1350
|
_client = createClient(opts);
|
|
554
1351
|
_resilientClient = wrapWithRetry(_client);
|
|
1352
|
+
_adapterClient = _resilientClient;
|
|
555
1353
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
556
1354
|
});
|
|
557
1355
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -562,11 +1360,17 @@ async function initDatabase(config) {
|
|
|
562
1360
|
});
|
|
563
1361
|
}, 3e4);
|
|
564
1362
|
_walCheckpointTimer.unref();
|
|
1363
|
+
if (process.env.DATABASE_URL) {
|
|
1364
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1365
|
+
}
|
|
565
1366
|
}
|
|
566
1367
|
function getClient() {
|
|
567
|
-
if (!
|
|
1368
|
+
if (!_adapterClient) {
|
|
568
1369
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
569
1370
|
}
|
|
1371
|
+
if (process.env.DATABASE_URL) {
|
|
1372
|
+
return _adapterClient;
|
|
1373
|
+
}
|
|
570
1374
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
571
1375
|
return _resilientClient;
|
|
572
1376
|
}
|
|
@@ -859,6 +1663,7 @@ async function ensureSchema() {
|
|
|
859
1663
|
project TEXT NOT NULL,
|
|
860
1664
|
summary TEXT NOT NULL,
|
|
861
1665
|
task_file TEXT,
|
|
1666
|
+
session_scope TEXT,
|
|
862
1667
|
read INTEGER NOT NULL DEFAULT 0,
|
|
863
1668
|
created_at TEXT NOT NULL
|
|
864
1669
|
);
|
|
@@ -867,7 +1672,7 @@ async function ensureSchema() {
|
|
|
867
1672
|
ON notifications(read);
|
|
868
1673
|
|
|
869
1674
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
870
|
-
ON notifications(agent_id);
|
|
1675
|
+
ON notifications(agent_id, session_scope);
|
|
871
1676
|
|
|
872
1677
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
873
1678
|
ON notifications(task_file);
|
|
@@ -905,6 +1710,7 @@ async function ensureSchema() {
|
|
|
905
1710
|
target_agent TEXT NOT NULL,
|
|
906
1711
|
target_project TEXT,
|
|
907
1712
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1713
|
+
session_scope TEXT,
|
|
908
1714
|
content TEXT NOT NULL,
|
|
909
1715
|
priority TEXT DEFAULT 'normal',
|
|
910
1716
|
status TEXT DEFAULT 'pending',
|
|
@@ -918,10 +1724,31 @@ async function ensureSchema() {
|
|
|
918
1724
|
);
|
|
919
1725
|
|
|
920
1726
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
921
|
-
ON messages(target_agent, status);
|
|
1727
|
+
ON messages(target_agent, session_scope, status);
|
|
922
1728
|
|
|
923
1729
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
924
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1730
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1731
|
+
`);
|
|
1732
|
+
try {
|
|
1733
|
+
await client.execute({
|
|
1734
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1735
|
+
args: []
|
|
1736
|
+
});
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
await client.execute({
|
|
1741
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1742
|
+
args: []
|
|
1743
|
+
});
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
await client.executeMultiple(`
|
|
1747
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1748
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1749
|
+
|
|
1750
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1751
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
925
1752
|
`);
|
|
926
1753
|
try {
|
|
927
1754
|
await client.execute({
|
|
@@ -1505,28 +2332,45 @@ async function ensureSchema() {
|
|
|
1505
2332
|
} catch {
|
|
1506
2333
|
}
|
|
1507
2334
|
}
|
|
2335
|
+
try {
|
|
2336
|
+
await client.execute({
|
|
2337
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2338
|
+
args: []
|
|
2339
|
+
});
|
|
2340
|
+
} catch {
|
|
2341
|
+
}
|
|
1508
2342
|
}
|
|
1509
2343
|
async function disposeDatabase() {
|
|
2344
|
+
if (_walCheckpointTimer) {
|
|
2345
|
+
clearInterval(_walCheckpointTimer);
|
|
2346
|
+
_walCheckpointTimer = null;
|
|
2347
|
+
}
|
|
1510
2348
|
if (_daemonClient) {
|
|
1511
2349
|
_daemonClient.close();
|
|
1512
2350
|
_daemonClient = null;
|
|
1513
2351
|
}
|
|
2352
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2353
|
+
_adapterClient.close();
|
|
2354
|
+
}
|
|
2355
|
+
_adapterClient = null;
|
|
1514
2356
|
if (_client) {
|
|
1515
2357
|
_client.close();
|
|
1516
2358
|
_client = null;
|
|
1517
2359
|
_resilientClient = null;
|
|
1518
2360
|
}
|
|
1519
2361
|
}
|
|
1520
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2362
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1521
2363
|
var init_database = __esm({
|
|
1522
2364
|
"src/lib/database.ts"() {
|
|
1523
2365
|
"use strict";
|
|
1524
2366
|
init_db_retry();
|
|
1525
2367
|
init_employees();
|
|
2368
|
+
init_database_adapter();
|
|
1526
2369
|
_client = null;
|
|
1527
2370
|
_resilientClient = null;
|
|
1528
2371
|
_walCheckpointTimer = null;
|
|
1529
2372
|
_daemonClient = null;
|
|
2373
|
+
_adapterClient = null;
|
|
1530
2374
|
initTurso = initDatabase;
|
|
1531
2375
|
disposeTurso = disposeDatabase;
|
|
1532
2376
|
}
|
|
@@ -1534,14 +2378,14 @@ var init_database = __esm({
|
|
|
1534
2378
|
|
|
1535
2379
|
// src/lib/keychain.ts
|
|
1536
2380
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1537
|
-
import { existsSync as
|
|
1538
|
-
import
|
|
1539
|
-
import
|
|
2381
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2382
|
+
import path5 from "path";
|
|
2383
|
+
import os4 from "os";
|
|
1540
2384
|
function getKeyDir() {
|
|
1541
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2385
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
|
|
1542
2386
|
}
|
|
1543
2387
|
function getKeyPath() {
|
|
1544
|
-
return
|
|
2388
|
+
return path5.join(getKeyDir(), "master.key");
|
|
1545
2389
|
}
|
|
1546
2390
|
async function tryKeytar() {
|
|
1547
2391
|
try {
|
|
@@ -1562,9 +2406,9 @@ async function getMasterKey() {
|
|
|
1562
2406
|
}
|
|
1563
2407
|
}
|
|
1564
2408
|
const keyPath = getKeyPath();
|
|
1565
|
-
if (!
|
|
2409
|
+
if (!existsSync5(keyPath)) {
|
|
1566
2410
|
process.stderr.write(
|
|
1567
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2411
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1568
2412
|
`
|
|
1569
2413
|
);
|
|
1570
2414
|
return null;
|
|
@@ -1649,6 +2493,7 @@ var shard_manager_exports = {};
|
|
|
1649
2493
|
__export(shard_manager_exports, {
|
|
1650
2494
|
disposeShards: () => disposeShards,
|
|
1651
2495
|
ensureShardSchema: () => ensureShardSchema,
|
|
2496
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1652
2497
|
getReadyShardClient: () => getReadyShardClient,
|
|
1653
2498
|
getShardClient: () => getShardClient,
|
|
1654
2499
|
getShardsDir: () => getShardsDir,
|
|
@@ -1657,15 +2502,18 @@ __export(shard_manager_exports, {
|
|
|
1657
2502
|
listShards: () => listShards,
|
|
1658
2503
|
shardExists: () => shardExists
|
|
1659
2504
|
});
|
|
1660
|
-
import
|
|
1661
|
-
import { existsSync as
|
|
2505
|
+
import path6 from "path";
|
|
2506
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1662
2507
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1663
2508
|
function initShardManager(encryptionKey) {
|
|
1664
2509
|
_encryptionKey = encryptionKey;
|
|
1665
|
-
if (!
|
|
1666
|
-
|
|
2510
|
+
if (!existsSync6(SHARDS_DIR)) {
|
|
2511
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1667
2512
|
}
|
|
1668
2513
|
_shardingEnabled = true;
|
|
2514
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2515
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2516
|
+
_evictionTimer.unref();
|
|
1669
2517
|
}
|
|
1670
2518
|
function isShardingEnabled() {
|
|
1671
2519
|
return _shardingEnabled;
|
|
@@ -1682,21 +2530,28 @@ function getShardClient(projectName) {
|
|
|
1682
2530
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1683
2531
|
}
|
|
1684
2532
|
const cached = _shards.get(safeName);
|
|
1685
|
-
if (cached)
|
|
1686
|
-
|
|
2533
|
+
if (cached) {
|
|
2534
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2535
|
+
return cached;
|
|
2536
|
+
}
|
|
2537
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2538
|
+
evictLRU();
|
|
2539
|
+
}
|
|
2540
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1687
2541
|
const client = createClient2({
|
|
1688
2542
|
url: `file:${dbPath}`,
|
|
1689
2543
|
encryptionKey: _encryptionKey
|
|
1690
2544
|
});
|
|
1691
2545
|
_shards.set(safeName, client);
|
|
2546
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1692
2547
|
return client;
|
|
1693
2548
|
}
|
|
1694
2549
|
function shardExists(projectName) {
|
|
1695
2550
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1696
|
-
return
|
|
2551
|
+
return existsSync6(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1697
2552
|
}
|
|
1698
2553
|
function listShards() {
|
|
1699
|
-
if (!
|
|
2554
|
+
if (!existsSync6(SHARDS_DIR)) return [];
|
|
1700
2555
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1701
2556
|
}
|
|
1702
2557
|
async function ensureShardSchema(client) {
|
|
@@ -1748,6 +2603,8 @@ async function ensureShardSchema(client) {
|
|
|
1748
2603
|
for (const col of [
|
|
1749
2604
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1750
2605
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2606
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2607
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1751
2608
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1752
2609
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1753
2610
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1770,7 +2627,23 @@ async function ensureShardSchema(client) {
|
|
|
1770
2627
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1771
2628
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1772
2629
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1773
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2630
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2631
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2632
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2633
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2634
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2635
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2636
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2637
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2638
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2639
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2640
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2641
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2642
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2643
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2644
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2645
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2646
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1774
2647
|
]) {
|
|
1775
2648
|
try {
|
|
1776
2649
|
await client.execute(col);
|
|
@@ -1869,21 +2742,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1869
2742
|
await ensureShardSchema(client);
|
|
1870
2743
|
return client;
|
|
1871
2744
|
}
|
|
2745
|
+
function evictLRU() {
|
|
2746
|
+
let oldest = null;
|
|
2747
|
+
let oldestTime = Infinity;
|
|
2748
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2749
|
+
if (time < oldestTime) {
|
|
2750
|
+
oldestTime = time;
|
|
2751
|
+
oldest = name;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (oldest) {
|
|
2755
|
+
const client = _shards.get(oldest);
|
|
2756
|
+
if (client) {
|
|
2757
|
+
client.close();
|
|
2758
|
+
}
|
|
2759
|
+
_shards.delete(oldest);
|
|
2760
|
+
_shardLastAccess.delete(oldest);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
function evictIdleShards() {
|
|
2764
|
+
const now = Date.now();
|
|
2765
|
+
const toEvict = [];
|
|
2766
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2767
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2768
|
+
toEvict.push(name);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
for (const name of toEvict) {
|
|
2772
|
+
const client = _shards.get(name);
|
|
2773
|
+
if (client) {
|
|
2774
|
+
client.close();
|
|
2775
|
+
}
|
|
2776
|
+
_shards.delete(name);
|
|
2777
|
+
_shardLastAccess.delete(name);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
function getOpenShardCount() {
|
|
2781
|
+
return _shards.size;
|
|
2782
|
+
}
|
|
1872
2783
|
function disposeShards() {
|
|
2784
|
+
if (_evictionTimer) {
|
|
2785
|
+
clearInterval(_evictionTimer);
|
|
2786
|
+
_evictionTimer = null;
|
|
2787
|
+
}
|
|
1873
2788
|
for (const [, client] of _shards) {
|
|
1874
2789
|
client.close();
|
|
1875
2790
|
}
|
|
1876
2791
|
_shards.clear();
|
|
2792
|
+
_shardLastAccess.clear();
|
|
1877
2793
|
_shardingEnabled = false;
|
|
1878
2794
|
_encryptionKey = null;
|
|
1879
2795
|
}
|
|
1880
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2796
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1881
2797
|
var init_shard_manager = __esm({
|
|
1882
2798
|
"src/lib/shard-manager.ts"() {
|
|
1883
2799
|
"use strict";
|
|
1884
2800
|
init_config();
|
|
1885
|
-
SHARDS_DIR =
|
|
2801
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
2802
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2803
|
+
MAX_OPEN_SHARDS = 10;
|
|
2804
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1886
2805
|
_shards = /* @__PURE__ */ new Map();
|
|
2806
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2807
|
+
_evictionTimer = null;
|
|
1887
2808
|
_encryptionKey = null;
|
|
1888
2809
|
_shardingEnabled = false;
|
|
1889
2810
|
}
|
|
@@ -2745,8 +3666,8 @@ __export(reranker_exports, {
|
|
|
2745
3666
|
rerankWithContext: () => rerankWithContext,
|
|
2746
3667
|
rerankWithScores: () => rerankWithScores
|
|
2747
3668
|
});
|
|
2748
|
-
import
|
|
2749
|
-
import { existsSync as
|
|
3669
|
+
import path7 from "path";
|
|
3670
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2750
3671
|
function resetIdleTimer() {
|
|
2751
3672
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
2752
3673
|
_idleTimer = setTimeout(() => {
|
|
@@ -2757,18 +3678,18 @@ function resetIdleTimer() {
|
|
|
2757
3678
|
}
|
|
2758
3679
|
}
|
|
2759
3680
|
function isRerankerAvailable() {
|
|
2760
|
-
return
|
|
3681
|
+
return existsSync7(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
2761
3682
|
}
|
|
2762
3683
|
function getRerankerModelPath() {
|
|
2763
|
-
return
|
|
3684
|
+
return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
2764
3685
|
}
|
|
2765
3686
|
async function ensureLoaded() {
|
|
2766
3687
|
if (_rerankerContext) {
|
|
2767
3688
|
resetIdleTimer();
|
|
2768
3689
|
return;
|
|
2769
3690
|
}
|
|
2770
|
-
const modelPath =
|
|
2771
|
-
if (!
|
|
3691
|
+
const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3692
|
+
if (!existsSync7(modelPath)) {
|
|
2772
3693
|
throw new Error(
|
|
2773
3694
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
2774
3695
|
);
|
|
@@ -2864,13 +3785,50 @@ var init_reranker = __esm({
|
|
|
2864
3785
|
}
|
|
2865
3786
|
});
|
|
2866
3787
|
|
|
3788
|
+
// src/lib/daemon-auth.ts
|
|
3789
|
+
import crypto from "crypto";
|
|
3790
|
+
import path8 from "path";
|
|
3791
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
3792
|
+
function normalizeToken(token) {
|
|
3793
|
+
if (!token) return null;
|
|
3794
|
+
const trimmed = token.trim();
|
|
3795
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3796
|
+
}
|
|
3797
|
+
function readDaemonToken() {
|
|
3798
|
+
try {
|
|
3799
|
+
if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
|
|
3800
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
3801
|
+
} catch {
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function ensureDaemonToken(seed) {
|
|
3806
|
+
const existing = readDaemonToken();
|
|
3807
|
+
if (existing) return existing;
|
|
3808
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
3809
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
3810
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
3811
|
+
`, "utf8");
|
|
3812
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
3813
|
+
return token;
|
|
3814
|
+
}
|
|
3815
|
+
var DAEMON_TOKEN_PATH;
|
|
3816
|
+
var init_daemon_auth = __esm({
|
|
3817
|
+
"src/lib/daemon-auth.ts"() {
|
|
3818
|
+
"use strict";
|
|
3819
|
+
init_config();
|
|
3820
|
+
init_secure_files();
|
|
3821
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
3822
|
+
}
|
|
3823
|
+
});
|
|
3824
|
+
|
|
2867
3825
|
// src/lib/exe-daemon-client.ts
|
|
2868
3826
|
import net from "net";
|
|
2869
|
-
import
|
|
3827
|
+
import os5 from "os";
|
|
2870
3828
|
import { spawn } from "child_process";
|
|
2871
3829
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2872
|
-
import { existsSync as
|
|
2873
|
-
import
|
|
3830
|
+
import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
3831
|
+
import path9 from "path";
|
|
2874
3832
|
import { fileURLToPath } from "url";
|
|
2875
3833
|
function handleData(chunk) {
|
|
2876
3834
|
_buffer += chunk.toString();
|
|
@@ -2898,9 +3856,9 @@ function handleData(chunk) {
|
|
|
2898
3856
|
}
|
|
2899
3857
|
}
|
|
2900
3858
|
function cleanupStaleFiles() {
|
|
2901
|
-
if (
|
|
3859
|
+
if (existsSync9(PID_PATH)) {
|
|
2902
3860
|
try {
|
|
2903
|
-
const pid = parseInt(
|
|
3861
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
2904
3862
|
if (pid > 0) {
|
|
2905
3863
|
try {
|
|
2906
3864
|
process.kill(pid, 0);
|
|
@@ -2921,17 +3879,17 @@ function cleanupStaleFiles() {
|
|
|
2921
3879
|
}
|
|
2922
3880
|
}
|
|
2923
3881
|
function findPackageRoot() {
|
|
2924
|
-
let dir =
|
|
2925
|
-
const { root } =
|
|
3882
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
3883
|
+
const { root } = path9.parse(dir);
|
|
2926
3884
|
while (dir !== root) {
|
|
2927
|
-
if (
|
|
2928
|
-
dir =
|
|
3885
|
+
if (existsSync9(path9.join(dir, "package.json"))) return dir;
|
|
3886
|
+
dir = path9.dirname(dir);
|
|
2929
3887
|
}
|
|
2930
3888
|
return null;
|
|
2931
3889
|
}
|
|
2932
3890
|
function spawnDaemon() {
|
|
2933
|
-
const freeGB =
|
|
2934
|
-
const totalGB =
|
|
3891
|
+
const freeGB = os5.freemem() / (1024 * 1024 * 1024);
|
|
3892
|
+
const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
|
|
2935
3893
|
if (totalGB <= 8) {
|
|
2936
3894
|
process.stderr.write(
|
|
2937
3895
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2951,16 +3909,17 @@ function spawnDaemon() {
|
|
|
2951
3909
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2952
3910
|
return;
|
|
2953
3911
|
}
|
|
2954
|
-
const daemonPath =
|
|
2955
|
-
if (!
|
|
3912
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
3913
|
+
if (!existsSync9(daemonPath)) {
|
|
2956
3914
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2957
3915
|
`);
|
|
2958
3916
|
return;
|
|
2959
3917
|
}
|
|
2960
3918
|
const resolvedPath = daemonPath;
|
|
3919
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2961
3920
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2962
3921
|
`);
|
|
2963
|
-
const logPath =
|
|
3922
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
2964
3923
|
let stderrFd = "ignore";
|
|
2965
3924
|
try {
|
|
2966
3925
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2978,7 +3937,8 @@ function spawnDaemon() {
|
|
|
2978
3937
|
TMUX_PANE: void 0,
|
|
2979
3938
|
// Prevents resolveExeSession() from scoping to one session
|
|
2980
3939
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
2981
|
-
EXE_DAEMON_PID: PID_PATH
|
|
3940
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
3941
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
2982
3942
|
}
|
|
2983
3943
|
});
|
|
2984
3944
|
child.unref();
|
|
@@ -3088,13 +4048,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
3088
4048
|
return;
|
|
3089
4049
|
}
|
|
3090
4050
|
const id = randomUUID2();
|
|
4051
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
3091
4052
|
const timer = setTimeout(() => {
|
|
3092
4053
|
_pending.delete(id);
|
|
3093
4054
|
resolve({ error: "Request timeout" });
|
|
3094
4055
|
}, timeoutMs);
|
|
3095
4056
|
_pending.set(id, { resolve, timer });
|
|
3096
4057
|
try {
|
|
3097
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
4058
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
3098
4059
|
} catch {
|
|
3099
4060
|
clearTimeout(timer);
|
|
3100
4061
|
_pending.delete(id);
|
|
@@ -3111,74 +4072,123 @@ async function pingDaemon() {
|
|
|
3111
4072
|
return null;
|
|
3112
4073
|
}
|
|
3113
4074
|
function killAndRespawnDaemon() {
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
4075
|
+
if (!acquireSpawnLock()) {
|
|
4076
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
4077
|
+
if (_socket) {
|
|
4078
|
+
_socket.destroy();
|
|
4079
|
+
_socket = null;
|
|
4080
|
+
}
|
|
4081
|
+
_connected = false;
|
|
4082
|
+
_buffer = "";
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
try {
|
|
4086
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
4087
|
+
if (existsSync9(PID_PATH)) {
|
|
4088
|
+
try {
|
|
4089
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
4090
|
+
if (pid > 0) {
|
|
4091
|
+
try {
|
|
4092
|
+
process.kill(pid, "SIGKILL");
|
|
4093
|
+
} catch {
|
|
4094
|
+
}
|
|
3122
4095
|
}
|
|
4096
|
+
} catch {
|
|
3123
4097
|
}
|
|
4098
|
+
}
|
|
4099
|
+
if (_socket) {
|
|
4100
|
+
_socket.destroy();
|
|
4101
|
+
_socket = null;
|
|
4102
|
+
}
|
|
4103
|
+
_connected = false;
|
|
4104
|
+
_buffer = "";
|
|
4105
|
+
try {
|
|
4106
|
+
unlinkSync2(PID_PATH);
|
|
3124
4107
|
} catch {
|
|
3125
4108
|
}
|
|
4109
|
+
try {
|
|
4110
|
+
unlinkSync2(SOCKET_PATH);
|
|
4111
|
+
} catch {
|
|
4112
|
+
}
|
|
4113
|
+
spawnDaemon();
|
|
4114
|
+
} finally {
|
|
4115
|
+
releaseSpawnLock();
|
|
3126
4116
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
_socket = null;
|
|
3130
|
-
}
|
|
3131
|
-
_connected = false;
|
|
3132
|
-
_buffer = "";
|
|
4117
|
+
}
|
|
4118
|
+
function isDaemonTooYoung() {
|
|
3133
4119
|
try {
|
|
3134
|
-
|
|
4120
|
+
const stat = statSync(PID_PATH);
|
|
4121
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
3135
4122
|
} catch {
|
|
4123
|
+
return false;
|
|
3136
4124
|
}
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
4125
|
+
}
|
|
4126
|
+
async function retryThenRestart(doRequest, label) {
|
|
4127
|
+
const result = await doRequest();
|
|
4128
|
+
if (!result.error) {
|
|
4129
|
+
_consecutiveFailures = 0;
|
|
4130
|
+
return result;
|
|
3140
4131
|
}
|
|
3141
|
-
|
|
4132
|
+
_consecutiveFailures++;
|
|
4133
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
4134
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
4135
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
4136
|
+
`);
|
|
4137
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
4138
|
+
if (!_connected) {
|
|
4139
|
+
if (!await connectToSocket()) continue;
|
|
4140
|
+
}
|
|
4141
|
+
const retry = await doRequest();
|
|
4142
|
+
if (!retry.error) {
|
|
4143
|
+
_consecutiveFailures = 0;
|
|
4144
|
+
return retry;
|
|
4145
|
+
}
|
|
4146
|
+
_consecutiveFailures++;
|
|
4147
|
+
}
|
|
4148
|
+
if (isDaemonTooYoung()) {
|
|
4149
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
4150
|
+
`);
|
|
4151
|
+
return { error: result.error };
|
|
4152
|
+
}
|
|
4153
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
4154
|
+
`);
|
|
4155
|
+
killAndRespawnDaemon();
|
|
4156
|
+
const start = Date.now();
|
|
4157
|
+
let delay2 = 200;
|
|
4158
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
4159
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
4160
|
+
if (await connectToSocket()) break;
|
|
4161
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
4162
|
+
}
|
|
4163
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
4164
|
+
const final = await doRequest();
|
|
4165
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
4166
|
+
return final;
|
|
3142
4167
|
}
|
|
3143
4168
|
async function embedViaClient(text, priority = "high") {
|
|
3144
4169
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
3145
4170
|
_requestCount++;
|
|
3146
4171
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
3147
4172
|
const health = await pingDaemon();
|
|
3148
|
-
if (!health) {
|
|
4173
|
+
if (!health && !isDaemonTooYoung()) {
|
|
3149
4174
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
3150
4175
|
`);
|
|
3151
4176
|
killAndRespawnDaemon();
|
|
3152
4177
|
const start = Date.now();
|
|
3153
|
-
let
|
|
4178
|
+
let d = 200;
|
|
3154
4179
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3155
|
-
await new Promise((r) => setTimeout(r,
|
|
4180
|
+
await new Promise((r) => setTimeout(r, d));
|
|
3156
4181
|
if (await connectToSocket()) break;
|
|
3157
|
-
|
|
4182
|
+
d = Math.min(d * 2, 3e3);
|
|
3158
4183
|
}
|
|
3159
4184
|
if (!_connected) return null;
|
|
3160
4185
|
}
|
|
3161
4186
|
}
|
|
3162
|
-
const result = await
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
killAndRespawnDaemon();
|
|
3168
|
-
const start = Date.now();
|
|
3169
|
-
let delay2 = 200;
|
|
3170
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
3171
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
3172
|
-
if (await connectToSocket()) break;
|
|
3173
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
3174
|
-
}
|
|
3175
|
-
if (!_connected) return null;
|
|
3176
|
-
const retry = await sendRequest([text], priority);
|
|
3177
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
3178
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
3179
|
-
`);
|
|
3180
|
-
}
|
|
3181
|
-
return null;
|
|
4187
|
+
const result = await retryThenRestart(
|
|
4188
|
+
() => sendRequest([text], priority),
|
|
4189
|
+
"Embed"
|
|
4190
|
+
);
|
|
4191
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
3182
4192
|
}
|
|
3183
4193
|
function disconnectClient() {
|
|
3184
4194
|
if (_socket) {
|
|
@@ -3193,22 +4203,28 @@ function disconnectClient() {
|
|
|
3193
4203
|
entry.resolve({ error: "Client disconnected" });
|
|
3194
4204
|
}
|
|
3195
4205
|
}
|
|
3196
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
4206
|
+
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;
|
|
3197
4207
|
var init_exe_daemon_client = __esm({
|
|
3198
4208
|
"src/lib/exe-daemon-client.ts"() {
|
|
3199
4209
|
"use strict";
|
|
3200
4210
|
init_config();
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
4211
|
+
init_daemon_auth();
|
|
4212
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
4213
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
4214
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
3204
4215
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
3205
4216
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
3206
4217
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
4218
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
3207
4219
|
_socket = null;
|
|
3208
4220
|
_connected = false;
|
|
3209
4221
|
_buffer = "";
|
|
3210
4222
|
_requestCount = 0;
|
|
4223
|
+
_consecutiveFailures = 0;
|
|
3211
4224
|
HEALTH_CHECK_INTERVAL = 100;
|
|
4225
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
4226
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
4227
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
3212
4228
|
_pending = /* @__PURE__ */ new Map();
|
|
3213
4229
|
MAX_BUFFER = 1e7;
|
|
3214
4230
|
}
|
|
@@ -3251,10 +4267,10 @@ async function disposeEmbedder() {
|
|
|
3251
4267
|
async function embedDirect(text) {
|
|
3252
4268
|
const llamaCpp = await import("node-llama-cpp");
|
|
3253
4269
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3254
|
-
const { existsSync:
|
|
3255
|
-
const
|
|
3256
|
-
const modelPath =
|
|
3257
|
-
if (!
|
|
4270
|
+
const { existsSync: existsSync20 } = await import("fs");
|
|
4271
|
+
const path24 = await import("path");
|
|
4272
|
+
const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
4273
|
+
if (!existsSync20(modelPath)) {
|
|
3258
4274
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
3259
4275
|
}
|
|
3260
4276
|
const llama = await llamaCpp.getLlama();
|
|
@@ -3289,7 +4305,7 @@ __export(project_name_exports, {
|
|
|
3289
4305
|
getProjectName: () => getProjectName
|
|
3290
4306
|
});
|
|
3291
4307
|
import { execSync as execSync2 } from "child_process";
|
|
3292
|
-
import
|
|
4308
|
+
import path10 from "path";
|
|
3293
4309
|
function getProjectName(cwd) {
|
|
3294
4310
|
const dir = cwd ?? process.cwd();
|
|
3295
4311
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
@@ -3302,7 +4318,7 @@ function getProjectName(cwd) {
|
|
|
3302
4318
|
timeout: 2e3,
|
|
3303
4319
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3304
4320
|
}).trim();
|
|
3305
|
-
repoRoot =
|
|
4321
|
+
repoRoot = path10.dirname(gitCommonDir);
|
|
3306
4322
|
} catch {
|
|
3307
4323
|
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
3308
4324
|
cwd: dir,
|
|
@@ -3311,11 +4327,11 @@ function getProjectName(cwd) {
|
|
|
3311
4327
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3312
4328
|
}).trim();
|
|
3313
4329
|
}
|
|
3314
|
-
_cached =
|
|
4330
|
+
_cached = path10.basename(repoRoot);
|
|
3315
4331
|
_cachedCwd = dir;
|
|
3316
4332
|
return _cached;
|
|
3317
4333
|
} catch {
|
|
3318
|
-
_cached =
|
|
4334
|
+
_cached = path10.basename(dir);
|
|
3319
4335
|
_cachedCwd = dir;
|
|
3320
4336
|
return _cached;
|
|
3321
4337
|
}
|
|
@@ -3339,9 +4355,9 @@ __export(file_grep_exports, {
|
|
|
3339
4355
|
grepProjectFiles: () => grepProjectFiles
|
|
3340
4356
|
});
|
|
3341
4357
|
import { execSync as execSync3 } from "child_process";
|
|
3342
|
-
import { readFileSync as
|
|
3343
|
-
import
|
|
3344
|
-
import
|
|
4358
|
+
import { readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync10 } from "fs";
|
|
4359
|
+
import path11 from "path";
|
|
4360
|
+
import crypto2 from "crypto";
|
|
3345
4361
|
function hasRipgrep() {
|
|
3346
4362
|
if (_hasRg === null) {
|
|
3347
4363
|
try {
|
|
@@ -3374,13 +4390,13 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
3374
4390
|
const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
|
|
3375
4391
|
const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
|
|
3376
4392
|
return {
|
|
3377
|
-
id:
|
|
4393
|
+
id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
|
|
3378
4394
|
agent_id: "project",
|
|
3379
4395
|
agent_role: "file",
|
|
3380
4396
|
session_id: "file-grep",
|
|
3381
4397
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3382
4398
|
tool_name: "file_grep",
|
|
3383
|
-
project_name:
|
|
4399
|
+
project_name: path11.basename(projectRoot),
|
|
3384
4400
|
has_error: false,
|
|
3385
4401
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
3386
4402
|
vector: null,
|
|
@@ -3392,7 +4408,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
3392
4408
|
try {
|
|
3393
4409
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
3394
4410
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
3395
|
-
const source =
|
|
4411
|
+
const source = readFileSync6(filePath, "utf8");
|
|
3396
4412
|
const lines = source.split("\n");
|
|
3397
4413
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
3398
4414
|
const line = lines[i];
|
|
@@ -3454,11 +4470,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
3454
4470
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
3455
4471
|
const hits = [];
|
|
3456
4472
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
3457
|
-
const absPath =
|
|
4473
|
+
const absPath = path11.join(projectRoot, filePath);
|
|
3458
4474
|
try {
|
|
3459
4475
|
const stat = statSync2(absPath);
|
|
3460
4476
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
3461
|
-
const content =
|
|
4477
|
+
const content = readFileSync6(absPath, "utf8");
|
|
3462
4478
|
const lines = content.split("\n");
|
|
3463
4479
|
const matches = content.match(regex);
|
|
3464
4480
|
if (!matches || matches.length === 0) continue;
|
|
@@ -3481,15 +4497,15 @@ function collectFiles(root, patterns) {
|
|
|
3481
4497
|
const files = [];
|
|
3482
4498
|
function walk(dir, relative) {
|
|
3483
4499
|
if (files.length >= MAX_FILES) return;
|
|
3484
|
-
const basename =
|
|
4500
|
+
const basename = path11.basename(dir);
|
|
3485
4501
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
3486
4502
|
try {
|
|
3487
4503
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
3488
4504
|
for (const entry of entries) {
|
|
3489
4505
|
if (files.length >= MAX_FILES) return;
|
|
3490
|
-
const rel =
|
|
4506
|
+
const rel = path11.join(relative, entry.name);
|
|
3491
4507
|
if (entry.isDirectory()) {
|
|
3492
|
-
walk(
|
|
4508
|
+
walk(path11.join(dir, entry.name), rel);
|
|
3493
4509
|
} else if (entry.isFile()) {
|
|
3494
4510
|
for (const pat of patterns) {
|
|
3495
4511
|
if (matchGlob(rel, pat)) {
|
|
@@ -3521,7 +4537,7 @@ function matchGlob(filePath, pattern) {
|
|
|
3521
4537
|
if (slashIdx !== -1) {
|
|
3522
4538
|
const dir = pattern.slice(0, slashIdx);
|
|
3523
4539
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
3524
|
-
const fileDir =
|
|
4540
|
+
const fileDir = path11.dirname(filePath);
|
|
3525
4541
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
3526
4542
|
}
|
|
3527
4543
|
const ext = pattern.replace("*", "");
|
|
@@ -3529,9 +4545,9 @@ function matchGlob(filePath, pattern) {
|
|
|
3529
4545
|
}
|
|
3530
4546
|
function buildSnippet(hit, projectRoot) {
|
|
3531
4547
|
try {
|
|
3532
|
-
const absPath =
|
|
3533
|
-
if (!
|
|
3534
|
-
const lines =
|
|
4548
|
+
const absPath = path11.join(projectRoot, hit.filePath);
|
|
4549
|
+
if (!existsSync10(absPath)) return hit.matchLine;
|
|
4550
|
+
const lines = readFileSync6(absPath, "utf8").split("\n");
|
|
3535
4551
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
3536
4552
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
3537
4553
|
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
@@ -4196,13 +5212,13 @@ var init_session_key = __esm({
|
|
|
4196
5212
|
});
|
|
4197
5213
|
|
|
4198
5214
|
// src/lib/session-registry.ts
|
|
4199
|
-
import { readFileSync as
|
|
4200
|
-
import
|
|
4201
|
-
import
|
|
5215
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync11 } from "fs";
|
|
5216
|
+
import path13 from "path";
|
|
5217
|
+
import os6 from "os";
|
|
4202
5218
|
function registerSession(entry) {
|
|
4203
|
-
const dir =
|
|
4204
|
-
if (!
|
|
4205
|
-
|
|
5219
|
+
const dir = path13.dirname(REGISTRY_PATH);
|
|
5220
|
+
if (!existsSync11(dir)) {
|
|
5221
|
+
mkdirSync4(dir, { recursive: true });
|
|
4206
5222
|
}
|
|
4207
5223
|
const sessions = listSessions();
|
|
4208
5224
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -4211,11 +5227,11 @@ function registerSession(entry) {
|
|
|
4211
5227
|
} else {
|
|
4212
5228
|
sessions.push(entry);
|
|
4213
5229
|
}
|
|
4214
|
-
|
|
5230
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
4215
5231
|
}
|
|
4216
5232
|
function listSessions() {
|
|
4217
5233
|
try {
|
|
4218
|
-
const raw =
|
|
5234
|
+
const raw = readFileSync8(REGISTRY_PATH, "utf8");
|
|
4219
5235
|
return JSON.parse(raw);
|
|
4220
5236
|
} catch {
|
|
4221
5237
|
return [];
|
|
@@ -4225,7 +5241,7 @@ var REGISTRY_PATH;
|
|
|
4225
5241
|
var init_session_registry = __esm({
|
|
4226
5242
|
"src/lib/session-registry.ts"() {
|
|
4227
5243
|
"use strict";
|
|
4228
|
-
REGISTRY_PATH =
|
|
5244
|
+
REGISTRY_PATH = path13.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
4229
5245
|
}
|
|
4230
5246
|
});
|
|
4231
5247
|
|
|
@@ -4417,118 +5433,6 @@ var init_provider_table = __esm({
|
|
|
4417
5433
|
}
|
|
4418
5434
|
});
|
|
4419
5435
|
|
|
4420
|
-
// src/lib/runtime-table.ts
|
|
4421
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
4422
|
-
var init_runtime_table = __esm({
|
|
4423
|
-
"src/lib/runtime-table.ts"() {
|
|
4424
|
-
"use strict";
|
|
4425
|
-
RUNTIME_TABLE = {
|
|
4426
|
-
codex: {
|
|
4427
|
-
binary: "codex",
|
|
4428
|
-
launchMode: "interactive",
|
|
4429
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
4430
|
-
inlineFlag: "--no-alt-screen",
|
|
4431
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
4432
|
-
defaultModel: "gpt-5.4"
|
|
4433
|
-
},
|
|
4434
|
-
opencode: {
|
|
4435
|
-
binary: "opencode",
|
|
4436
|
-
launchMode: "exec",
|
|
4437
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
4438
|
-
inlineFlag: "",
|
|
4439
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
4440
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
4441
|
-
}
|
|
4442
|
-
};
|
|
4443
|
-
DEFAULT_RUNTIME = "claude";
|
|
4444
|
-
}
|
|
4445
|
-
});
|
|
4446
|
-
|
|
4447
|
-
// src/lib/agent-config.ts
|
|
4448
|
-
var agent_config_exports = {};
|
|
4449
|
-
__export(agent_config_exports, {
|
|
4450
|
-
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
4451
|
-
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
4452
|
-
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
4453
|
-
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
4454
|
-
clearAgentRuntime: () => clearAgentRuntime,
|
|
4455
|
-
getAgentRuntime: () => getAgentRuntime,
|
|
4456
|
-
loadAgentConfig: () => loadAgentConfig,
|
|
4457
|
-
saveAgentConfig: () => saveAgentConfig,
|
|
4458
|
-
setAgentRuntime: () => setAgentRuntime
|
|
4459
|
-
});
|
|
4460
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
4461
|
-
import path11 from "path";
|
|
4462
|
-
function loadAgentConfig() {
|
|
4463
|
-
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
4464
|
-
try {
|
|
4465
|
-
return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
|
|
4466
|
-
} catch {
|
|
4467
|
-
return {};
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
function saveAgentConfig(config) {
|
|
4471
|
-
const dir = path11.dirname(AGENT_CONFIG_PATH);
|
|
4472
|
-
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
4473
|
-
writeFileSync4(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
4474
|
-
}
|
|
4475
|
-
function getAgentRuntime(agentId) {
|
|
4476
|
-
const config = loadAgentConfig();
|
|
4477
|
-
const entry = config[agentId];
|
|
4478
|
-
if (entry) return entry;
|
|
4479
|
-
const orgDefault = config["default"];
|
|
4480
|
-
if (orgDefault) return orgDefault;
|
|
4481
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
4482
|
-
}
|
|
4483
|
-
function setAgentRuntime(agentId, runtime, model) {
|
|
4484
|
-
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
4485
|
-
if (!knownModels) {
|
|
4486
|
-
return {
|
|
4487
|
-
ok: false,
|
|
4488
|
-
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
4489
|
-
};
|
|
4490
|
-
}
|
|
4491
|
-
if (!knownModels.includes(model)) {
|
|
4492
|
-
return {
|
|
4493
|
-
ok: false,
|
|
4494
|
-
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
4495
|
-
};
|
|
4496
|
-
}
|
|
4497
|
-
const config = loadAgentConfig();
|
|
4498
|
-
config[agentId] = { runtime, model };
|
|
4499
|
-
saveAgentConfig(config);
|
|
4500
|
-
return { ok: true };
|
|
4501
|
-
}
|
|
4502
|
-
function clearAgentRuntime(agentId) {
|
|
4503
|
-
const config = loadAgentConfig();
|
|
4504
|
-
delete config[agentId];
|
|
4505
|
-
saveAgentConfig(config);
|
|
4506
|
-
}
|
|
4507
|
-
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
4508
|
-
var init_agent_config = __esm({
|
|
4509
|
-
"src/lib/agent-config.ts"() {
|
|
4510
|
-
"use strict";
|
|
4511
|
-
init_config();
|
|
4512
|
-
init_runtime_table();
|
|
4513
|
-
AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4514
|
-
KNOWN_RUNTIMES = {
|
|
4515
|
-
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
4516
|
-
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
4517
|
-
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
4518
|
-
};
|
|
4519
|
-
RUNTIME_LABELS = {
|
|
4520
|
-
claude: "Claude Code (Anthropic)",
|
|
4521
|
-
codex: "Codex (OpenAI)",
|
|
4522
|
-
opencode: "OpenCode (open source)"
|
|
4523
|
-
};
|
|
4524
|
-
DEFAULT_MODELS = {
|
|
4525
|
-
claude: "claude-opus-4",
|
|
4526
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
4527
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
4528
|
-
};
|
|
4529
|
-
}
|
|
4530
|
-
});
|
|
4531
|
-
|
|
4532
5436
|
// src/lib/intercom-queue.ts
|
|
4533
5437
|
var intercom_queue_exports = {};
|
|
4534
5438
|
__export(intercom_queue_exports, {
|
|
@@ -4538,17 +5442,17 @@ __export(intercom_queue_exports, {
|
|
|
4538
5442
|
queueIntercom: () => queueIntercom,
|
|
4539
5443
|
readQueue: () => readQueue
|
|
4540
5444
|
});
|
|
4541
|
-
import { readFileSync as
|
|
4542
|
-
import
|
|
4543
|
-
import
|
|
5445
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
|
|
5446
|
+
import path14 from "path";
|
|
5447
|
+
import os7 from "os";
|
|
4544
5448
|
function ensureDir() {
|
|
4545
|
-
const dir =
|
|
4546
|
-
if (!
|
|
5449
|
+
const dir = path14.dirname(QUEUE_PATH);
|
|
5450
|
+
if (!existsSync12(dir)) mkdirSync5(dir, { recursive: true });
|
|
4547
5451
|
}
|
|
4548
5452
|
function readQueue() {
|
|
4549
5453
|
try {
|
|
4550
|
-
if (!
|
|
4551
|
-
return JSON.parse(
|
|
5454
|
+
if (!existsSync12(QUEUE_PATH)) return [];
|
|
5455
|
+
return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
|
|
4552
5456
|
} catch {
|
|
4553
5457
|
return [];
|
|
4554
5458
|
}
|
|
@@ -4556,7 +5460,7 @@ function readQueue() {
|
|
|
4556
5460
|
function writeQueue(queue) {
|
|
4557
5461
|
ensureDir();
|
|
4558
5462
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
4559
|
-
|
|
5463
|
+
writeFileSync6(tmp, JSON.stringify(queue, null, 2));
|
|
4560
5464
|
renameSync3(tmp, QUEUE_PATH);
|
|
4561
5465
|
}
|
|
4562
5466
|
function queueIntercom(targetSession, reason) {
|
|
@@ -4648,26 +5552,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
4648
5552
|
var init_intercom_queue = __esm({
|
|
4649
5553
|
"src/lib/intercom-queue.ts"() {
|
|
4650
5554
|
"use strict";
|
|
4651
|
-
QUEUE_PATH =
|
|
5555
|
+
QUEUE_PATH = path14.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
4652
5556
|
MAX_RETRIES2 = 5;
|
|
4653
5557
|
TTL_MS = 60 * 60 * 1e3;
|
|
4654
|
-
INTERCOM_LOG =
|
|
5558
|
+
INTERCOM_LOG = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4655
5559
|
}
|
|
4656
5560
|
});
|
|
4657
5561
|
|
|
4658
5562
|
// src/lib/license.ts
|
|
4659
|
-
import { readFileSync as
|
|
5563
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
|
|
4660
5564
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4661
|
-
import
|
|
5565
|
+
import { createRequire as createRequire2 } from "module";
|
|
5566
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5567
|
+
import os8 from "os";
|
|
5568
|
+
import path15 from "path";
|
|
4662
5569
|
import { jwtVerify, importSPKI } from "jose";
|
|
4663
5570
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
4664
5571
|
var init_license = __esm({
|
|
4665
5572
|
"src/lib/license.ts"() {
|
|
4666
5573
|
"use strict";
|
|
4667
5574
|
init_config();
|
|
4668
|
-
LICENSE_PATH =
|
|
4669
|
-
CACHE_PATH =
|
|
4670
|
-
DEVICE_ID_PATH =
|
|
5575
|
+
LICENSE_PATH = path15.join(EXE_AI_DIR, "license.key");
|
|
5576
|
+
CACHE_PATH = path15.join(EXE_AI_DIR, "license-cache.json");
|
|
5577
|
+
DEVICE_ID_PATH = path15.join(EXE_AI_DIR, "device-id");
|
|
4671
5578
|
PLAN_LIMITS = {
|
|
4672
5579
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
4673
5580
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -4679,12 +5586,12 @@ var init_license = __esm({
|
|
|
4679
5586
|
});
|
|
4680
5587
|
|
|
4681
5588
|
// src/lib/plan-limits.ts
|
|
4682
|
-
import { readFileSync as
|
|
4683
|
-
import
|
|
5589
|
+
import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
|
|
5590
|
+
import path16 from "path";
|
|
4684
5591
|
function getLicenseSync() {
|
|
4685
5592
|
try {
|
|
4686
|
-
if (!
|
|
4687
|
-
const raw = JSON.parse(
|
|
5593
|
+
if (!existsSync14(CACHE_PATH2)) return freeLicense();
|
|
5594
|
+
const raw = JSON.parse(readFileSync11(CACHE_PATH2, "utf8"));
|
|
4688
5595
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
4689
5596
|
const parts = raw.token.split(".");
|
|
4690
5597
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -4722,8 +5629,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
4722
5629
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
4723
5630
|
let count = 0;
|
|
4724
5631
|
try {
|
|
4725
|
-
if (
|
|
4726
|
-
const raw =
|
|
5632
|
+
if (existsSync14(filePath)) {
|
|
5633
|
+
const raw = readFileSync11(filePath, "utf8");
|
|
4727
5634
|
const employees = JSON.parse(raw);
|
|
4728
5635
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
4729
5636
|
}
|
|
@@ -4752,29 +5659,63 @@ var init_plan_limits = __esm({
|
|
|
4752
5659
|
this.name = "PlanLimitError";
|
|
4753
5660
|
}
|
|
4754
5661
|
};
|
|
4755
|
-
CACHE_PATH2 =
|
|
5662
|
+
CACHE_PATH2 = path16.join(EXE_AI_DIR, "license-cache.json");
|
|
5663
|
+
}
|
|
5664
|
+
});
|
|
5665
|
+
|
|
5666
|
+
// src/lib/task-scope.ts
|
|
5667
|
+
function getCurrentSessionScope() {
|
|
5668
|
+
try {
|
|
5669
|
+
return resolveExeSession();
|
|
5670
|
+
} catch {
|
|
5671
|
+
return null;
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
5675
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5676
|
+
if (!scope) return { sql: "", args: [] };
|
|
5677
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5678
|
+
return {
|
|
5679
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
5680
|
+
args: [scope]
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5684
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5685
|
+
if (!scope) return { sql: "", args: [] };
|
|
5686
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5687
|
+
return {
|
|
5688
|
+
sql: ` AND ${col} = ?`,
|
|
5689
|
+
args: [scope]
|
|
5690
|
+
};
|
|
5691
|
+
}
|
|
5692
|
+
var init_task_scope = __esm({
|
|
5693
|
+
"src/lib/task-scope.ts"() {
|
|
5694
|
+
"use strict";
|
|
5695
|
+
init_tmux_routing();
|
|
4756
5696
|
}
|
|
4757
5697
|
});
|
|
4758
5698
|
|
|
4759
5699
|
// src/lib/notifications.ts
|
|
4760
|
-
import
|
|
4761
|
-
import
|
|
4762
|
-
import
|
|
5700
|
+
import crypto3 from "crypto";
|
|
5701
|
+
import path17 from "path";
|
|
5702
|
+
import os9 from "os";
|
|
4763
5703
|
import {
|
|
4764
|
-
readFileSync as
|
|
5704
|
+
readFileSync as readFileSync12,
|
|
4765
5705
|
readdirSync as readdirSync4,
|
|
4766
5706
|
unlinkSync as unlinkSync4,
|
|
4767
|
-
existsSync as
|
|
5707
|
+
existsSync as existsSync15,
|
|
4768
5708
|
rmdirSync
|
|
4769
5709
|
} from "fs";
|
|
4770
5710
|
async function writeNotification(notification) {
|
|
4771
5711
|
try {
|
|
4772
5712
|
const client = getClient();
|
|
4773
|
-
const id =
|
|
5713
|
+
const id = crypto3.randomUUID();
|
|
4774
5714
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5715
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
4775
5716
|
await client.execute({
|
|
4776
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
4777
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
5717
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
5718
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4778
5719
|
args: [
|
|
4779
5720
|
id,
|
|
4780
5721
|
notification.agentId,
|
|
@@ -4783,6 +5724,7 @@ async function writeNotification(notification) {
|
|
|
4783
5724
|
notification.project,
|
|
4784
5725
|
notification.summary,
|
|
4785
5726
|
notification.taskFile ?? null,
|
|
5727
|
+
sessionScope,
|
|
4786
5728
|
now
|
|
4787
5729
|
]
|
|
4788
5730
|
});
|
|
@@ -4791,12 +5733,14 @@ async function writeNotification(notification) {
|
|
|
4791
5733
|
`);
|
|
4792
5734
|
}
|
|
4793
5735
|
}
|
|
4794
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
5736
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
4795
5737
|
try {
|
|
4796
5738
|
const client = getClient();
|
|
5739
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
4797
5740
|
await client.execute({
|
|
4798
|
-
sql:
|
|
4799
|
-
|
|
5741
|
+
sql: `UPDATE notifications SET read = 1
|
|
5742
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
5743
|
+
args: [taskFile, ...scope.args]
|
|
4800
5744
|
});
|
|
4801
5745
|
} catch {
|
|
4802
5746
|
}
|
|
@@ -4805,11 +5749,12 @@ var init_notifications = __esm({
|
|
|
4805
5749
|
"src/lib/notifications.ts"() {
|
|
4806
5750
|
"use strict";
|
|
4807
5751
|
init_database();
|
|
5752
|
+
init_task_scope();
|
|
4808
5753
|
}
|
|
4809
5754
|
});
|
|
4810
5755
|
|
|
4811
5756
|
// src/lib/session-kill-telemetry.ts
|
|
4812
|
-
import
|
|
5757
|
+
import crypto4 from "crypto";
|
|
4813
5758
|
async function recordSessionKill(input2) {
|
|
4814
5759
|
try {
|
|
4815
5760
|
const client = getClient();
|
|
@@ -4819,7 +5764,7 @@ async function recordSessionKill(input2) {
|
|
|
4819
5764
|
ticks_idle, estimated_tokens_saved)
|
|
4820
5765
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
4821
5766
|
args: [
|
|
4822
|
-
|
|
5767
|
+
crypto4.randomUUID(),
|
|
4823
5768
|
input2.sessionName,
|
|
4824
5769
|
input2.agentId,
|
|
4825
5770
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4842,37 +5787,13 @@ var init_session_kill_telemetry = __esm({
|
|
|
4842
5787
|
}
|
|
4843
5788
|
});
|
|
4844
5789
|
|
|
4845
|
-
// src/lib/task-scope.ts
|
|
4846
|
-
function getCurrentSessionScope() {
|
|
4847
|
-
try {
|
|
4848
|
-
return resolveExeSession();
|
|
4849
|
-
} catch {
|
|
4850
|
-
return null;
|
|
4851
|
-
}
|
|
4852
|
-
}
|
|
4853
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
4854
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
4855
|
-
if (!scope) return { sql: "", args: [] };
|
|
4856
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
4857
|
-
return {
|
|
4858
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
4859
|
-
args: [scope]
|
|
4860
|
-
};
|
|
4861
|
-
}
|
|
4862
|
-
var init_task_scope = __esm({
|
|
4863
|
-
"src/lib/task-scope.ts"() {
|
|
4864
|
-
"use strict";
|
|
4865
|
-
init_tmux_routing();
|
|
4866
|
-
}
|
|
4867
|
-
});
|
|
4868
|
-
|
|
4869
5790
|
// src/lib/tasks-crud.ts
|
|
4870
|
-
import
|
|
4871
|
-
import
|
|
4872
|
-
import
|
|
5791
|
+
import crypto5 from "crypto";
|
|
5792
|
+
import path18 from "path";
|
|
5793
|
+
import os10 from "os";
|
|
4873
5794
|
import { execSync as execSync7 } from "child_process";
|
|
4874
5795
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4875
|
-
import { existsSync as
|
|
5796
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
4876
5797
|
async function writeCheckpoint(input2) {
|
|
4877
5798
|
const client = getClient();
|
|
4878
5799
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -4988,7 +5909,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4988
5909
|
}
|
|
4989
5910
|
async function createTaskCore(input2) {
|
|
4990
5911
|
const client = getClient();
|
|
4991
|
-
const id =
|
|
5912
|
+
const id = crypto5.randomUUID();
|
|
4992
5913
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4993
5914
|
const slug = slugify(input2.title);
|
|
4994
5915
|
let earlySessionScope = null;
|
|
@@ -5047,8 +5968,8 @@ ${laneWarning}` : laneWarning;
|
|
|
5047
5968
|
}
|
|
5048
5969
|
if (input2.baseDir) {
|
|
5049
5970
|
try {
|
|
5050
|
-
await mkdir4(
|
|
5051
|
-
await mkdir4(
|
|
5971
|
+
await mkdir4(path18.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
5972
|
+
await mkdir4(path18.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
5052
5973
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
5053
5974
|
await ensureGitignoreExe(input2.baseDir);
|
|
5054
5975
|
} catch {
|
|
@@ -5084,13 +6005,19 @@ ${laneWarning}` : laneWarning;
|
|
|
5084
6005
|
});
|
|
5085
6006
|
if (input2.baseDir) {
|
|
5086
6007
|
try {
|
|
5087
|
-
const EXE_OS_DIR =
|
|
5088
|
-
const mdPath =
|
|
5089
|
-
const mdDir =
|
|
5090
|
-
if (!
|
|
6008
|
+
const EXE_OS_DIR = path18.join(os10.homedir(), ".exe-os");
|
|
6009
|
+
const mdPath = path18.join(EXE_OS_DIR, taskFile);
|
|
6010
|
+
const mdDir = path18.dirname(mdPath);
|
|
6011
|
+
if (!existsSync16(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
5091
6012
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
5092
6013
|
const mdContent = `# ${input2.title}
|
|
5093
6014
|
|
|
6015
|
+
## MANDATORY: When done
|
|
6016
|
+
|
|
6017
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
6018
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
6019
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
6020
|
+
|
|
5094
6021
|
**ID:** ${id}
|
|
5095
6022
|
**Status:** ${initialStatus}
|
|
5096
6023
|
**Priority:** ${input2.priority}
|
|
@@ -5104,12 +6031,6 @@ ${laneWarning}` : laneWarning;
|
|
|
5104
6031
|
## Context
|
|
5105
6032
|
|
|
5106
6033
|
${input2.context}
|
|
5107
|
-
|
|
5108
|
-
## MANDATORY: When done
|
|
5109
|
-
|
|
5110
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
5111
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
5112
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
5113
6034
|
`;
|
|
5114
6035
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
5115
6036
|
} catch (err) {
|
|
@@ -5358,7 +6279,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
5358
6279
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
5359
6280
|
} catch {
|
|
5360
6281
|
}
|
|
5361
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
6282
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
5362
6283
|
try {
|
|
5363
6284
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
5364
6285
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -5387,9 +6308,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
5387
6308
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5388
6309
|
}
|
|
5389
6310
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5390
|
-
const archPath =
|
|
6311
|
+
const archPath = path18.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5391
6312
|
try {
|
|
5392
|
-
if (
|
|
6313
|
+
if (existsSync16(archPath)) return;
|
|
5393
6314
|
const template = [
|
|
5394
6315
|
`# ${projectName} \u2014 System Architecture`,
|
|
5395
6316
|
"",
|
|
@@ -5422,10 +6343,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
5422
6343
|
}
|
|
5423
6344
|
}
|
|
5424
6345
|
async function ensureGitignoreExe(baseDir) {
|
|
5425
|
-
const gitignorePath =
|
|
6346
|
+
const gitignorePath = path18.join(baseDir, ".gitignore");
|
|
5426
6347
|
try {
|
|
5427
|
-
if (
|
|
5428
|
-
const content =
|
|
6348
|
+
if (existsSync16(gitignorePath)) {
|
|
6349
|
+
const content = readFileSync13(gitignorePath, "utf-8");
|
|
5429
6350
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
5430
6351
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
5431
6352
|
} else {
|
|
@@ -5468,8 +6389,8 @@ __export(tasks_review_exports, {
|
|
|
5468
6389
|
isStale: () => isStale,
|
|
5469
6390
|
listPendingReviews: () => listPendingReviews
|
|
5470
6391
|
});
|
|
5471
|
-
import
|
|
5472
|
-
import { existsSync as
|
|
6392
|
+
import path19 from "path";
|
|
6393
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
5473
6394
|
function formatAge(isoTimestamp) {
|
|
5474
6395
|
if (!isoTimestamp) return "";
|
|
5475
6396
|
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
@@ -5487,54 +6408,38 @@ function isStale(isoTimestamp) {
|
|
|
5487
6408
|
}
|
|
5488
6409
|
async function countPendingReviews(sessionScope) {
|
|
5489
6410
|
const client = getClient();
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
args: [sessionScope]
|
|
5494
|
-
});
|
|
5495
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5496
|
-
}
|
|
6411
|
+
const scope = strictSessionScopeFilter(
|
|
6412
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6413
|
+
);
|
|
5497
6414
|
const result = await client.execute({
|
|
5498
|
-
sql:
|
|
5499
|
-
|
|
6415
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
6416
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
6417
|
+
args: [...scope.args]
|
|
5500
6418
|
});
|
|
5501
6419
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5502
6420
|
}
|
|
5503
6421
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
5504
6422
|
const client = getClient();
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
5509
|
-
AND session_scope = ?`,
|
|
5510
|
-
args: [sinceIso, sessionScope]
|
|
5511
|
-
});
|
|
5512
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
5513
|
-
}
|
|
6423
|
+
const scope = strictSessionScopeFilter(
|
|
6424
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6425
|
+
);
|
|
5514
6426
|
const result = await client.execute({
|
|
5515
6427
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5516
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
5517
|
-
args: [sinceIso]
|
|
6428
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
6429
|
+
args: [sinceIso, ...scope.args]
|
|
5518
6430
|
});
|
|
5519
6431
|
return Number(result.rows[0]?.cnt) || 0;
|
|
5520
6432
|
}
|
|
5521
6433
|
async function listPendingReviews(limit, sessionScope) {
|
|
5522
6434
|
const client = getClient();
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
WHERE status = 'needs_review'
|
|
5527
|
-
AND session_scope = ?
|
|
5528
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
5529
|
-
args: [sessionScope, limit]
|
|
5530
|
-
});
|
|
5531
|
-
return result2.rows;
|
|
5532
|
-
}
|
|
6435
|
+
const scope = strictSessionScopeFilter(
|
|
6436
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
6437
|
+
);
|
|
5533
6438
|
const result = await client.execute({
|
|
5534
6439
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
5535
|
-
WHERE status = 'needs_review'
|
|
6440
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
5536
6441
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
5537
|
-
args: [limit]
|
|
6442
|
+
args: [...scope.args, limit]
|
|
5538
6443
|
});
|
|
5539
6444
|
return result.rows;
|
|
5540
6445
|
}
|
|
@@ -5546,7 +6451,7 @@ async function cleanupOrphanedReviews() {
|
|
|
5546
6451
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
5547
6452
|
AND assigned_by = 'system'
|
|
5548
6453
|
AND title LIKE 'Review:%'
|
|
5549
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
6454
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
5550
6455
|
args: [now]
|
|
5551
6456
|
});
|
|
5552
6457
|
const r1b = await client.execute({
|
|
@@ -5754,11 +6659,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5754
6659
|
);
|
|
5755
6660
|
}
|
|
5756
6661
|
try {
|
|
5757
|
-
const cacheDir =
|
|
5758
|
-
if (
|
|
6662
|
+
const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
|
|
6663
|
+
if (existsSync17(cacheDir)) {
|
|
5759
6664
|
for (const f of readdirSync5(cacheDir)) {
|
|
5760
6665
|
if (f.startsWith("review-notified-")) {
|
|
5761
|
-
unlinkSync5(
|
|
6666
|
+
unlinkSync5(path19.join(cacheDir, f));
|
|
5762
6667
|
}
|
|
5763
6668
|
}
|
|
5764
6669
|
}
|
|
@@ -5775,11 +6680,12 @@ var init_tasks_review = __esm({
|
|
|
5775
6680
|
init_tmux_routing();
|
|
5776
6681
|
init_session_key();
|
|
5777
6682
|
init_state_bus();
|
|
6683
|
+
init_task_scope();
|
|
5778
6684
|
}
|
|
5779
6685
|
});
|
|
5780
6686
|
|
|
5781
6687
|
// src/lib/tasks-chain.ts
|
|
5782
|
-
import
|
|
6688
|
+
import path20 from "path";
|
|
5783
6689
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
5784
6690
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
5785
6691
|
const client = getClient();
|
|
@@ -5796,7 +6702,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5796
6702
|
});
|
|
5797
6703
|
for (const ur of unblockedRows.rows) {
|
|
5798
6704
|
try {
|
|
5799
|
-
const ubFile =
|
|
6705
|
+
const ubFile = path20.join(baseDir, String(ur.task_file));
|
|
5800
6706
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
5801
6707
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
5802
6708
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -5831,7 +6737,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
5831
6737
|
const scScope = sessionScopeFilter();
|
|
5832
6738
|
const remaining = await client.execute({
|
|
5833
6739
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5834
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
6740
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
5835
6741
|
args: [parentTaskId, ...scScope.args]
|
|
5836
6742
|
});
|
|
5837
6743
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -5992,10 +6898,10 @@ var init_tasks_notify = __esm({
|
|
|
5992
6898
|
});
|
|
5993
6899
|
|
|
5994
6900
|
// src/lib/behaviors.ts
|
|
5995
|
-
import
|
|
6901
|
+
import crypto6 from "crypto";
|
|
5996
6902
|
async function storeBehavior(opts) {
|
|
5997
6903
|
const client = getClient();
|
|
5998
|
-
const id =
|
|
6904
|
+
const id = crypto6.randomUUID();
|
|
5999
6905
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6000
6906
|
await client.execute({
|
|
6001
6907
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -6024,7 +6930,7 @@ __export(skill_learning_exports, {
|
|
|
6024
6930
|
storeTrajectory: () => storeTrajectory,
|
|
6025
6931
|
sweepTrajectories: () => sweepTrajectories
|
|
6026
6932
|
});
|
|
6027
|
-
import
|
|
6933
|
+
import crypto7 from "crypto";
|
|
6028
6934
|
async function extractTrajectory(taskId, agentId) {
|
|
6029
6935
|
const client = getClient();
|
|
6030
6936
|
const result = await client.execute({
|
|
@@ -6053,11 +6959,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
6053
6959
|
return signature;
|
|
6054
6960
|
}
|
|
6055
6961
|
function hashSignature(signature) {
|
|
6056
|
-
return
|
|
6962
|
+
return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
6057
6963
|
}
|
|
6058
6964
|
async function storeTrajectory(opts) {
|
|
6059
6965
|
const client = getClient();
|
|
6060
|
-
const id =
|
|
6966
|
+
const id = crypto7.randomUUID();
|
|
6061
6967
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6062
6968
|
const signatureHash = hashSignature(opts.signature);
|
|
6063
6969
|
await client.execute({
|
|
@@ -6322,8 +7228,8 @@ __export(tasks_exports, {
|
|
|
6322
7228
|
updateTaskStatus: () => updateTaskStatus,
|
|
6323
7229
|
writeCheckpoint: () => writeCheckpoint
|
|
6324
7230
|
});
|
|
6325
|
-
import
|
|
6326
|
-
import { writeFileSync as
|
|
7231
|
+
import path21 from "path";
|
|
7232
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
|
|
6327
7233
|
async function createTask(input2) {
|
|
6328
7234
|
const result = await createTaskCore(input2);
|
|
6329
7235
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -6342,12 +7248,12 @@ async function updateTask(input2) {
|
|
|
6342
7248
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
6343
7249
|
try {
|
|
6344
7250
|
const agent = String(row.assigned_to);
|
|
6345
|
-
const cacheDir =
|
|
6346
|
-
const cachePath =
|
|
7251
|
+
const cacheDir = path21.join(EXE_AI_DIR, "session-cache");
|
|
7252
|
+
const cachePath = path21.join(cacheDir, `current-task-${agent}.json`);
|
|
6347
7253
|
if (input2.status === "in_progress") {
|
|
6348
7254
|
mkdirSync7(cacheDir, { recursive: true });
|
|
6349
|
-
|
|
6350
|
-
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
7255
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
7256
|
+
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
|
|
6351
7257
|
try {
|
|
6352
7258
|
unlinkSync6(cachePath);
|
|
6353
7259
|
} catch {
|
|
@@ -6355,10 +7261,10 @@ async function updateTask(input2) {
|
|
|
6355
7261
|
}
|
|
6356
7262
|
} catch {
|
|
6357
7263
|
}
|
|
6358
|
-
if (input2.status === "done") {
|
|
7264
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
6359
7265
|
await cleanupReviewFile(row, taskFile, input2.baseDir);
|
|
6360
7266
|
}
|
|
6361
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
7267
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
6362
7268
|
try {
|
|
6363
7269
|
const client = getClient();
|
|
6364
7270
|
const taskTitle = String(row.title);
|
|
@@ -6374,7 +7280,7 @@ async function updateTask(input2) {
|
|
|
6374
7280
|
if (!isCoordinatorName(assignedAgent)) {
|
|
6375
7281
|
try {
|
|
6376
7282
|
const draftClient = getClient();
|
|
6377
|
-
if (input2.status === "done") {
|
|
7283
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
6378
7284
|
await draftClient.execute({
|
|
6379
7285
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
6380
7286
|
args: [assignedAgent]
|
|
@@ -6391,7 +7297,7 @@ async function updateTask(input2) {
|
|
|
6391
7297
|
try {
|
|
6392
7298
|
const client = getClient();
|
|
6393
7299
|
const cascaded = await client.execute({
|
|
6394
|
-
sql: `UPDATE tasks SET status = '
|
|
7300
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
6395
7301
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
6396
7302
|
args: [now, taskId]
|
|
6397
7303
|
});
|
|
@@ -6404,14 +7310,14 @@ async function updateTask(input2) {
|
|
|
6404
7310
|
} catch {
|
|
6405
7311
|
}
|
|
6406
7312
|
}
|
|
6407
|
-
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
7313
|
+
const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
|
|
6408
7314
|
if (isTerminal) {
|
|
6409
7315
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
6410
7316
|
if (!isCoordinator) {
|
|
6411
7317
|
notifyTaskDone();
|
|
6412
7318
|
}
|
|
6413
7319
|
await markTaskNotificationsRead(taskFile);
|
|
6414
|
-
if (input2.status === "done") {
|
|
7320
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
6415
7321
|
try {
|
|
6416
7322
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
6417
7323
|
} catch {
|
|
@@ -6431,7 +7337,7 @@ async function updateTask(input2) {
|
|
|
6431
7337
|
}
|
|
6432
7338
|
}
|
|
6433
7339
|
}
|
|
6434
|
-
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
7340
|
+
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6435
7341
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6436
7342
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
6437
7343
|
taskId,
|
|
@@ -6803,6 +7709,7 @@ __export(tmux_routing_exports, {
|
|
|
6803
7709
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
6804
7710
|
isExeSession: () => isExeSession,
|
|
6805
7711
|
isSessionBusy: () => isSessionBusy,
|
|
7712
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
6806
7713
|
notifyParentExe: () => notifyParentExe,
|
|
6807
7714
|
parseParentExe: () => parseParentExe,
|
|
6808
7715
|
registerParentExe: () => registerParentExe,
|
|
@@ -6813,13 +7720,13 @@ __export(tmux_routing_exports, {
|
|
|
6813
7720
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
6814
7721
|
});
|
|
6815
7722
|
import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
|
|
6816
|
-
import { readFileSync as
|
|
6817
|
-
import
|
|
6818
|
-
import
|
|
7723
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync18, appendFileSync, readdirSync as readdirSync6 } from "fs";
|
|
7724
|
+
import path22 from "path";
|
|
7725
|
+
import os11 from "os";
|
|
6819
7726
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6820
7727
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
6821
7728
|
function spawnLockPath(sessionName) {
|
|
6822
|
-
return
|
|
7729
|
+
return path22.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
6823
7730
|
}
|
|
6824
7731
|
function isProcessAlive(pid) {
|
|
6825
7732
|
try {
|
|
@@ -6830,13 +7737,13 @@ function isProcessAlive(pid) {
|
|
|
6830
7737
|
}
|
|
6831
7738
|
}
|
|
6832
7739
|
function acquireSpawnLock2(sessionName) {
|
|
6833
|
-
if (!
|
|
7740
|
+
if (!existsSync18(SPAWN_LOCK_DIR)) {
|
|
6834
7741
|
mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
|
|
6835
7742
|
}
|
|
6836
7743
|
const lockFile = spawnLockPath(sessionName);
|
|
6837
|
-
if (
|
|
7744
|
+
if (existsSync18(lockFile)) {
|
|
6838
7745
|
try {
|
|
6839
|
-
const lock = JSON.parse(
|
|
7746
|
+
const lock = JSON.parse(readFileSync14(lockFile, "utf8"));
|
|
6840
7747
|
const age = Date.now() - lock.timestamp;
|
|
6841
7748
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
6842
7749
|
return false;
|
|
@@ -6844,7 +7751,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
6844
7751
|
} catch {
|
|
6845
7752
|
}
|
|
6846
7753
|
}
|
|
6847
|
-
|
|
7754
|
+
writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
6848
7755
|
return true;
|
|
6849
7756
|
}
|
|
6850
7757
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -6856,13 +7763,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
6856
7763
|
function resolveBehaviorsExporterScript() {
|
|
6857
7764
|
try {
|
|
6858
7765
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6859
|
-
const scriptPath =
|
|
6860
|
-
|
|
7766
|
+
const scriptPath = path22.join(
|
|
7767
|
+
path22.dirname(thisFile),
|
|
6861
7768
|
"..",
|
|
6862
7769
|
"bin",
|
|
6863
7770
|
"exe-export-behaviors.js"
|
|
6864
7771
|
);
|
|
6865
|
-
return
|
|
7772
|
+
return existsSync18(scriptPath) ? scriptPath : null;
|
|
6866
7773
|
} catch {
|
|
6867
7774
|
return null;
|
|
6868
7775
|
}
|
|
@@ -6928,12 +7835,12 @@ function extractRootExe(name) {
|
|
|
6928
7835
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
6929
7836
|
}
|
|
6930
7837
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
6931
|
-
if (!
|
|
7838
|
+
if (!existsSync18(SESSION_CACHE)) {
|
|
6932
7839
|
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
6933
7840
|
}
|
|
6934
7841
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
6935
|
-
const filePath =
|
|
6936
|
-
|
|
7842
|
+
const filePath = path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
7843
|
+
writeFileSync9(filePath, JSON.stringify({
|
|
6937
7844
|
parentExe: rootExe,
|
|
6938
7845
|
dispatchedBy: dispatchedBy || rootExe,
|
|
6939
7846
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6941,7 +7848,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
6941
7848
|
}
|
|
6942
7849
|
function getParentExe(sessionKey) {
|
|
6943
7850
|
try {
|
|
6944
|
-
const data = JSON.parse(
|
|
7851
|
+
const data = JSON.parse(readFileSync14(path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
6945
7852
|
return data.parentExe || null;
|
|
6946
7853
|
} catch {
|
|
6947
7854
|
return null;
|
|
@@ -6949,8 +7856,8 @@ function getParentExe(sessionKey) {
|
|
|
6949
7856
|
}
|
|
6950
7857
|
function getDispatchedBy(sessionKey) {
|
|
6951
7858
|
try {
|
|
6952
|
-
const data = JSON.parse(
|
|
6953
|
-
|
|
7859
|
+
const data = JSON.parse(readFileSync14(
|
|
7860
|
+
path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
6954
7861
|
"utf8"
|
|
6955
7862
|
));
|
|
6956
7863
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -7020,8 +7927,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
7020
7927
|
}
|
|
7021
7928
|
function readDebounceState() {
|
|
7022
7929
|
try {
|
|
7023
|
-
if (!
|
|
7024
|
-
const raw = JSON.parse(
|
|
7930
|
+
if (!existsSync18(DEBOUNCE_FILE)) return {};
|
|
7931
|
+
const raw = JSON.parse(readFileSync14(DEBOUNCE_FILE, "utf8"));
|
|
7025
7932
|
const state = {};
|
|
7026
7933
|
for (const [key, val] of Object.entries(raw)) {
|
|
7027
7934
|
if (typeof val === "number") {
|
|
@@ -7037,8 +7944,8 @@ function readDebounceState() {
|
|
|
7037
7944
|
}
|
|
7038
7945
|
function writeDebounceState(state) {
|
|
7039
7946
|
try {
|
|
7040
|
-
if (!
|
|
7041
|
-
|
|
7947
|
+
if (!existsSync18(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
7948
|
+
writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
|
|
7042
7949
|
} catch {
|
|
7043
7950
|
}
|
|
7044
7951
|
}
|
|
@@ -7136,8 +8043,8 @@ function sendIntercom(targetSession) {
|
|
|
7136
8043
|
try {
|
|
7137
8044
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
7138
8045
|
const agent = baseAgentName(rawAgent);
|
|
7139
|
-
const markerPath =
|
|
7140
|
-
if (
|
|
8046
|
+
const markerPath = path22.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
8047
|
+
if (existsSync18(markerPath)) {
|
|
7141
8048
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
7142
8049
|
return "debounced";
|
|
7143
8050
|
}
|
|
@@ -7146,8 +8053,8 @@ function sendIntercom(targetSession) {
|
|
|
7146
8053
|
try {
|
|
7147
8054
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
7148
8055
|
const agent = baseAgentName(rawAgent);
|
|
7149
|
-
const taskDir =
|
|
7150
|
-
if (
|
|
8056
|
+
const taskDir = path22.join(process.cwd(), "exe", agent);
|
|
8057
|
+
if (existsSync18(taskDir)) {
|
|
7151
8058
|
const files = readdirSync6(taskDir).filter(
|
|
7152
8059
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
7153
8060
|
);
|
|
@@ -7207,6 +8114,21 @@ function notifyParentExe(sessionKey) {
|
|
|
7207
8114
|
}
|
|
7208
8115
|
return true;
|
|
7209
8116
|
}
|
|
8117
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
8118
|
+
const transport = getTransport();
|
|
8119
|
+
try {
|
|
8120
|
+
const sessions = transport.listSessions();
|
|
8121
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
8122
|
+
execSync8(
|
|
8123
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
8124
|
+
{ timeout: 3e3 }
|
|
8125
|
+
);
|
|
8126
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
8127
|
+
return true;
|
|
8128
|
+
} catch {
|
|
8129
|
+
return false;
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
7210
8132
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
7211
8133
|
if (isCoordinatorName(employeeName)) {
|
|
7212
8134
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -7280,26 +8202,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7280
8202
|
const transport = getTransport();
|
|
7281
8203
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
7282
8204
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
7283
|
-
const logDir =
|
|
7284
|
-
const logFile =
|
|
7285
|
-
if (!
|
|
8205
|
+
const logDir = path22.join(os11.homedir(), ".exe-os", "session-logs");
|
|
8206
|
+
const logFile = path22.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
8207
|
+
if (!existsSync18(logDir)) {
|
|
7286
8208
|
mkdirSync8(logDir, { recursive: true });
|
|
7287
8209
|
}
|
|
7288
8210
|
transport.kill(sessionName);
|
|
7289
8211
|
let cleanupSuffix = "";
|
|
7290
8212
|
try {
|
|
7291
8213
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
7292
|
-
const cleanupScript =
|
|
7293
|
-
if (
|
|
8214
|
+
const cleanupScript = path22.join(path22.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
8215
|
+
if (existsSync18(cleanupScript)) {
|
|
7294
8216
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
7295
8217
|
}
|
|
7296
8218
|
} catch {
|
|
7297
8219
|
}
|
|
7298
8220
|
try {
|
|
7299
|
-
const claudeJsonPath =
|
|
8221
|
+
const claudeJsonPath = path22.join(os11.homedir(), ".claude.json");
|
|
7300
8222
|
let claudeJson = {};
|
|
7301
8223
|
try {
|
|
7302
|
-
claudeJson = JSON.parse(
|
|
8224
|
+
claudeJson = JSON.parse(readFileSync14(claudeJsonPath, "utf8"));
|
|
7303
8225
|
} catch {
|
|
7304
8226
|
}
|
|
7305
8227
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -7307,17 +8229,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7307
8229
|
const trustDir = opts?.cwd ?? projectDir;
|
|
7308
8230
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
7309
8231
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
7310
|
-
|
|
8232
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
7311
8233
|
} catch {
|
|
7312
8234
|
}
|
|
7313
8235
|
try {
|
|
7314
|
-
const settingsDir =
|
|
8236
|
+
const settingsDir = path22.join(os11.homedir(), ".claude", "projects");
|
|
7315
8237
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
7316
|
-
const projSettingsDir =
|
|
7317
|
-
const settingsPath =
|
|
8238
|
+
const projSettingsDir = path22.join(settingsDir, normalizedKey);
|
|
8239
|
+
const settingsPath = path22.join(projSettingsDir, "settings.json");
|
|
7318
8240
|
let settings = {};
|
|
7319
8241
|
try {
|
|
7320
|
-
settings = JSON.parse(
|
|
8242
|
+
settings = JSON.parse(readFileSync14(settingsPath, "utf8"));
|
|
7321
8243
|
} catch {
|
|
7322
8244
|
}
|
|
7323
8245
|
const perms = settings.permissions ?? {};
|
|
@@ -7346,7 +8268,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7346
8268
|
perms.allow = allow;
|
|
7347
8269
|
settings.permissions = perms;
|
|
7348
8270
|
mkdirSync8(projSettingsDir, { recursive: true });
|
|
7349
|
-
|
|
8271
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
7350
8272
|
}
|
|
7351
8273
|
} catch {
|
|
7352
8274
|
}
|
|
@@ -7361,8 +8283,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7361
8283
|
let behaviorsFlag = "";
|
|
7362
8284
|
let legacyFallbackWarned = false;
|
|
7363
8285
|
if (!useExeAgent && !useBinSymlink) {
|
|
7364
|
-
const identityPath =
|
|
7365
|
-
|
|
8286
|
+
const identityPath = path22.join(
|
|
8287
|
+
os11.homedir(),
|
|
7366
8288
|
".exe-os",
|
|
7367
8289
|
"identity",
|
|
7368
8290
|
`${employeeName}.md`
|
|
@@ -7371,13 +8293,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7371
8293
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
7372
8294
|
if (hasAgentFlag) {
|
|
7373
8295
|
identityFlag = ` --agent ${employeeName}`;
|
|
7374
|
-
} else if (
|
|
8296
|
+
} else if (existsSync18(identityPath)) {
|
|
7375
8297
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
7376
8298
|
legacyFallbackWarned = true;
|
|
7377
8299
|
}
|
|
7378
8300
|
const behaviorsFile = exportBehaviorsSync(
|
|
7379
8301
|
employeeName,
|
|
7380
|
-
|
|
8302
|
+
path22.basename(spawnCwd),
|
|
7381
8303
|
sessionName
|
|
7382
8304
|
);
|
|
7383
8305
|
if (behaviorsFile) {
|
|
@@ -7392,16 +8314,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7392
8314
|
}
|
|
7393
8315
|
let sessionContextFlag = "";
|
|
7394
8316
|
try {
|
|
7395
|
-
const ctxDir =
|
|
8317
|
+
const ctxDir = path22.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7396
8318
|
mkdirSync8(ctxDir, { recursive: true });
|
|
7397
|
-
const ctxFile =
|
|
8319
|
+
const ctxFile = path22.join(ctxDir, `session-context-${sessionName}.md`);
|
|
7398
8320
|
const ctxContent = [
|
|
7399
8321
|
`## Session Context`,
|
|
7400
8322
|
`You are running in tmux session: ${sessionName}.`,
|
|
7401
8323
|
`Your parent coordinator session is ${exeSession}.`,
|
|
7402
8324
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
7403
8325
|
].join("\n");
|
|
7404
|
-
|
|
8326
|
+
writeFileSync9(ctxFile, ctxContent);
|
|
7405
8327
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
7406
8328
|
} catch {
|
|
7407
8329
|
}
|
|
@@ -7478,8 +8400,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
7478
8400
|
transport.pipeLog(sessionName, logFile);
|
|
7479
8401
|
try {
|
|
7480
8402
|
const mySession = getMySession();
|
|
7481
|
-
const dispatchInfo =
|
|
7482
|
-
|
|
8403
|
+
const dispatchInfo = path22.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
8404
|
+
writeFileSync9(dispatchInfo, JSON.stringify({
|
|
7483
8405
|
dispatchedBy: mySession,
|
|
7484
8406
|
rootExe: exeSession,
|
|
7485
8407
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -7553,15 +8475,15 @@ var init_tmux_routing = __esm({
|
|
|
7553
8475
|
init_intercom_queue();
|
|
7554
8476
|
init_plan_limits();
|
|
7555
8477
|
init_employees();
|
|
7556
|
-
SPAWN_LOCK_DIR =
|
|
7557
|
-
SESSION_CACHE =
|
|
8478
|
+
SPAWN_LOCK_DIR = path22.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
8479
|
+
SESSION_CACHE = path22.join(os11.homedir(), ".exe-os", "session-cache");
|
|
7558
8480
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
7559
8481
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
7560
8482
|
VERIFY_PANE_LINES = 200;
|
|
7561
8483
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
7562
8484
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
7563
|
-
INTERCOM_LOG2 =
|
|
7564
|
-
DEBOUNCE_FILE =
|
|
8485
|
+
INTERCOM_LOG2 = path22.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
8486
|
+
DEBOUNCE_FILE = path22.join(SESSION_CACHE, "intercom-debounce.json");
|
|
7565
8487
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
7566
8488
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
7567
8489
|
}
|
|
@@ -7584,10 +8506,10 @@ __export(messaging_exports, {
|
|
|
7584
8506
|
sendMessage: () => sendMessage,
|
|
7585
8507
|
setWsClientSend: () => setWsClientSend
|
|
7586
8508
|
});
|
|
7587
|
-
import
|
|
8509
|
+
import crypto8 from "crypto";
|
|
7588
8510
|
function generateUlid() {
|
|
7589
8511
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
7590
|
-
const random =
|
|
8512
|
+
const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
|
|
7591
8513
|
return (timestamp + random).toUpperCase();
|
|
7592
8514
|
}
|
|
7593
8515
|
function rowToMessage(row) {
|
|
@@ -7598,6 +8520,7 @@ function rowToMessage(row) {
|
|
|
7598
8520
|
targetAgent: row.target_agent,
|
|
7599
8521
|
targetProject: row.target_project ?? null,
|
|
7600
8522
|
targetDevice: row.target_device,
|
|
8523
|
+
sessionScope: row.session_scope ?? null,
|
|
7601
8524
|
content: row.content,
|
|
7602
8525
|
priority: row.priority ?? "normal",
|
|
7603
8526
|
status: row.status ?? "pending",
|
|
@@ -7615,15 +8538,17 @@ async function sendMessage(input2) {
|
|
|
7615
8538
|
const id = generateUlid();
|
|
7616
8539
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7617
8540
|
const targetDevice = input2.targetDevice ?? "local";
|
|
8541
|
+
const sessionScope = input2.sessionScope === void 0 ? resolveExeSession() : input2.sessionScope;
|
|
7618
8542
|
await client.execute({
|
|
7619
|
-
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
|
|
7620
|
-
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
8543
|
+
sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
|
|
8544
|
+
VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
|
|
7621
8545
|
args: [
|
|
7622
8546
|
id,
|
|
7623
8547
|
input2.fromAgent,
|
|
7624
8548
|
input2.targetAgent,
|
|
7625
8549
|
input2.targetProject ?? null,
|
|
7626
8550
|
targetDevice,
|
|
8551
|
+
sessionScope,
|
|
7627
8552
|
input2.content,
|
|
7628
8553
|
input2.priority ?? "normal",
|
|
7629
8554
|
now
|
|
@@ -7637,9 +8562,10 @@ async function sendMessage(input2) {
|
|
|
7637
8562
|
}
|
|
7638
8563
|
} catch {
|
|
7639
8564
|
}
|
|
8565
|
+
const sentScope = strictSessionScopeFilter(sessionScope);
|
|
7640
8566
|
const result = await client.execute({
|
|
7641
|
-
sql:
|
|
7642
|
-
args: [id]
|
|
8567
|
+
sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
|
|
8568
|
+
args: [id, ...sentScope.args]
|
|
7643
8569
|
});
|
|
7644
8570
|
return rowToMessage(result.rows[0]);
|
|
7645
8571
|
}
|
|
@@ -7663,6 +8589,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
|
|
|
7663
8589
|
fromAgent: msg.fromAgent,
|
|
7664
8590
|
targetAgent: msg.targetAgent,
|
|
7665
8591
|
targetProject: msg.targetProject,
|
|
8592
|
+
sessionScope: msg.sessionScope,
|
|
7666
8593
|
content: msg.content,
|
|
7667
8594
|
priority: msg.priority,
|
|
7668
8595
|
createdAt: msg.createdAt
|
|
@@ -7706,7 +8633,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
7706
8633
|
} catch {
|
|
7707
8634
|
const newRetryCount = msg.retryCount + 1;
|
|
7708
8635
|
if (newRetryCount >= MAX_RETRIES3) {
|
|
7709
|
-
await markFailed(messageId, "session unavailable after 10 retries");
|
|
8636
|
+
await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
|
|
7710
8637
|
} else {
|
|
7711
8638
|
await client.execute({
|
|
7712
8639
|
sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
|
|
@@ -7716,85 +8643,101 @@ async function deliverLocalMessage(messageId) {
|
|
|
7716
8643
|
return false;
|
|
7717
8644
|
}
|
|
7718
8645
|
}
|
|
7719
|
-
async function getPendingMessages(targetAgent) {
|
|
8646
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
7720
8647
|
const client = getClient();
|
|
8648
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7721
8649
|
const result = await client.execute({
|
|
7722
8650
|
sql: `SELECT * FROM messages
|
|
7723
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
8651
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
7724
8652
|
ORDER BY id`,
|
|
7725
|
-
args: [targetAgent]
|
|
8653
|
+
args: [targetAgent, ...scope.args]
|
|
7726
8654
|
});
|
|
7727
8655
|
return result.rows.map((row) => rowToMessage(row));
|
|
7728
8656
|
}
|
|
7729
|
-
async function markRead(messageId) {
|
|
8657
|
+
async function markRead(messageId, sessionScope) {
|
|
7730
8658
|
const client = getClient();
|
|
8659
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7731
8660
|
await client.execute({
|
|
7732
|
-
sql:
|
|
7733
|
-
|
|
8661
|
+
sql: `UPDATE messages SET status = 'read'
|
|
8662
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
8663
|
+
args: [messageId, ...scope.args]
|
|
7734
8664
|
});
|
|
7735
8665
|
}
|
|
7736
|
-
async function markAcknowledged(messageId) {
|
|
8666
|
+
async function markAcknowledged(messageId, sessionScope) {
|
|
7737
8667
|
const client = getClient();
|
|
8668
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7738
8669
|
await client.execute({
|
|
7739
|
-
sql:
|
|
7740
|
-
|
|
8670
|
+
sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
|
|
8671
|
+
WHERE id = ? AND status = 'read'${scope.sql}`,
|
|
8672
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
7741
8673
|
});
|
|
7742
8674
|
}
|
|
7743
|
-
async function markProcessed(messageId) {
|
|
8675
|
+
async function markProcessed(messageId, sessionScope) {
|
|
7744
8676
|
const client = getClient();
|
|
8677
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7745
8678
|
await client.execute({
|
|
7746
|
-
sql:
|
|
7747
|
-
|
|
8679
|
+
sql: `UPDATE messages SET status = 'processed', processed_at = ?
|
|
8680
|
+
WHERE id = ?${scope.sql}`,
|
|
8681
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
|
|
7748
8682
|
});
|
|
7749
8683
|
}
|
|
7750
|
-
async function getMessageStatus(messageId) {
|
|
8684
|
+
async function getMessageStatus(messageId, sessionScope) {
|
|
7751
8685
|
const client = getClient();
|
|
8686
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7752
8687
|
const result = await client.execute({
|
|
7753
|
-
sql:
|
|
7754
|
-
args: [messageId]
|
|
8688
|
+
sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
|
|
8689
|
+
args: [messageId, ...scope.args]
|
|
7755
8690
|
});
|
|
7756
8691
|
return result.rows[0]?.status ?? null;
|
|
7757
8692
|
}
|
|
7758
|
-
async function getUnacknowledgedMessages(targetAgent) {
|
|
8693
|
+
async function getUnacknowledgedMessages(targetAgent, sessionScope) {
|
|
7759
8694
|
const client = getClient();
|
|
8695
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7760
8696
|
const result = await client.execute({
|
|
7761
8697
|
sql: `SELECT * FROM messages
|
|
7762
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
|
|
8698
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
|
|
7763
8699
|
ORDER BY id`,
|
|
7764
|
-
args: [targetAgent]
|
|
8700
|
+
args: [targetAgent, ...scope.args]
|
|
7765
8701
|
});
|
|
7766
8702
|
return result.rows.map((row) => rowToMessage(row));
|
|
7767
8703
|
}
|
|
7768
|
-
async function getReadMessages(targetAgent) {
|
|
8704
|
+
async function getReadMessages(targetAgent, sessionScope) {
|
|
7769
8705
|
const client = getClient();
|
|
8706
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7770
8707
|
const result = await client.execute({
|
|
7771
|
-
sql:
|
|
7772
|
-
|
|
8708
|
+
sql: `SELECT * FROM messages
|
|
8709
|
+
WHERE target_agent = ? AND status = 'read'${scope.sql}
|
|
8710
|
+
ORDER BY id`,
|
|
8711
|
+
args: [targetAgent, ...scope.args]
|
|
7773
8712
|
});
|
|
7774
8713
|
return result.rows.map((row) => rowToMessage(row));
|
|
7775
8714
|
}
|
|
7776
|
-
async function markFailed(messageId, reason) {
|
|
8715
|
+
async function markFailed(messageId, reason, sessionScope) {
|
|
7777
8716
|
const client = getClient();
|
|
8717
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7778
8718
|
await client.execute({
|
|
7779
|
-
sql:
|
|
7780
|
-
|
|
8719
|
+
sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
|
|
8720
|
+
WHERE id = ?${scope.sql}`,
|
|
8721
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
|
|
7781
8722
|
});
|
|
7782
8723
|
}
|
|
7783
|
-
async function getFailedMessages() {
|
|
8724
|
+
async function getFailedMessages(sessionScope) {
|
|
7784
8725
|
const client = getClient();
|
|
8726
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7785
8727
|
const result = await client.execute({
|
|
7786
|
-
sql:
|
|
7787
|
-
args: []
|
|
8728
|
+
sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
|
|
8729
|
+
args: [...scope.args]
|
|
7788
8730
|
});
|
|
7789
8731
|
return result.rows.map((row) => rowToMessage(row));
|
|
7790
8732
|
}
|
|
7791
|
-
async function retryPendingMessages() {
|
|
8733
|
+
async function retryPendingMessages(sessionScope) {
|
|
7792
8734
|
const client = getClient();
|
|
8735
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
7793
8736
|
const result = await client.execute({
|
|
7794
8737
|
sql: `SELECT * FROM messages
|
|
7795
|
-
WHERE status = 'pending' AND retry_count <
|
|
8738
|
+
WHERE status = 'pending' AND retry_count < ?${scope.sql}
|
|
7796
8739
|
ORDER BY id`,
|
|
7797
|
-
args: [MAX_RETRIES3]
|
|
8740
|
+
args: [MAX_RETRIES3, ...scope.args]
|
|
7798
8741
|
});
|
|
7799
8742
|
let delivered = 0;
|
|
7800
8743
|
for (const row of result.rows) {
|
|
@@ -7813,6 +8756,7 @@ var init_messaging = __esm({
|
|
|
7813
8756
|
"use strict";
|
|
7814
8757
|
init_database();
|
|
7815
8758
|
init_tmux_routing();
|
|
8759
|
+
init_task_scope();
|
|
7816
8760
|
MAX_RETRIES3 = 10;
|
|
7817
8761
|
_wsClientSend = null;
|
|
7818
8762
|
}
|
|
@@ -7823,8 +8767,8 @@ init_config();
|
|
|
7823
8767
|
init_config();
|
|
7824
8768
|
init_store();
|
|
7825
8769
|
import { spawn as spawn2 } from "child_process";
|
|
7826
|
-
import { readFileSync as
|
|
7827
|
-
import
|
|
8770
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync19, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
8771
|
+
import path23 from "path";
|
|
7828
8772
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7829
8773
|
|
|
7830
8774
|
// src/lib/hybrid-search.ts
|
|
@@ -8364,10 +9308,10 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
|
|
|
8364
9308
|
init_config();
|
|
8365
9309
|
init_session_key();
|
|
8366
9310
|
init_employees();
|
|
8367
|
-
import { readFileSync as
|
|
9311
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
8368
9312
|
import { execSync as execSync5 } from "child_process";
|
|
8369
|
-
import
|
|
8370
|
-
var CACHE_DIR =
|
|
9313
|
+
import path12 from "path";
|
|
9314
|
+
var CACHE_DIR = path12.join(EXE_AI_DIR, "session-cache");
|
|
8371
9315
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
8372
9316
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
8373
9317
|
if (candidate === baseName) return true;
|
|
@@ -8412,12 +9356,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
8412
9356
|
return null;
|
|
8413
9357
|
}
|
|
8414
9358
|
function getMarkerPath() {
|
|
8415
|
-
return
|
|
9359
|
+
return path12.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
8416
9360
|
}
|
|
8417
9361
|
function getActiveAgent() {
|
|
8418
9362
|
try {
|
|
8419
9363
|
const markerPath = getMarkerPath();
|
|
8420
|
-
const raw =
|
|
9364
|
+
const raw = readFileSync7(markerPath, "utf8");
|
|
8421
9365
|
const data = JSON.parse(raw);
|
|
8422
9366
|
if (data.agentId) {
|
|
8423
9367
|
if (data.startedAt) {
|
|
@@ -8470,7 +9414,7 @@ if (!process.env.AGENT_ID) {
|
|
|
8470
9414
|
if (!loadConfigSync().autoRetrieval) {
|
|
8471
9415
|
process.exit(0);
|
|
8472
9416
|
}
|
|
8473
|
-
var WORKER_LOG_PATH =
|
|
9417
|
+
var WORKER_LOG_PATH = path23.join(EXE_AI_DIR, "workers.log");
|
|
8474
9418
|
function openWorkerLog() {
|
|
8475
9419
|
try {
|
|
8476
9420
|
return openSync2(WORKER_LOG_PATH, "a");
|
|
@@ -8478,10 +9422,10 @@ function openWorkerLog() {
|
|
|
8478
9422
|
return "ignore";
|
|
8479
9423
|
}
|
|
8480
9424
|
}
|
|
8481
|
-
var CACHE_DIR2 =
|
|
9425
|
+
var CACHE_DIR2 = path23.join(EXE_AI_DIR, "session-cache");
|
|
8482
9426
|
function loadInjectedIds(sessionId) {
|
|
8483
9427
|
try {
|
|
8484
|
-
const raw =
|
|
9428
|
+
const raw = readFileSync15(path23.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
|
|
8485
9429
|
return new Set(JSON.parse(raw));
|
|
8486
9430
|
} catch {
|
|
8487
9431
|
return /* @__PURE__ */ new Set();
|
|
@@ -8490,8 +9434,8 @@ function loadInjectedIds(sessionId) {
|
|
|
8490
9434
|
function saveInjectedIds(sessionId, ids) {
|
|
8491
9435
|
try {
|
|
8492
9436
|
mkdirSync9(CACHE_DIR2, { recursive: true });
|
|
8493
|
-
|
|
8494
|
-
|
|
9437
|
+
writeFileSync10(
|
|
9438
|
+
path23.join(CACHE_DIR2, `${sessionId}.json`),
|
|
8495
9439
|
JSON.stringify([...ids])
|
|
8496
9440
|
);
|
|
8497
9441
|
} catch {
|
|
@@ -8541,13 +9485,23 @@ process.stdin.on("end", async () => {
|
|
|
8541
9485
|
const { getAgentRuntime: getAgentRuntime2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
8542
9486
|
const { baseAgentName: baseAgentName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
8543
9487
|
const { isSessionBusy: isSessionBusy2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
9488
|
+
const MARKER_STALE_MS = 4 * 60 * 60 * 1e3;
|
|
8544
9489
|
const hasInProgressTask = (agentName) => {
|
|
8545
9490
|
try {
|
|
8546
|
-
const
|
|
8547
|
-
const
|
|
8548
|
-
const
|
|
8549
|
-
const markerPath =
|
|
8550
|
-
|
|
9491
|
+
const p = __require("path");
|
|
9492
|
+
const fs = __require("fs");
|
|
9493
|
+
const o = __require("os");
|
|
9494
|
+
const markerPath = p.join(o.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
|
|
9495
|
+
if (!fs.existsSync(markerPath)) return false;
|
|
9496
|
+
const stat = fs.statSync(markerPath);
|
|
9497
|
+
if (Date.now() - stat.mtimeMs > MARKER_STALE_MS) {
|
|
9498
|
+
try {
|
|
9499
|
+
fs.unlinkSync(markerPath);
|
|
9500
|
+
} catch {
|
|
9501
|
+
}
|
|
9502
|
+
return false;
|
|
9503
|
+
}
|
|
9504
|
+
return true;
|
|
8551
9505
|
} catch {
|
|
8552
9506
|
return false;
|
|
8553
9507
|
}
|
|
@@ -8599,7 +9553,7 @@ ${fresh.map(
|
|
|
8599
9553
|
try {
|
|
8600
9554
|
const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
|
|
8601
9555
|
const sessionKey = getSessionKey();
|
|
8602
|
-
const lastCheckPath =
|
|
9556
|
+
const lastCheckPath = path23.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
|
|
8603
9557
|
let sessionScope;
|
|
8604
9558
|
try {
|
|
8605
9559
|
const { execSync: execSync9 } = await import("child_process");
|
|
@@ -8609,7 +9563,7 @@ ${fresh.map(
|
|
|
8609
9563
|
}
|
|
8610
9564
|
let lastCheckedAt = "";
|
|
8611
9565
|
try {
|
|
8612
|
-
lastCheckedAt =
|
|
9566
|
+
lastCheckedAt = readFileSync15(lastCheckPath, "utf8").trim();
|
|
8613
9567
|
} catch {
|
|
8614
9568
|
}
|
|
8615
9569
|
const totalCount = await countPendingReviews2(sessionScope);
|
|
@@ -8667,11 +9621,11 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
|
|
|
8667
9621
|
function spawnPromptWorker(prompt, sessionId, agent) {
|
|
8668
9622
|
if (!loadConfigSync().autoIngestion) return;
|
|
8669
9623
|
try {
|
|
8670
|
-
const workerPath =
|
|
8671
|
-
|
|
9624
|
+
const workerPath = path23.resolve(
|
|
9625
|
+
path23.dirname(fileURLToPath3(import.meta.url)),
|
|
8672
9626
|
"prompt-ingest-worker.js"
|
|
8673
9627
|
);
|
|
8674
|
-
if (!
|
|
9628
|
+
if (!existsSync19(workerPath)) {
|
|
8675
9629
|
process.stderr.write(`[prompt-submit] WARN: prompt-ingest-worker not found at ${workerPath}
|
|
8676
9630
|
`);
|
|
8677
9631
|
return;
|