@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
|
@@ -19,9 +19,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
22
|
+
// src/lib/secure-files.ts
|
|
23
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
24
|
+
import { chmod, mkdir } from "fs/promises";
|
|
25
|
+
async function ensurePrivateDir(dirPath) {
|
|
26
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
27
|
+
try {
|
|
28
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function ensurePrivateDirSync(dirPath) {
|
|
33
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
34
|
+
try {
|
|
35
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function enforcePrivateFile(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function enforcePrivateFileSync(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
52
|
+
var init_secure_files = __esm({
|
|
53
|
+
"src/lib/secure-files.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
PRIVATE_DIR_MODE = 448;
|
|
56
|
+
PRIVATE_FILE_MODE = 384;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
22
60
|
// src/lib/config.ts
|
|
23
|
-
import { readFile, writeFile
|
|
24
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
61
|
+
import { readFile, writeFile } from "fs/promises";
|
|
62
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
25
63
|
import path from "path";
|
|
26
64
|
import os from "os";
|
|
27
65
|
function resolveDataDir() {
|
|
@@ -29,7 +67,7 @@ function resolveDataDir() {
|
|
|
29
67
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
30
68
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
31
69
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
32
|
-
if (!
|
|
70
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
33
71
|
try {
|
|
34
72
|
renameSync(legacyDir, newDir);
|
|
35
73
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -92,9 +130,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
92
130
|
}
|
|
93
131
|
async function loadConfig() {
|
|
94
132
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
95
|
-
await
|
|
133
|
+
await ensurePrivateDir(dir);
|
|
96
134
|
const configPath = path.join(dir, "config.json");
|
|
97
|
-
if (!
|
|
135
|
+
if (!existsSync2(configPath)) {
|
|
98
136
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
99
137
|
}
|
|
100
138
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -107,6 +145,7 @@ async function loadConfig() {
|
|
|
107
145
|
`);
|
|
108
146
|
try {
|
|
109
147
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
148
|
+
await enforcePrivateFile(configPath);
|
|
110
149
|
} catch {
|
|
111
150
|
}
|
|
112
151
|
}
|
|
@@ -126,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
126
165
|
var init_config = __esm({
|
|
127
166
|
"src/lib/config.ts"() {
|
|
128
167
|
"use strict";
|
|
168
|
+
init_secure_files();
|
|
129
169
|
EXE_AI_DIR = resolveDataDir();
|
|
130
170
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
131
171
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -270,6 +310,120 @@ var init_session_key = __esm({
|
|
|
270
310
|
}
|
|
271
311
|
});
|
|
272
312
|
|
|
313
|
+
// src/lib/runtime-table.ts
|
|
314
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
315
|
+
var init_runtime_table = __esm({
|
|
316
|
+
"src/lib/runtime-table.ts"() {
|
|
317
|
+
"use strict";
|
|
318
|
+
RUNTIME_TABLE = {
|
|
319
|
+
codex: {
|
|
320
|
+
binary: "codex",
|
|
321
|
+
launchMode: "interactive",
|
|
322
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
323
|
+
inlineFlag: "--no-alt-screen",
|
|
324
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
325
|
+
defaultModel: "gpt-5.4"
|
|
326
|
+
},
|
|
327
|
+
opencode: {
|
|
328
|
+
binary: "opencode",
|
|
329
|
+
launchMode: "exec",
|
|
330
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
331
|
+
inlineFlag: "",
|
|
332
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
333
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
DEFAULT_RUNTIME = "claude";
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// src/lib/agent-config.ts
|
|
341
|
+
var agent_config_exports = {};
|
|
342
|
+
__export(agent_config_exports, {
|
|
343
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
344
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
345
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
346
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
347
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
348
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
349
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
350
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
351
|
+
setAgentRuntime: () => setAgentRuntime
|
|
352
|
+
});
|
|
353
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
354
|
+
import path2 from "path";
|
|
355
|
+
function loadAgentConfig() {
|
|
356
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
357
|
+
try {
|
|
358
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
359
|
+
} catch {
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function saveAgentConfig(config) {
|
|
364
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
365
|
+
ensurePrivateDirSync(dir);
|
|
366
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
367
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
368
|
+
}
|
|
369
|
+
function getAgentRuntime(agentId) {
|
|
370
|
+
const config = loadAgentConfig();
|
|
371
|
+
const entry = config[agentId];
|
|
372
|
+
if (entry) return entry;
|
|
373
|
+
const orgDefault = config["default"];
|
|
374
|
+
if (orgDefault) return orgDefault;
|
|
375
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
376
|
+
}
|
|
377
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
378
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
379
|
+
if (!knownModels) {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (!knownModels.includes(model)) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const config = loadAgentConfig();
|
|
392
|
+
config[agentId] = { runtime, model };
|
|
393
|
+
saveAgentConfig(config);
|
|
394
|
+
return { ok: true };
|
|
395
|
+
}
|
|
396
|
+
function clearAgentRuntime(agentId) {
|
|
397
|
+
const config = loadAgentConfig();
|
|
398
|
+
delete config[agentId];
|
|
399
|
+
saveAgentConfig(config);
|
|
400
|
+
}
|
|
401
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
402
|
+
var init_agent_config = __esm({
|
|
403
|
+
"src/lib/agent-config.ts"() {
|
|
404
|
+
"use strict";
|
|
405
|
+
init_config();
|
|
406
|
+
init_runtime_table();
|
|
407
|
+
init_secure_files();
|
|
408
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
409
|
+
KNOWN_RUNTIMES = {
|
|
410
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
411
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
412
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
413
|
+
};
|
|
414
|
+
RUNTIME_LABELS = {
|
|
415
|
+
claude: "Claude Code (Anthropic)",
|
|
416
|
+
codex: "Codex (OpenAI)",
|
|
417
|
+
opencode: "OpenCode (open source)"
|
|
418
|
+
};
|
|
419
|
+
DEFAULT_MODELS = {
|
|
420
|
+
claude: "claude-opus-4",
|
|
421
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
422
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
273
427
|
// src/lib/employees.ts
|
|
274
428
|
var employees_exports = {};
|
|
275
429
|
__export(employees_exports, {
|
|
@@ -285,6 +439,7 @@ __export(employees_exports, {
|
|
|
285
439
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
286
440
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
287
441
|
hasRole: () => hasRole,
|
|
442
|
+
hireEmployee: () => hireEmployee,
|
|
288
443
|
isCoordinatorName: () => isCoordinatorName,
|
|
289
444
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
290
445
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -297,9 +452,9 @@ __export(employees_exports, {
|
|
|
297
452
|
validateEmployeeName: () => validateEmployeeName
|
|
298
453
|
});
|
|
299
454
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
300
|
-
import { existsSync as
|
|
455
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
301
456
|
import { execSync as execSync2 } from "child_process";
|
|
302
|
-
import
|
|
457
|
+
import path3 from "path";
|
|
303
458
|
import os2 from "os";
|
|
304
459
|
function normalizeRole(role) {
|
|
305
460
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -336,7 +491,7 @@ function validateEmployeeName(name) {
|
|
|
336
491
|
return { valid: true };
|
|
337
492
|
}
|
|
338
493
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
339
|
-
if (!
|
|
494
|
+
if (!existsSync4(employeesPath)) {
|
|
340
495
|
return [];
|
|
341
496
|
}
|
|
342
497
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -347,13 +502,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
347
502
|
}
|
|
348
503
|
}
|
|
349
504
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
350
|
-
await mkdir2(
|
|
505
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
351
506
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
352
507
|
}
|
|
353
508
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
354
|
-
if (!
|
|
509
|
+
if (!existsSync4(employeesPath)) return [];
|
|
355
510
|
try {
|
|
356
|
-
return JSON.parse(
|
|
511
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
357
512
|
} catch {
|
|
358
513
|
return [];
|
|
359
514
|
}
|
|
@@ -395,6 +550,52 @@ function addEmployee(employees, employee) {
|
|
|
395
550
|
}
|
|
396
551
|
return [...employees, normalized];
|
|
397
552
|
}
|
|
553
|
+
function appendToCoordinatorTeam(employee) {
|
|
554
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
555
|
+
if (!coordinator) return;
|
|
556
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
557
|
+
if (!existsSync4(idPath)) return;
|
|
558
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
559
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
560
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
561
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
562
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
563
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
564
|
+
const entry = `
|
|
565
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
566
|
+
`;
|
|
567
|
+
let updated;
|
|
568
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
569
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
570
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
571
|
+
} else {
|
|
572
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
573
|
+
}
|
|
574
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
575
|
+
}
|
|
576
|
+
function capitalize(s) {
|
|
577
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
578
|
+
}
|
|
579
|
+
async function hireEmployee(employee) {
|
|
580
|
+
const employees = await loadEmployees();
|
|
581
|
+
const updated = addEmployee(employees, employee);
|
|
582
|
+
await saveEmployees(updated);
|
|
583
|
+
try {
|
|
584
|
+
appendToCoordinatorTeam(employee);
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
589
|
+
const config = loadAgentConfig2();
|
|
590
|
+
const name = employee.name.toLowerCase();
|
|
591
|
+
if (!config[name] && config["default"]) {
|
|
592
|
+
config[name] = { ...config["default"] };
|
|
593
|
+
saveAgentConfig2(config);
|
|
594
|
+
}
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
return updated;
|
|
598
|
+
}
|
|
398
599
|
async function normalizeRosterCase(rosterPath) {
|
|
399
600
|
const employees = await loadEmployees(rosterPath);
|
|
400
601
|
let changed = false;
|
|
@@ -404,14 +605,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
404
605
|
emp.name = emp.name.toLowerCase();
|
|
405
606
|
changed = true;
|
|
406
607
|
try {
|
|
407
|
-
const identityDir =
|
|
408
|
-
const oldPath =
|
|
409
|
-
const newPath =
|
|
410
|
-
if (
|
|
608
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
609
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
610
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
611
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
411
612
|
renameSync2(oldPath, newPath);
|
|
412
|
-
} else if (
|
|
413
|
-
const content =
|
|
414
|
-
|
|
613
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
614
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
615
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
415
616
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
416
617
|
unlinkSync(oldPath);
|
|
417
618
|
}
|
|
@@ -441,7 +642,7 @@ function registerBinSymlinks(name) {
|
|
|
441
642
|
errors.push("Could not find 'exe-os' in PATH");
|
|
442
643
|
return { created, skipped, errors };
|
|
443
644
|
}
|
|
444
|
-
const binDir =
|
|
645
|
+
const binDir = path3.dirname(exeBinPath);
|
|
445
646
|
let target;
|
|
446
647
|
try {
|
|
447
648
|
target = readlinkSync(exeBinPath);
|
|
@@ -451,8 +652,8 @@ function registerBinSymlinks(name) {
|
|
|
451
652
|
}
|
|
452
653
|
for (const suffix of ["", "-opencode"]) {
|
|
453
654
|
const linkName = `${name}${suffix}`;
|
|
454
|
-
const linkPath =
|
|
455
|
-
if (
|
|
655
|
+
const linkPath = path3.join(binDir, linkName);
|
|
656
|
+
if (existsSync4(linkPath)) {
|
|
456
657
|
skipped.push(linkName);
|
|
457
658
|
continue;
|
|
458
659
|
}
|
|
@@ -465,26 +666,28 @@ function registerBinSymlinks(name) {
|
|
|
465
666
|
}
|
|
466
667
|
return { created, skipped, errors };
|
|
467
668
|
}
|
|
468
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
669
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
469
670
|
var init_employees = __esm({
|
|
470
671
|
"src/lib/employees.ts"() {
|
|
471
672
|
"use strict";
|
|
472
673
|
init_config();
|
|
473
|
-
EMPLOYEES_PATH =
|
|
674
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
474
675
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
475
676
|
COORDINATOR_ROLE = "COO";
|
|
476
677
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
678
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
679
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
477
680
|
}
|
|
478
681
|
});
|
|
479
682
|
|
|
480
683
|
// src/lib/session-registry.ts
|
|
481
|
-
import
|
|
684
|
+
import path5 from "path";
|
|
482
685
|
import os3 from "os";
|
|
483
686
|
var REGISTRY_PATH;
|
|
484
687
|
var init_session_registry = __esm({
|
|
485
688
|
"src/lib/session-registry.ts"() {
|
|
486
689
|
"use strict";
|
|
487
|
-
REGISTRY_PATH =
|
|
690
|
+
REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
|
|
488
691
|
}
|
|
489
692
|
});
|
|
490
693
|
|
|
@@ -624,52 +827,8 @@ var init_provider_table = __esm({
|
|
|
624
827
|
}
|
|
625
828
|
});
|
|
626
829
|
|
|
627
|
-
// src/lib/runtime-table.ts
|
|
628
|
-
var RUNTIME_TABLE;
|
|
629
|
-
var init_runtime_table = __esm({
|
|
630
|
-
"src/lib/runtime-table.ts"() {
|
|
631
|
-
"use strict";
|
|
632
|
-
RUNTIME_TABLE = {
|
|
633
|
-
codex: {
|
|
634
|
-
binary: "codex",
|
|
635
|
-
launchMode: "interactive",
|
|
636
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
637
|
-
inlineFlag: "--no-alt-screen",
|
|
638
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
639
|
-
defaultModel: "gpt-5.4"
|
|
640
|
-
},
|
|
641
|
-
opencode: {
|
|
642
|
-
binary: "opencode",
|
|
643
|
-
launchMode: "exec",
|
|
644
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
645
|
-
inlineFlag: "",
|
|
646
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
647
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// src/lib/agent-config.ts
|
|
654
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
655
|
-
import path5 from "path";
|
|
656
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
657
|
-
var init_agent_config = __esm({
|
|
658
|
-
"src/lib/agent-config.ts"() {
|
|
659
|
-
"use strict";
|
|
660
|
-
init_config();
|
|
661
|
-
init_runtime_table();
|
|
662
|
-
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
663
|
-
DEFAULT_MODELS = {
|
|
664
|
-
claude: "claude-opus-4",
|
|
665
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
666
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
|
|
671
830
|
// src/lib/intercom-queue.ts
|
|
672
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
831
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
673
832
|
import path6 from "path";
|
|
674
833
|
import os4 from "os";
|
|
675
834
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
@@ -737,13 +896,634 @@ var init_db_retry = __esm({
|
|
|
737
896
|
}
|
|
738
897
|
});
|
|
739
898
|
|
|
899
|
+
// src/lib/database-adapter.ts
|
|
900
|
+
import os5 from "os";
|
|
901
|
+
import path7 from "path";
|
|
902
|
+
import { createRequire } from "module";
|
|
903
|
+
import { pathToFileURL } from "url";
|
|
904
|
+
function quotedIdentifier(identifier) {
|
|
905
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
906
|
+
}
|
|
907
|
+
function unqualifiedTableName(name) {
|
|
908
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
909
|
+
const parts = raw.split(".");
|
|
910
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
911
|
+
}
|
|
912
|
+
function stripTrailingSemicolon(sql) {
|
|
913
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
914
|
+
}
|
|
915
|
+
function appendClause(sql, clause) {
|
|
916
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
917
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
918
|
+
if (!returningMatch) {
|
|
919
|
+
return `${trimmed}${clause}`;
|
|
920
|
+
}
|
|
921
|
+
const idx = returningMatch.index;
|
|
922
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
923
|
+
}
|
|
924
|
+
function normalizeStatement(stmt) {
|
|
925
|
+
if (typeof stmt === "string") {
|
|
926
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
927
|
+
}
|
|
928
|
+
const sql = stmt.sql;
|
|
929
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
930
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
931
|
+
}
|
|
932
|
+
return { kind: "named", sql, args: stmt.args };
|
|
933
|
+
}
|
|
934
|
+
function rewriteBooleanLiterals(sql) {
|
|
935
|
+
let out = sql;
|
|
936
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
937
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
938
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
939
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
940
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
941
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
942
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
943
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
944
|
+
}
|
|
945
|
+
return out;
|
|
946
|
+
}
|
|
947
|
+
function rewriteInsertOrIgnore(sql) {
|
|
948
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
949
|
+
return sql;
|
|
950
|
+
}
|
|
951
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
952
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
953
|
+
}
|
|
954
|
+
function rewriteInsertOrReplace(sql) {
|
|
955
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
956
|
+
if (!match) {
|
|
957
|
+
return sql;
|
|
958
|
+
}
|
|
959
|
+
const rawTable = match[1];
|
|
960
|
+
const rawColumns = match[2];
|
|
961
|
+
const remainder = match[3];
|
|
962
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
963
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
964
|
+
if (!conflictKeys?.length) {
|
|
965
|
+
return sql;
|
|
966
|
+
}
|
|
967
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
968
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
969
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
970
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
971
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
972
|
+
}
|
|
973
|
+
function rewriteSql(sql) {
|
|
974
|
+
let out = sql;
|
|
975
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
976
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
977
|
+
out = rewriteBooleanLiterals(out);
|
|
978
|
+
out = rewriteInsertOrReplace(out);
|
|
979
|
+
out = rewriteInsertOrIgnore(out);
|
|
980
|
+
return stripTrailingSemicolon(out);
|
|
981
|
+
}
|
|
982
|
+
function toBoolean(value) {
|
|
983
|
+
if (value === null || value === void 0) return value;
|
|
984
|
+
if (typeof value === "boolean") return value;
|
|
985
|
+
if (typeof value === "number") return value !== 0;
|
|
986
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
987
|
+
if (typeof value === "string") {
|
|
988
|
+
const normalized = value.trim().toLowerCase();
|
|
989
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
990
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
991
|
+
}
|
|
992
|
+
return Boolean(value);
|
|
993
|
+
}
|
|
994
|
+
function countQuestionMarks(sql, end) {
|
|
995
|
+
let count = 0;
|
|
996
|
+
let inSingle = false;
|
|
997
|
+
let inDouble = false;
|
|
998
|
+
let inLineComment = false;
|
|
999
|
+
let inBlockComment = false;
|
|
1000
|
+
for (let i = 0; i < end; i++) {
|
|
1001
|
+
const ch = sql[i];
|
|
1002
|
+
const next = sql[i + 1];
|
|
1003
|
+
if (inLineComment) {
|
|
1004
|
+
if (ch === "\n") inLineComment = false;
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (inBlockComment) {
|
|
1008
|
+
if (ch === "*" && next === "/") {
|
|
1009
|
+
inBlockComment = false;
|
|
1010
|
+
i += 1;
|
|
1011
|
+
}
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1015
|
+
inLineComment = true;
|
|
1016
|
+
i += 1;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1020
|
+
inBlockComment = true;
|
|
1021
|
+
i += 1;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1025
|
+
inSingle = !inSingle;
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1029
|
+
inDouble = !inDouble;
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1033
|
+
count += 1;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return count;
|
|
1037
|
+
}
|
|
1038
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1039
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1040
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1041
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1042
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1043
|
+
const matchText = match[0];
|
|
1044
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1045
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return indexes;
|
|
1049
|
+
}
|
|
1050
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1051
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1052
|
+
if (!match) return;
|
|
1053
|
+
const rawTable = match[1];
|
|
1054
|
+
const rawColumns = match[2];
|
|
1055
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1056
|
+
if (!boolColumns?.size) return;
|
|
1057
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1058
|
+
for (const [index, column] of columns.entries()) {
|
|
1059
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1060
|
+
args[index] = toBoolean(args[index]);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1065
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1066
|
+
if (!match) return;
|
|
1067
|
+
const rawTable = match[1];
|
|
1068
|
+
const setClause = match[2];
|
|
1069
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1070
|
+
if (!boolColumns?.size) return;
|
|
1071
|
+
const assignments = setClause.split(",");
|
|
1072
|
+
let placeholderIndex = 0;
|
|
1073
|
+
for (const assignment of assignments) {
|
|
1074
|
+
if (!assignment.includes("?")) continue;
|
|
1075
|
+
placeholderIndex += 1;
|
|
1076
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1077
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1078
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function coerceBooleanArgs(sql, args) {
|
|
1083
|
+
const nextArgs = [...args];
|
|
1084
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1085
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1086
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1087
|
+
for (const index of placeholderIndexes) {
|
|
1088
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1089
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return nextArgs;
|
|
1093
|
+
}
|
|
1094
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1095
|
+
let out = "";
|
|
1096
|
+
let placeholder = 0;
|
|
1097
|
+
let inSingle = false;
|
|
1098
|
+
let inDouble = false;
|
|
1099
|
+
let inLineComment = false;
|
|
1100
|
+
let inBlockComment = false;
|
|
1101
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1102
|
+
const ch = sql[i];
|
|
1103
|
+
const next = sql[i + 1];
|
|
1104
|
+
if (inLineComment) {
|
|
1105
|
+
out += ch;
|
|
1106
|
+
if (ch === "\n") inLineComment = false;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (inBlockComment) {
|
|
1110
|
+
out += ch;
|
|
1111
|
+
if (ch === "*" && next === "/") {
|
|
1112
|
+
out += next;
|
|
1113
|
+
inBlockComment = false;
|
|
1114
|
+
i += 1;
|
|
1115
|
+
}
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1119
|
+
out += ch + next;
|
|
1120
|
+
inLineComment = true;
|
|
1121
|
+
i += 1;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1125
|
+
out += ch + next;
|
|
1126
|
+
inBlockComment = true;
|
|
1127
|
+
i += 1;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1131
|
+
inSingle = !inSingle;
|
|
1132
|
+
out += ch;
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1136
|
+
inDouble = !inDouble;
|
|
1137
|
+
out += ch;
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1141
|
+
placeholder += 1;
|
|
1142
|
+
out += `$${placeholder}`;
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
out += ch;
|
|
1146
|
+
}
|
|
1147
|
+
return out;
|
|
1148
|
+
}
|
|
1149
|
+
function translateStatementForPostgres(stmt) {
|
|
1150
|
+
const normalized = normalizeStatement(stmt);
|
|
1151
|
+
if (normalized.kind === "named") {
|
|
1152
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1153
|
+
}
|
|
1154
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1155
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1156
|
+
return {
|
|
1157
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1158
|
+
args: coercedArgs
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function shouldBypassPostgres(stmt) {
|
|
1162
|
+
const normalized = normalizeStatement(stmt);
|
|
1163
|
+
if (normalized.kind === "named") {
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1167
|
+
}
|
|
1168
|
+
function shouldFallbackOnError(error) {
|
|
1169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1170
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1171
|
+
}
|
|
1172
|
+
function isReadQuery(sql) {
|
|
1173
|
+
const trimmed = sql.trimStart();
|
|
1174
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1175
|
+
}
|
|
1176
|
+
function buildRow(row, columns) {
|
|
1177
|
+
const values = columns.map((column) => row[column]);
|
|
1178
|
+
return Object.assign(values, row);
|
|
1179
|
+
}
|
|
1180
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1181
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1182
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1183
|
+
return {
|
|
1184
|
+
columns,
|
|
1185
|
+
columnTypes: columns.map(() => ""),
|
|
1186
|
+
rows: resultRows,
|
|
1187
|
+
rowsAffected,
|
|
1188
|
+
lastInsertRowid: void 0,
|
|
1189
|
+
toJSON() {
|
|
1190
|
+
return {
|
|
1191
|
+
columns,
|
|
1192
|
+
columnTypes: columns.map(() => ""),
|
|
1193
|
+
rows,
|
|
1194
|
+
rowsAffected,
|
|
1195
|
+
lastInsertRowid: void 0
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
async function loadPrismaClient() {
|
|
1201
|
+
if (!prismaClientPromise) {
|
|
1202
|
+
prismaClientPromise = (async () => {
|
|
1203
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1204
|
+
if (explicitPath) {
|
|
1205
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1206
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1207
|
+
if (!PrismaClient2) {
|
|
1208
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1209
|
+
}
|
|
1210
|
+
return new PrismaClient2();
|
|
1211
|
+
}
|
|
1212
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1213
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1214
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1215
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1216
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1217
|
+
if (!PrismaClient) {
|
|
1218
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1219
|
+
}
|
|
1220
|
+
return new PrismaClient();
|
|
1221
|
+
})();
|
|
1222
|
+
}
|
|
1223
|
+
return prismaClientPromise;
|
|
1224
|
+
}
|
|
1225
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1226
|
+
if (!compatibilityBootstrapPromise) {
|
|
1227
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1228
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1229
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1230
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1231
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1232
|
+
relation
|
|
1233
|
+
);
|
|
1234
|
+
if (!rows[0]?.regclass) {
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
await prisma.$executeRawUnsafe(
|
|
1238
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
})();
|
|
1242
|
+
}
|
|
1243
|
+
return compatibilityBootstrapPromise;
|
|
1244
|
+
}
|
|
1245
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1246
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1247
|
+
if (isReadQuery(translated.sql)) {
|
|
1248
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1249
|
+
translated.sql,
|
|
1250
|
+
...translated.args
|
|
1251
|
+
);
|
|
1252
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1253
|
+
}
|
|
1254
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1255
|
+
return buildResultSet([], rowsAffected);
|
|
1256
|
+
}
|
|
1257
|
+
function splitSqlStatements(sql) {
|
|
1258
|
+
const parts = [];
|
|
1259
|
+
let current = "";
|
|
1260
|
+
let inSingle = false;
|
|
1261
|
+
let inDouble = false;
|
|
1262
|
+
let inLineComment = false;
|
|
1263
|
+
let inBlockComment = false;
|
|
1264
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1265
|
+
const ch = sql[i];
|
|
1266
|
+
const next = sql[i + 1];
|
|
1267
|
+
if (inLineComment) {
|
|
1268
|
+
current += ch;
|
|
1269
|
+
if (ch === "\n") inLineComment = false;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (inBlockComment) {
|
|
1273
|
+
current += ch;
|
|
1274
|
+
if (ch === "*" && next === "/") {
|
|
1275
|
+
current += next;
|
|
1276
|
+
inBlockComment = false;
|
|
1277
|
+
i += 1;
|
|
1278
|
+
}
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1282
|
+
current += ch + next;
|
|
1283
|
+
inLineComment = true;
|
|
1284
|
+
i += 1;
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1288
|
+
current += ch + next;
|
|
1289
|
+
inBlockComment = true;
|
|
1290
|
+
i += 1;
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1294
|
+
inSingle = !inSingle;
|
|
1295
|
+
current += ch;
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1299
|
+
inDouble = !inDouble;
|
|
1300
|
+
current += ch;
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1304
|
+
if (current.trim()) {
|
|
1305
|
+
parts.push(current.trim());
|
|
1306
|
+
}
|
|
1307
|
+
current = "";
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
current += ch;
|
|
1311
|
+
}
|
|
1312
|
+
if (current.trim()) {
|
|
1313
|
+
parts.push(current.trim());
|
|
1314
|
+
}
|
|
1315
|
+
return parts;
|
|
1316
|
+
}
|
|
1317
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1318
|
+
const prisma = await loadPrismaClient();
|
|
1319
|
+
await ensureCompatibilityViews(prisma);
|
|
1320
|
+
let closed = false;
|
|
1321
|
+
let adapter;
|
|
1322
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1323
|
+
if (!fallbackClient) {
|
|
1324
|
+
if (error) throw error;
|
|
1325
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1326
|
+
}
|
|
1327
|
+
if (error) {
|
|
1328
|
+
process.stderr.write(
|
|
1329
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1330
|
+
`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
return fallbackClient.execute(stmt);
|
|
1334
|
+
};
|
|
1335
|
+
adapter = {
|
|
1336
|
+
async execute(stmt) {
|
|
1337
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1338
|
+
return fallbackExecute(stmt);
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
if (shouldFallbackOnError(error)) {
|
|
1344
|
+
return fallbackExecute(stmt, error);
|
|
1345
|
+
}
|
|
1346
|
+
throw error;
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
async batch(stmts, mode) {
|
|
1350
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1351
|
+
if (!fallbackClient) {
|
|
1352
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1353
|
+
}
|
|
1354
|
+
return fallbackClient.batch(stmts, mode);
|
|
1355
|
+
}
|
|
1356
|
+
try {
|
|
1357
|
+
if (prisma.$transaction) {
|
|
1358
|
+
return await prisma.$transaction(async (tx) => {
|
|
1359
|
+
const results2 = [];
|
|
1360
|
+
for (const stmt of stmts) {
|
|
1361
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1362
|
+
}
|
|
1363
|
+
return results2;
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
const results = [];
|
|
1367
|
+
for (const stmt of stmts) {
|
|
1368
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1369
|
+
}
|
|
1370
|
+
return results;
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1373
|
+
process.stderr.write(
|
|
1374
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1375
|
+
`
|
|
1376
|
+
);
|
|
1377
|
+
return fallbackClient.batch(stmts, mode);
|
|
1378
|
+
}
|
|
1379
|
+
throw error;
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
async migrate(stmts) {
|
|
1383
|
+
if (fallbackClient) {
|
|
1384
|
+
return fallbackClient.migrate(stmts);
|
|
1385
|
+
}
|
|
1386
|
+
return adapter.batch(stmts, "deferred");
|
|
1387
|
+
},
|
|
1388
|
+
async transaction(mode) {
|
|
1389
|
+
if (!fallbackClient) {
|
|
1390
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1391
|
+
}
|
|
1392
|
+
return fallbackClient.transaction(mode);
|
|
1393
|
+
},
|
|
1394
|
+
async executeMultiple(sql) {
|
|
1395
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1396
|
+
return fallbackClient.executeMultiple(sql);
|
|
1397
|
+
}
|
|
1398
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1399
|
+
await adapter.execute(statement);
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
async sync() {
|
|
1403
|
+
if (fallbackClient) {
|
|
1404
|
+
return fallbackClient.sync();
|
|
1405
|
+
}
|
|
1406
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1407
|
+
},
|
|
1408
|
+
close() {
|
|
1409
|
+
closed = true;
|
|
1410
|
+
prismaClientPromise = null;
|
|
1411
|
+
compatibilityBootstrapPromise = null;
|
|
1412
|
+
void prisma.$disconnect?.();
|
|
1413
|
+
},
|
|
1414
|
+
get closed() {
|
|
1415
|
+
return closed;
|
|
1416
|
+
},
|
|
1417
|
+
get protocol() {
|
|
1418
|
+
return "prisma-postgres";
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
return adapter;
|
|
1422
|
+
}
|
|
1423
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1424
|
+
var init_database_adapter = __esm({
|
|
1425
|
+
"src/lib/database-adapter.ts"() {
|
|
1426
|
+
"use strict";
|
|
1427
|
+
VIEW_MAPPINGS = [
|
|
1428
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1429
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1430
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1431
|
+
{ view: "entities", source: "memory.entities" },
|
|
1432
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1433
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1434
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1435
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1436
|
+
{ view: "messages", source: "memory.messages" },
|
|
1437
|
+
{ view: "users", source: "wiki.users" },
|
|
1438
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1439
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1440
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1441
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1442
|
+
];
|
|
1443
|
+
UPSERT_KEYS = {
|
|
1444
|
+
memories: ["id"],
|
|
1445
|
+
tasks: ["id"],
|
|
1446
|
+
behaviors: ["id"],
|
|
1447
|
+
entities: ["id"],
|
|
1448
|
+
relationships: ["id"],
|
|
1449
|
+
entity_aliases: ["alias"],
|
|
1450
|
+
notifications: ["id"],
|
|
1451
|
+
messages: ["id"],
|
|
1452
|
+
users: ["id"],
|
|
1453
|
+
workspaces: ["id"],
|
|
1454
|
+
workspace_users: ["id"],
|
|
1455
|
+
documents: ["id"],
|
|
1456
|
+
chats: ["id"]
|
|
1457
|
+
};
|
|
1458
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1459
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1460
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1461
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1462
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1463
|
+
};
|
|
1464
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1465
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1466
|
+
);
|
|
1467
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1468
|
+
/\bPRAGMA\b/i,
|
|
1469
|
+
/\bsqlite_master\b/i,
|
|
1470
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1471
|
+
/\bMATCH\b/i,
|
|
1472
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1473
|
+
/\bjson_extract\s*\(/i,
|
|
1474
|
+
/\bjulianday\s*\(/i,
|
|
1475
|
+
/\bstrftime\s*\(/i,
|
|
1476
|
+
/\blast_insert_rowid\s*\(/i
|
|
1477
|
+
];
|
|
1478
|
+
prismaClientPromise = null;
|
|
1479
|
+
compatibilityBootstrapPromise = null;
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
// src/lib/daemon-auth.ts
|
|
1484
|
+
import crypto from "crypto";
|
|
1485
|
+
import path8 from "path";
|
|
1486
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1487
|
+
function normalizeToken(token) {
|
|
1488
|
+
if (!token) return null;
|
|
1489
|
+
const trimmed = token.trim();
|
|
1490
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1491
|
+
}
|
|
1492
|
+
function readDaemonToken() {
|
|
1493
|
+
try {
|
|
1494
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
1495
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1496
|
+
} catch {
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
function ensureDaemonToken(seed) {
|
|
1501
|
+
const existing = readDaemonToken();
|
|
1502
|
+
if (existing) return existing;
|
|
1503
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1504
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1505
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1506
|
+
`, "utf8");
|
|
1507
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1508
|
+
return token;
|
|
1509
|
+
}
|
|
1510
|
+
var DAEMON_TOKEN_PATH;
|
|
1511
|
+
var init_daemon_auth = __esm({
|
|
1512
|
+
"src/lib/daemon-auth.ts"() {
|
|
1513
|
+
"use strict";
|
|
1514
|
+
init_config();
|
|
1515
|
+
init_secure_files();
|
|
1516
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
|
|
740
1520
|
// src/lib/exe-daemon-client.ts
|
|
741
1521
|
import net from "net";
|
|
742
|
-
import
|
|
1522
|
+
import os6 from "os";
|
|
743
1523
|
import { spawn } from "child_process";
|
|
744
1524
|
import { randomUUID } from "crypto";
|
|
745
|
-
import { existsSync as
|
|
746
|
-
import
|
|
1525
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1526
|
+
import path9 from "path";
|
|
747
1527
|
import { fileURLToPath } from "url";
|
|
748
1528
|
function handleData(chunk) {
|
|
749
1529
|
_buffer += chunk.toString();
|
|
@@ -771,9 +1551,9 @@ function handleData(chunk) {
|
|
|
771
1551
|
}
|
|
772
1552
|
}
|
|
773
1553
|
function cleanupStaleFiles() {
|
|
774
|
-
if (
|
|
1554
|
+
if (existsSync7(PID_PATH)) {
|
|
775
1555
|
try {
|
|
776
|
-
const pid = parseInt(
|
|
1556
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
777
1557
|
if (pid > 0) {
|
|
778
1558
|
try {
|
|
779
1559
|
process.kill(pid, 0);
|
|
@@ -794,17 +1574,17 @@ function cleanupStaleFiles() {
|
|
|
794
1574
|
}
|
|
795
1575
|
}
|
|
796
1576
|
function findPackageRoot() {
|
|
797
|
-
let dir =
|
|
798
|
-
const { root } =
|
|
1577
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1578
|
+
const { root } = path9.parse(dir);
|
|
799
1579
|
while (dir !== root) {
|
|
800
|
-
if (
|
|
801
|
-
dir =
|
|
1580
|
+
if (existsSync7(path9.join(dir, "package.json"))) return dir;
|
|
1581
|
+
dir = path9.dirname(dir);
|
|
802
1582
|
}
|
|
803
1583
|
return null;
|
|
804
1584
|
}
|
|
805
1585
|
function spawnDaemon() {
|
|
806
|
-
const freeGB =
|
|
807
|
-
const totalGB =
|
|
1586
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1587
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
808
1588
|
if (totalGB <= 8) {
|
|
809
1589
|
process.stderr.write(
|
|
810
1590
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -824,16 +1604,17 @@ function spawnDaemon() {
|
|
|
824
1604
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
825
1605
|
return;
|
|
826
1606
|
}
|
|
827
|
-
const daemonPath =
|
|
828
|
-
if (!
|
|
1607
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1608
|
+
if (!existsSync7(daemonPath)) {
|
|
829
1609
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
830
1610
|
`);
|
|
831
1611
|
return;
|
|
832
1612
|
}
|
|
833
1613
|
const resolvedPath = daemonPath;
|
|
1614
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
834
1615
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
835
1616
|
`);
|
|
836
|
-
const logPath =
|
|
1617
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
837
1618
|
let stderrFd = "ignore";
|
|
838
1619
|
try {
|
|
839
1620
|
stderrFd = openSync(logPath, "a");
|
|
@@ -851,7 +1632,8 @@ function spawnDaemon() {
|
|
|
851
1632
|
TMUX_PANE: void 0,
|
|
852
1633
|
// Prevents resolveExeSession() from scoping to one session
|
|
853
1634
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
854
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1635
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1636
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
855
1637
|
}
|
|
856
1638
|
});
|
|
857
1639
|
child.unref();
|
|
@@ -958,13 +1740,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
958
1740
|
return;
|
|
959
1741
|
}
|
|
960
1742
|
const id = randomUUID();
|
|
1743
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
961
1744
|
const timer = setTimeout(() => {
|
|
962
1745
|
_pending.delete(id);
|
|
963
1746
|
resolve({ error: "Request timeout" });
|
|
964
1747
|
}, timeoutMs);
|
|
965
1748
|
_pending.set(id, { resolve, timer });
|
|
966
1749
|
try {
|
|
967
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1750
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
968
1751
|
} catch {
|
|
969
1752
|
clearTimeout(timer);
|
|
970
1753
|
_pending.delete(id);
|
|
@@ -975,17 +1758,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
975
1758
|
function isClientConnected() {
|
|
976
1759
|
return _connected;
|
|
977
1760
|
}
|
|
978
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1761
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
979
1762
|
var init_exe_daemon_client = __esm({
|
|
980
1763
|
"src/lib/exe-daemon-client.ts"() {
|
|
981
1764
|
"use strict";
|
|
982
1765
|
init_config();
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1766
|
+
init_daemon_auth();
|
|
1767
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1768
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1769
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
986
1770
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
987
1771
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
988
1772
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1773
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
989
1774
|
_socket = null;
|
|
990
1775
|
_connected = false;
|
|
991
1776
|
_buffer = "";
|
|
@@ -1064,7 +1849,7 @@ __export(db_daemon_client_exports, {
|
|
|
1064
1849
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1065
1850
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1066
1851
|
});
|
|
1067
|
-
function
|
|
1852
|
+
function normalizeStatement2(stmt) {
|
|
1068
1853
|
if (typeof stmt === "string") {
|
|
1069
1854
|
return { sql: stmt, args: [] };
|
|
1070
1855
|
}
|
|
@@ -1088,7 +1873,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1088
1873
|
if (!_useDaemon || !isClientConnected()) {
|
|
1089
1874
|
return fallbackClient.execute(stmt);
|
|
1090
1875
|
}
|
|
1091
|
-
const { sql, args } =
|
|
1876
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1092
1877
|
const response = await sendDaemonRequest({
|
|
1093
1878
|
type: "db-execute",
|
|
1094
1879
|
sql,
|
|
@@ -1113,7 +1898,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1113
1898
|
if (!_useDaemon || !isClientConnected()) {
|
|
1114
1899
|
return fallbackClient.batch(stmts, mode);
|
|
1115
1900
|
}
|
|
1116
|
-
const statements = stmts.map(
|
|
1901
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1117
1902
|
const response = await sendDaemonRequest({
|
|
1118
1903
|
type: "db-batch",
|
|
1119
1904
|
statements,
|
|
@@ -1208,6 +1993,18 @@ __export(database_exports, {
|
|
|
1208
1993
|
});
|
|
1209
1994
|
import { createClient } from "@libsql/client";
|
|
1210
1995
|
async function initDatabase(config) {
|
|
1996
|
+
if (_walCheckpointTimer) {
|
|
1997
|
+
clearInterval(_walCheckpointTimer);
|
|
1998
|
+
_walCheckpointTimer = null;
|
|
1999
|
+
}
|
|
2000
|
+
if (_daemonClient) {
|
|
2001
|
+
_daemonClient.close();
|
|
2002
|
+
_daemonClient = null;
|
|
2003
|
+
}
|
|
2004
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2005
|
+
_adapterClient.close();
|
|
2006
|
+
}
|
|
2007
|
+
_adapterClient = null;
|
|
1211
2008
|
if (_client) {
|
|
1212
2009
|
_client.close();
|
|
1213
2010
|
_client = null;
|
|
@@ -1221,6 +2018,7 @@ async function initDatabase(config) {
|
|
|
1221
2018
|
}
|
|
1222
2019
|
_client = createClient(opts);
|
|
1223
2020
|
_resilientClient = wrapWithRetry(_client);
|
|
2021
|
+
_adapterClient = _resilientClient;
|
|
1224
2022
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1225
2023
|
});
|
|
1226
2024
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1231,14 +2029,20 @@ async function initDatabase(config) {
|
|
|
1231
2029
|
});
|
|
1232
2030
|
}, 3e4);
|
|
1233
2031
|
_walCheckpointTimer.unref();
|
|
2032
|
+
if (process.env.DATABASE_URL) {
|
|
2033
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2034
|
+
}
|
|
1234
2035
|
}
|
|
1235
2036
|
function isInitialized() {
|
|
1236
|
-
return _client !== null;
|
|
2037
|
+
return _adapterClient !== null || _client !== null;
|
|
1237
2038
|
}
|
|
1238
2039
|
function getClient() {
|
|
1239
|
-
if (!
|
|
2040
|
+
if (!_adapterClient) {
|
|
1240
2041
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1241
2042
|
}
|
|
2043
|
+
if (process.env.DATABASE_URL) {
|
|
2044
|
+
return _adapterClient;
|
|
2045
|
+
}
|
|
1242
2046
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1243
2047
|
return _resilientClient;
|
|
1244
2048
|
}
|
|
@@ -1248,6 +2052,7 @@ function getClient() {
|
|
|
1248
2052
|
return _resilientClient;
|
|
1249
2053
|
}
|
|
1250
2054
|
async function initDaemonClient() {
|
|
2055
|
+
if (process.env.DATABASE_URL) return;
|
|
1251
2056
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1252
2057
|
if (!_resilientClient) return;
|
|
1253
2058
|
try {
|
|
@@ -1544,6 +2349,7 @@ async function ensureSchema() {
|
|
|
1544
2349
|
project TEXT NOT NULL,
|
|
1545
2350
|
summary TEXT NOT NULL,
|
|
1546
2351
|
task_file TEXT,
|
|
2352
|
+
session_scope TEXT,
|
|
1547
2353
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1548
2354
|
created_at TEXT NOT NULL
|
|
1549
2355
|
);
|
|
@@ -1552,7 +2358,7 @@ async function ensureSchema() {
|
|
|
1552
2358
|
ON notifications(read);
|
|
1553
2359
|
|
|
1554
2360
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1555
|
-
ON notifications(agent_id);
|
|
2361
|
+
ON notifications(agent_id, session_scope);
|
|
1556
2362
|
|
|
1557
2363
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1558
2364
|
ON notifications(task_file);
|
|
@@ -1590,6 +2396,7 @@ async function ensureSchema() {
|
|
|
1590
2396
|
target_agent TEXT NOT NULL,
|
|
1591
2397
|
target_project TEXT,
|
|
1592
2398
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2399
|
+
session_scope TEXT,
|
|
1593
2400
|
content TEXT NOT NULL,
|
|
1594
2401
|
priority TEXT DEFAULT 'normal',
|
|
1595
2402
|
status TEXT DEFAULT 'pending',
|
|
@@ -1603,10 +2410,31 @@ async function ensureSchema() {
|
|
|
1603
2410
|
);
|
|
1604
2411
|
|
|
1605
2412
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1606
|
-
ON messages(target_agent, status);
|
|
2413
|
+
ON messages(target_agent, session_scope, status);
|
|
1607
2414
|
|
|
1608
2415
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1609
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2416
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2417
|
+
`);
|
|
2418
|
+
try {
|
|
2419
|
+
await client.execute({
|
|
2420
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2421
|
+
args: []
|
|
2422
|
+
});
|
|
2423
|
+
} catch {
|
|
2424
|
+
}
|
|
2425
|
+
try {
|
|
2426
|
+
await client.execute({
|
|
2427
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2428
|
+
args: []
|
|
2429
|
+
});
|
|
2430
|
+
} catch {
|
|
2431
|
+
}
|
|
2432
|
+
await client.executeMultiple(`
|
|
2433
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2434
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2435
|
+
|
|
2436
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2437
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1610
2438
|
`);
|
|
1611
2439
|
try {
|
|
1612
2440
|
await client.execute({
|
|
@@ -2190,52 +3018,72 @@ async function ensureSchema() {
|
|
|
2190
3018
|
} catch {
|
|
2191
3019
|
}
|
|
2192
3020
|
}
|
|
3021
|
+
try {
|
|
3022
|
+
await client.execute({
|
|
3023
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3024
|
+
args: []
|
|
3025
|
+
});
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
2193
3028
|
}
|
|
2194
3029
|
async function disposeDatabase() {
|
|
3030
|
+
if (_walCheckpointTimer) {
|
|
3031
|
+
clearInterval(_walCheckpointTimer);
|
|
3032
|
+
_walCheckpointTimer = null;
|
|
3033
|
+
}
|
|
2195
3034
|
if (_daemonClient) {
|
|
2196
3035
|
_daemonClient.close();
|
|
2197
3036
|
_daemonClient = null;
|
|
2198
3037
|
}
|
|
3038
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3039
|
+
_adapterClient.close();
|
|
3040
|
+
}
|
|
3041
|
+
_adapterClient = null;
|
|
2199
3042
|
if (_client) {
|
|
2200
3043
|
_client.close();
|
|
2201
3044
|
_client = null;
|
|
2202
3045
|
_resilientClient = null;
|
|
2203
3046
|
}
|
|
2204
3047
|
}
|
|
2205
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3048
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2206
3049
|
var init_database = __esm({
|
|
2207
3050
|
"src/lib/database.ts"() {
|
|
2208
3051
|
"use strict";
|
|
2209
3052
|
init_db_retry();
|
|
2210
3053
|
init_employees();
|
|
3054
|
+
init_database_adapter();
|
|
2211
3055
|
_client = null;
|
|
2212
3056
|
_resilientClient = null;
|
|
2213
3057
|
_walCheckpointTimer = null;
|
|
2214
3058
|
_daemonClient = null;
|
|
3059
|
+
_adapterClient = null;
|
|
2215
3060
|
initTurso = initDatabase;
|
|
2216
3061
|
disposeTurso = disposeDatabase;
|
|
2217
3062
|
}
|
|
2218
3063
|
});
|
|
2219
3064
|
|
|
2220
3065
|
// src/lib/license.ts
|
|
2221
|
-
import { readFileSync as
|
|
3066
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2222
3067
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2223
|
-
import
|
|
3068
|
+
import { createRequire as createRequire2 } from "module";
|
|
3069
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3070
|
+
import os7 from "os";
|
|
3071
|
+
import path10 from "path";
|
|
2224
3072
|
import { jwtVerify, importSPKI } from "jose";
|
|
2225
3073
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
2226
3074
|
var init_license = __esm({
|
|
2227
3075
|
"src/lib/license.ts"() {
|
|
2228
3076
|
"use strict";
|
|
2229
3077
|
init_config();
|
|
2230
|
-
LICENSE_PATH =
|
|
2231
|
-
CACHE_PATH =
|
|
2232
|
-
DEVICE_ID_PATH =
|
|
3078
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3079
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3080
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2233
3081
|
}
|
|
2234
3082
|
});
|
|
2235
3083
|
|
|
2236
3084
|
// src/lib/plan-limits.ts
|
|
2237
|
-
import { readFileSync as
|
|
2238
|
-
import
|
|
3085
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3086
|
+
import path11 from "path";
|
|
2239
3087
|
var CACHE_PATH2;
|
|
2240
3088
|
var init_plan_limits = __esm({
|
|
2241
3089
|
"src/lib/plan-limits.ts"() {
|
|
@@ -2244,14 +3092,14 @@ var init_plan_limits = __esm({
|
|
|
2244
3092
|
init_employees();
|
|
2245
3093
|
init_license();
|
|
2246
3094
|
init_config();
|
|
2247
|
-
CACHE_PATH2 =
|
|
3095
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2248
3096
|
}
|
|
2249
3097
|
});
|
|
2250
3098
|
|
|
2251
3099
|
// src/lib/tmux-routing.ts
|
|
2252
|
-
import { readFileSync as
|
|
2253
|
-
import
|
|
2254
|
-
import
|
|
3100
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
3101
|
+
import path12 from "path";
|
|
3102
|
+
import os8 from "os";
|
|
2255
3103
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2256
3104
|
function getMySession() {
|
|
2257
3105
|
return getTransport().getMySession();
|
|
@@ -2264,7 +3112,7 @@ function extractRootExe(name) {
|
|
|
2264
3112
|
}
|
|
2265
3113
|
function getParentExe(sessionKey) {
|
|
2266
3114
|
try {
|
|
2267
|
-
const data = JSON.parse(
|
|
3115
|
+
const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2268
3116
|
return data.parentExe || null;
|
|
2269
3117
|
} catch {
|
|
2270
3118
|
return null;
|
|
@@ -2307,10 +3155,10 @@ var init_tmux_routing = __esm({
|
|
|
2307
3155
|
init_intercom_queue();
|
|
2308
3156
|
init_plan_limits();
|
|
2309
3157
|
init_employees();
|
|
2310
|
-
SPAWN_LOCK_DIR =
|
|
2311
|
-
SESSION_CACHE =
|
|
2312
|
-
INTERCOM_LOG2 =
|
|
2313
|
-
DEBOUNCE_FILE =
|
|
3158
|
+
SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
3159
|
+
SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
|
|
3160
|
+
INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
3161
|
+
DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2314
3162
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2315
3163
|
}
|
|
2316
3164
|
});
|
|
@@ -2352,9 +3200,9 @@ __export(cto_delegation_gate_exports, {
|
|
|
2352
3200
|
hasValidScratchpadEscape: () => hasValidScratchpadEscape,
|
|
2353
3201
|
scratchpadPath: () => scratchpadPath
|
|
2354
3202
|
});
|
|
2355
|
-
import
|
|
2356
|
-
import
|
|
2357
|
-
import { existsSync as
|
|
3203
|
+
import os9 from "os";
|
|
3204
|
+
import path13 from "path";
|
|
3205
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
|
|
2358
3206
|
function resolveGatedAgent() {
|
|
2359
3207
|
try {
|
|
2360
3208
|
const employees = loadEmployeesSync();
|
|
@@ -2368,12 +3216,12 @@ function getGatedAgent() {
|
|
|
2368
3216
|
return resolveGatedAgent() || GATED_AGENT;
|
|
2369
3217
|
}
|
|
2370
3218
|
function toWorkspaceRelative(filePath, cwd = process.cwd()) {
|
|
2371
|
-
const normalized =
|
|
2372
|
-
if (normalized.startsWith(cwd +
|
|
3219
|
+
const normalized = path13.normalize(filePath);
|
|
3220
|
+
if (normalized.startsWith(cwd + path13.sep)) {
|
|
2373
3221
|
return normalized.slice(cwd.length + 1);
|
|
2374
3222
|
}
|
|
2375
|
-
if (
|
|
2376
|
-
return
|
|
3223
|
+
if (path13.isAbsolute(normalized)) {
|
|
3224
|
+
return path13.basename(normalized);
|
|
2377
3225
|
}
|
|
2378
3226
|
return normalized;
|
|
2379
3227
|
}
|
|
@@ -2382,8 +3230,8 @@ function isDockerfile(basename) {
|
|
|
2382
3230
|
}
|
|
2383
3231
|
function classifyPath(filePath, cwd) {
|
|
2384
3232
|
const rel = toWorkspaceRelative(filePath, cwd);
|
|
2385
|
-
const basename =
|
|
2386
|
-
const ext =
|
|
3233
|
+
const basename = path13.basename(rel);
|
|
3234
|
+
const ext = path13.extname(rel).toLowerCase();
|
|
2387
3235
|
if (ext === ".md") {
|
|
2388
3236
|
for (const prefix of EXEMPT_MD_DIR_PREFIXES) {
|
|
2389
3237
|
if (rel.startsWith(prefix)) return "exempt";
|
|
@@ -2425,22 +3273,22 @@ async function hasRecentEngineerDispatch(now = Date.now()) {
|
|
|
2425
3273
|
return false;
|
|
2426
3274
|
}
|
|
2427
3275
|
}
|
|
2428
|
-
function scratchpadPath(sessionId, homeDir =
|
|
2429
|
-
return
|
|
3276
|
+
function scratchpadPath(sessionId, homeDir = os9.homedir()) {
|
|
3277
|
+
return path13.join(
|
|
2430
3278
|
homeDir,
|
|
2431
3279
|
".exe-os",
|
|
2432
3280
|
"session-cache",
|
|
2433
3281
|
`cto-scratchpad-${sessionId}.txt`
|
|
2434
3282
|
);
|
|
2435
3283
|
}
|
|
2436
|
-
function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir =
|
|
3284
|
+
function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os9.homedir()) {
|
|
2437
3285
|
const paths = [scratchpadPath(sessionId, homeDir)];
|
|
2438
3286
|
for (const filePath of paths) {
|
|
2439
|
-
if (!
|
|
3287
|
+
if (!existsSync11(filePath)) continue;
|
|
2440
3288
|
try {
|
|
2441
3289
|
const stat = statSync2(filePath);
|
|
2442
3290
|
if (now - stat.mtimeMs > SCRATCHPAD_MAX_AGE_MS) continue;
|
|
2443
|
-
const body =
|
|
3291
|
+
const body = readFileSync11(filePath, "utf-8");
|
|
2444
3292
|
if (SCRATCHPAD_ESCAPE_PATTERN.test(body)) return true;
|
|
2445
3293
|
} catch {
|
|
2446
3294
|
}
|
|
@@ -2531,14 +3379,14 @@ var init_memory = __esm({
|
|
|
2531
3379
|
|
|
2532
3380
|
// src/lib/keychain.ts
|
|
2533
3381
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2534
|
-
import { existsSync as
|
|
2535
|
-
import
|
|
2536
|
-
import
|
|
3382
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3383
|
+
import path14 from "path";
|
|
3384
|
+
import os10 from "os";
|
|
2537
3385
|
function getKeyDir() {
|
|
2538
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3386
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os10.homedir(), ".exe-os");
|
|
2539
3387
|
}
|
|
2540
3388
|
function getKeyPath() {
|
|
2541
|
-
return
|
|
3389
|
+
return path14.join(getKeyDir(), "master.key");
|
|
2542
3390
|
}
|
|
2543
3391
|
async function tryKeytar() {
|
|
2544
3392
|
try {
|
|
@@ -2559,9 +3407,9 @@ async function getMasterKey() {
|
|
|
2559
3407
|
}
|
|
2560
3408
|
}
|
|
2561
3409
|
const keyPath = getKeyPath();
|
|
2562
|
-
if (!
|
|
3410
|
+
if (!existsSync12(keyPath)) {
|
|
2563
3411
|
process.stderr.write(
|
|
2564
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3412
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2565
3413
|
`
|
|
2566
3414
|
);
|
|
2567
3415
|
return null;
|
|
@@ -2646,6 +3494,7 @@ var shard_manager_exports = {};
|
|
|
2646
3494
|
__export(shard_manager_exports, {
|
|
2647
3495
|
disposeShards: () => disposeShards,
|
|
2648
3496
|
ensureShardSchema: () => ensureShardSchema,
|
|
3497
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2649
3498
|
getReadyShardClient: () => getReadyShardClient,
|
|
2650
3499
|
getShardClient: () => getShardClient,
|
|
2651
3500
|
getShardsDir: () => getShardsDir,
|
|
@@ -2654,15 +3503,18 @@ __export(shard_manager_exports, {
|
|
|
2654
3503
|
listShards: () => listShards,
|
|
2655
3504
|
shardExists: () => shardExists
|
|
2656
3505
|
});
|
|
2657
|
-
import
|
|
2658
|
-
import { existsSync as
|
|
3506
|
+
import path15 from "path";
|
|
3507
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
2659
3508
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2660
3509
|
function initShardManager(encryptionKey) {
|
|
2661
3510
|
_encryptionKey = encryptionKey;
|
|
2662
|
-
if (!
|
|
3511
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
2663
3512
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
2664
3513
|
}
|
|
2665
3514
|
_shardingEnabled = true;
|
|
3515
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3516
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3517
|
+
_evictionTimer.unref();
|
|
2666
3518
|
}
|
|
2667
3519
|
function isShardingEnabled() {
|
|
2668
3520
|
return _shardingEnabled;
|
|
@@ -2679,21 +3531,28 @@ function getShardClient(projectName) {
|
|
|
2679
3531
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2680
3532
|
}
|
|
2681
3533
|
const cached = _shards.get(safeName);
|
|
2682
|
-
if (cached)
|
|
2683
|
-
|
|
3534
|
+
if (cached) {
|
|
3535
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3536
|
+
return cached;
|
|
3537
|
+
}
|
|
3538
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3539
|
+
evictLRU();
|
|
3540
|
+
}
|
|
3541
|
+
const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
|
|
2684
3542
|
const client = createClient2({
|
|
2685
3543
|
url: `file:${dbPath}`,
|
|
2686
3544
|
encryptionKey: _encryptionKey
|
|
2687
3545
|
});
|
|
2688
3546
|
_shards.set(safeName, client);
|
|
3547
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2689
3548
|
return client;
|
|
2690
3549
|
}
|
|
2691
3550
|
function shardExists(projectName) {
|
|
2692
3551
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2693
|
-
return
|
|
3552
|
+
return existsSync13(path15.join(SHARDS_DIR, `${safeName}.db`));
|
|
2694
3553
|
}
|
|
2695
3554
|
function listShards() {
|
|
2696
|
-
if (!
|
|
3555
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
2697
3556
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2698
3557
|
}
|
|
2699
3558
|
async function ensureShardSchema(client) {
|
|
@@ -2745,6 +3604,8 @@ async function ensureShardSchema(client) {
|
|
|
2745
3604
|
for (const col of [
|
|
2746
3605
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2747
3606
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3607
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3608
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2748
3609
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2749
3610
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2750
3611
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2767,7 +3628,23 @@ async function ensureShardSchema(client) {
|
|
|
2767
3628
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2768
3629
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2769
3630
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2770
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3631
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3632
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3633
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3634
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3635
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3636
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3637
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3638
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3639
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3640
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3641
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3642
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3643
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3644
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3645
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3646
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3647
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2771
3648
|
]) {
|
|
2772
3649
|
try {
|
|
2773
3650
|
await client.execute(col);
|
|
@@ -2866,21 +3743,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2866
3743
|
await ensureShardSchema(client);
|
|
2867
3744
|
return client;
|
|
2868
3745
|
}
|
|
3746
|
+
function evictLRU() {
|
|
3747
|
+
let oldest = null;
|
|
3748
|
+
let oldestTime = Infinity;
|
|
3749
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3750
|
+
if (time < oldestTime) {
|
|
3751
|
+
oldestTime = time;
|
|
3752
|
+
oldest = name;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
if (oldest) {
|
|
3756
|
+
const client = _shards.get(oldest);
|
|
3757
|
+
if (client) {
|
|
3758
|
+
client.close();
|
|
3759
|
+
}
|
|
3760
|
+
_shards.delete(oldest);
|
|
3761
|
+
_shardLastAccess.delete(oldest);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
function evictIdleShards() {
|
|
3765
|
+
const now = Date.now();
|
|
3766
|
+
const toEvict = [];
|
|
3767
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3768
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3769
|
+
toEvict.push(name);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
for (const name of toEvict) {
|
|
3773
|
+
const client = _shards.get(name);
|
|
3774
|
+
if (client) {
|
|
3775
|
+
client.close();
|
|
3776
|
+
}
|
|
3777
|
+
_shards.delete(name);
|
|
3778
|
+
_shardLastAccess.delete(name);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
function getOpenShardCount() {
|
|
3782
|
+
return _shards.size;
|
|
3783
|
+
}
|
|
2869
3784
|
function disposeShards() {
|
|
3785
|
+
if (_evictionTimer) {
|
|
3786
|
+
clearInterval(_evictionTimer);
|
|
3787
|
+
_evictionTimer = null;
|
|
3788
|
+
}
|
|
2870
3789
|
for (const [, client] of _shards) {
|
|
2871
3790
|
client.close();
|
|
2872
3791
|
}
|
|
2873
3792
|
_shards.clear();
|
|
3793
|
+
_shardLastAccess.clear();
|
|
2874
3794
|
_shardingEnabled = false;
|
|
2875
3795
|
_encryptionKey = null;
|
|
2876
3796
|
}
|
|
2877
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3797
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2878
3798
|
var init_shard_manager = __esm({
|
|
2879
3799
|
"src/lib/shard-manager.ts"() {
|
|
2880
3800
|
"use strict";
|
|
2881
3801
|
init_config();
|
|
2882
|
-
SHARDS_DIR =
|
|
3802
|
+
SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
|
|
3803
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3804
|
+
MAX_OPEN_SHARDS = 10;
|
|
3805
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2883
3806
|
_shards = /* @__PURE__ */ new Map();
|
|
3807
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3808
|
+
_evictionTimer = null;
|
|
2884
3809
|
_encryptionKey = null;
|
|
2885
3810
|
_shardingEnabled = false;
|
|
2886
3811
|
}
|
|
@@ -3653,7 +4578,7 @@ __export(review_gate_exports, {
|
|
|
3653
4578
|
runReviewGate: () => runReviewGate
|
|
3654
4579
|
});
|
|
3655
4580
|
import { execSync as execSync5 } from "child_process";
|
|
3656
|
-
import { existsSync as
|
|
4581
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3657
4582
|
function checkCommitsExist(taskCreatedAt) {
|
|
3658
4583
|
try {
|
|
3659
4584
|
const since = new Date(taskCreatedAt).toISOString();
|
|
@@ -3716,7 +4641,7 @@ function checkTestCoverage(taskCreatedAt) {
|
|
|
3716
4641
|
const testPath = file.replace(/^src\//, "tests/").replace(/\.ts$/, ".test.ts");
|
|
3717
4642
|
const baseName = file.split("/").pop()?.replace(/\.ts$/, "") ?? "";
|
|
3718
4643
|
const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
|
|
3719
|
-
const hasDirectTest =
|
|
4644
|
+
const hasDirectTest = existsSync14(testPath);
|
|
3720
4645
|
let hasRelatedTest = false;
|
|
3721
4646
|
try {
|
|
3722
4647
|
const related = execSync5(
|
|
@@ -3770,18 +4695,18 @@ var init_review_gate = __esm({
|
|
|
3770
4695
|
});
|
|
3771
4696
|
|
|
3772
4697
|
// src/adapters/claude/hooks/pre-tool-use.ts
|
|
3773
|
-
import { existsSync as
|
|
4698
|
+
import { existsSync as existsSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
3774
4699
|
import { execSync as execSync6 } from "child_process";
|
|
3775
|
-
import
|
|
4700
|
+
import path16 from "path";
|
|
3776
4701
|
|
|
3777
4702
|
// src/lib/active-agent.ts
|
|
3778
4703
|
init_config();
|
|
3779
4704
|
init_session_key();
|
|
3780
4705
|
init_employees();
|
|
3781
|
-
import { readFileSync as
|
|
4706
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
3782
4707
|
import { execSync as execSync3 } from "child_process";
|
|
3783
|
-
import
|
|
3784
|
-
var CACHE_DIR =
|
|
4708
|
+
import path4 from "path";
|
|
4709
|
+
var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
|
|
3785
4710
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
3786
4711
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
3787
4712
|
if (candidate === baseName) return true;
|
|
@@ -3826,12 +4751,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
3826
4751
|
return null;
|
|
3827
4752
|
}
|
|
3828
4753
|
function getMarkerPath() {
|
|
3829
|
-
return
|
|
4754
|
+
return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
3830
4755
|
}
|
|
3831
4756
|
function getActiveAgent() {
|
|
3832
4757
|
try {
|
|
3833
4758
|
const markerPath = getMarkerPath();
|
|
3834
|
-
const raw =
|
|
4759
|
+
const raw = readFileSync4(markerPath, "utf8");
|
|
3835
4760
|
const data = JSON.parse(raw);
|
|
3836
4761
|
if (data.agentId) {
|
|
3837
4762
|
if (data.startedAt) {
|
|
@@ -3886,17 +4811,17 @@ if (!process.env.AGENT_ID) {
|
|
|
3886
4811
|
}
|
|
3887
4812
|
var DELEGATION_TASK_THRESHOLD = 3;
|
|
3888
4813
|
var CTO_ROLES = ["CTO", "executive"];
|
|
3889
|
-
var CACHE_DIR2 =
|
|
4814
|
+
var CACHE_DIR2 = path16.join(EXE_AI_DIR, "session-cache");
|
|
3890
4815
|
var timeout = setTimeout(() => {
|
|
3891
4816
|
process.exit(0);
|
|
3892
4817
|
}, 5e3);
|
|
3893
4818
|
timeout.unref();
|
|
3894
4819
|
function getDelegationFlagPath() {
|
|
3895
|
-
return
|
|
4820
|
+
return path16.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
|
|
3896
4821
|
}
|
|
3897
4822
|
function hasDelegationFired() {
|
|
3898
4823
|
try {
|
|
3899
|
-
return
|
|
4824
|
+
return existsSync15(getDelegationFlagPath());
|
|
3900
4825
|
} catch {
|
|
3901
4826
|
return false;
|
|
3902
4827
|
}
|
|
@@ -3904,7 +4829,7 @@ function hasDelegationFired() {
|
|
|
3904
4829
|
function markDelegationFired() {
|
|
3905
4830
|
try {
|
|
3906
4831
|
mkdirSync7(CACHE_DIR2, { recursive: true });
|
|
3907
|
-
|
|
4832
|
+
writeFileSync8(getDelegationFlagPath(), JSON.stringify({ fired: true }));
|
|
3908
4833
|
} catch {
|
|
3909
4834
|
}
|
|
3910
4835
|
}
|