@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,9 +25,47 @@ 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
|
-
import { readFile, writeFile
|
|
30
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
67
|
+
import { readFile, writeFile } from "fs/promises";
|
|
68
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
31
69
|
import path from "path";
|
|
32
70
|
import os from "os";
|
|
33
71
|
function resolveDataDir() {
|
|
@@ -35,7 +73,7 @@ function resolveDataDir() {
|
|
|
35
73
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
36
74
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
37
75
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
38
|
-
if (!
|
|
76
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
39
77
|
try {
|
|
40
78
|
renameSync(legacyDir, newDir);
|
|
41
79
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
98
136
|
}
|
|
99
137
|
async function loadConfig() {
|
|
100
138
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
101
|
-
await
|
|
139
|
+
await ensurePrivateDir(dir);
|
|
102
140
|
const configPath = path.join(dir, "config.json");
|
|
103
|
-
if (!
|
|
141
|
+
if (!existsSync2(configPath)) {
|
|
104
142
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
105
143
|
}
|
|
106
144
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -113,6 +151,7 @@ async function loadConfig() {
|
|
|
113
151
|
`);
|
|
114
152
|
try {
|
|
115
153
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
154
|
+
await enforcePrivateFile(configPath);
|
|
116
155
|
} catch {
|
|
117
156
|
}
|
|
118
157
|
}
|
|
@@ -132,6 +171,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
132
171
|
var init_config = __esm({
|
|
133
172
|
"src/lib/config.ts"() {
|
|
134
173
|
"use strict";
|
|
174
|
+
init_secure_files();
|
|
135
175
|
EXE_AI_DIR = resolveDataDir();
|
|
136
176
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
137
177
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -278,7 +318,7 @@ var init_session_key = __esm({
|
|
|
278
318
|
|
|
279
319
|
// src/lib/employees.ts
|
|
280
320
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
281
|
-
import { existsSync as
|
|
321
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
282
322
|
import { execSync as execSync2 } from "child_process";
|
|
283
323
|
import path2 from "path";
|
|
284
324
|
import os2 from "os";
|
|
@@ -299,7 +339,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
299
339
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
300
340
|
}
|
|
301
341
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
302
|
-
if (!
|
|
342
|
+
if (!existsSync3(employeesPath)) return [];
|
|
303
343
|
try {
|
|
304
344
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
305
345
|
} catch {
|
|
@@ -323,7 +363,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
323
363
|
if (!emp) return false;
|
|
324
364
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
325
365
|
}
|
|
326
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
366
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
327
367
|
var init_employees = __esm({
|
|
328
368
|
"src/lib/employees.ts"() {
|
|
329
369
|
"use strict";
|
|
@@ -332,17 +372,18 @@ var init_employees = __esm({
|
|
|
332
372
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
333
373
|
COORDINATOR_ROLE = "COO";
|
|
334
374
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
375
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
335
376
|
}
|
|
336
377
|
});
|
|
337
378
|
|
|
338
379
|
// src/lib/session-registry.ts
|
|
339
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as
|
|
380
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
340
381
|
import path4 from "path";
|
|
341
382
|
import os3 from "os";
|
|
342
383
|
function registerSession(entry) {
|
|
343
384
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
344
|
-
if (!
|
|
345
|
-
|
|
385
|
+
if (!existsSync4(dir)) {
|
|
386
|
+
mkdirSync3(dir, { recursive: true });
|
|
346
387
|
}
|
|
347
388
|
const sessions = listSessions();
|
|
348
389
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -577,10 +618,10 @@ var init_runtime_table = __esm({
|
|
|
577
618
|
});
|
|
578
619
|
|
|
579
620
|
// src/lib/agent-config.ts
|
|
580
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
621
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
581
622
|
import path5 from "path";
|
|
582
623
|
function loadAgentConfig() {
|
|
583
|
-
if (!
|
|
624
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
584
625
|
try {
|
|
585
626
|
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
586
627
|
} catch {
|
|
@@ -601,6 +642,7 @@ var init_agent_config = __esm({
|
|
|
601
642
|
"use strict";
|
|
602
643
|
init_config();
|
|
603
644
|
init_runtime_table();
|
|
645
|
+
init_secure_files();
|
|
604
646
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
605
647
|
DEFAULT_MODELS = {
|
|
606
648
|
claude: "claude-opus-4",
|
|
@@ -619,16 +661,16 @@ __export(intercom_queue_exports, {
|
|
|
619
661
|
queueIntercom: () => queueIntercom,
|
|
620
662
|
readQueue: () => readQueue
|
|
621
663
|
});
|
|
622
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as
|
|
664
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
623
665
|
import path6 from "path";
|
|
624
666
|
import os4 from "os";
|
|
625
667
|
function ensureDir() {
|
|
626
668
|
const dir = path6.dirname(QUEUE_PATH);
|
|
627
|
-
if (!
|
|
669
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
628
670
|
}
|
|
629
671
|
function readQueue() {
|
|
630
672
|
try {
|
|
631
|
-
if (!
|
|
673
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
632
674
|
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
633
675
|
} catch {
|
|
634
676
|
return [];
|
|
@@ -791,13 +833,634 @@ var init_db_retry = __esm({
|
|
|
791
833
|
}
|
|
792
834
|
});
|
|
793
835
|
|
|
836
|
+
// src/lib/database-adapter.ts
|
|
837
|
+
import os5 from "os";
|
|
838
|
+
import path7 from "path";
|
|
839
|
+
import { createRequire } from "module";
|
|
840
|
+
import { pathToFileURL } from "url";
|
|
841
|
+
function quotedIdentifier(identifier) {
|
|
842
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
843
|
+
}
|
|
844
|
+
function unqualifiedTableName(name) {
|
|
845
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
846
|
+
const parts = raw.split(".");
|
|
847
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
848
|
+
}
|
|
849
|
+
function stripTrailingSemicolon(sql) {
|
|
850
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
851
|
+
}
|
|
852
|
+
function appendClause(sql, clause) {
|
|
853
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
854
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
855
|
+
if (!returningMatch) {
|
|
856
|
+
return `${trimmed}${clause}`;
|
|
857
|
+
}
|
|
858
|
+
const idx = returningMatch.index;
|
|
859
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
860
|
+
}
|
|
861
|
+
function normalizeStatement(stmt) {
|
|
862
|
+
if (typeof stmt === "string") {
|
|
863
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
864
|
+
}
|
|
865
|
+
const sql = stmt.sql;
|
|
866
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
867
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
868
|
+
}
|
|
869
|
+
return { kind: "named", sql, args: stmt.args };
|
|
870
|
+
}
|
|
871
|
+
function rewriteBooleanLiterals(sql) {
|
|
872
|
+
let out = sql;
|
|
873
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
874
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
875
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
876
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
877
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
878
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
879
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
880
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
881
|
+
}
|
|
882
|
+
return out;
|
|
883
|
+
}
|
|
884
|
+
function rewriteInsertOrIgnore(sql) {
|
|
885
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
886
|
+
return sql;
|
|
887
|
+
}
|
|
888
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
889
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
890
|
+
}
|
|
891
|
+
function rewriteInsertOrReplace(sql) {
|
|
892
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
893
|
+
if (!match) {
|
|
894
|
+
return sql;
|
|
895
|
+
}
|
|
896
|
+
const rawTable = match[1];
|
|
897
|
+
const rawColumns = match[2];
|
|
898
|
+
const remainder = match[3];
|
|
899
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
900
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
901
|
+
if (!conflictKeys?.length) {
|
|
902
|
+
return sql;
|
|
903
|
+
}
|
|
904
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
905
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
906
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
907
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
908
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
909
|
+
}
|
|
910
|
+
function rewriteSql(sql) {
|
|
911
|
+
let out = sql;
|
|
912
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
913
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
914
|
+
out = rewriteBooleanLiterals(out);
|
|
915
|
+
out = rewriteInsertOrReplace(out);
|
|
916
|
+
out = rewriteInsertOrIgnore(out);
|
|
917
|
+
return stripTrailingSemicolon(out);
|
|
918
|
+
}
|
|
919
|
+
function toBoolean(value) {
|
|
920
|
+
if (value === null || value === void 0) return value;
|
|
921
|
+
if (typeof value === "boolean") return value;
|
|
922
|
+
if (typeof value === "number") return value !== 0;
|
|
923
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
924
|
+
if (typeof value === "string") {
|
|
925
|
+
const normalized = value.trim().toLowerCase();
|
|
926
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
927
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
928
|
+
}
|
|
929
|
+
return Boolean(value);
|
|
930
|
+
}
|
|
931
|
+
function countQuestionMarks(sql, end) {
|
|
932
|
+
let count = 0;
|
|
933
|
+
let inSingle = false;
|
|
934
|
+
let inDouble = false;
|
|
935
|
+
let inLineComment = false;
|
|
936
|
+
let inBlockComment = false;
|
|
937
|
+
for (let i = 0; i < end; i++) {
|
|
938
|
+
const ch = sql[i];
|
|
939
|
+
const next = sql[i + 1];
|
|
940
|
+
if (inLineComment) {
|
|
941
|
+
if (ch === "\n") inLineComment = false;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (inBlockComment) {
|
|
945
|
+
if (ch === "*" && next === "/") {
|
|
946
|
+
inBlockComment = false;
|
|
947
|
+
i += 1;
|
|
948
|
+
}
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
952
|
+
inLineComment = true;
|
|
953
|
+
i += 1;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
957
|
+
inBlockComment = true;
|
|
958
|
+
i += 1;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
962
|
+
inSingle = !inSingle;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
966
|
+
inDouble = !inDouble;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
970
|
+
count += 1;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return count;
|
|
974
|
+
}
|
|
975
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
976
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
977
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
978
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
979
|
+
for (const match of sql.matchAll(pattern)) {
|
|
980
|
+
const matchText = match[0];
|
|
981
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
982
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return indexes;
|
|
986
|
+
}
|
|
987
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
988
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
989
|
+
if (!match) return;
|
|
990
|
+
const rawTable = match[1];
|
|
991
|
+
const rawColumns = match[2];
|
|
992
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
993
|
+
if (!boolColumns?.size) return;
|
|
994
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
995
|
+
for (const [index, column] of columns.entries()) {
|
|
996
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
997
|
+
args[index] = toBoolean(args[index]);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1002
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1003
|
+
if (!match) return;
|
|
1004
|
+
const rawTable = match[1];
|
|
1005
|
+
const setClause = match[2];
|
|
1006
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1007
|
+
if (!boolColumns?.size) return;
|
|
1008
|
+
const assignments = setClause.split(",");
|
|
1009
|
+
let placeholderIndex = 0;
|
|
1010
|
+
for (const assignment of assignments) {
|
|
1011
|
+
if (!assignment.includes("?")) continue;
|
|
1012
|
+
placeholderIndex += 1;
|
|
1013
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1014
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1015
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function coerceBooleanArgs(sql, args) {
|
|
1020
|
+
const nextArgs = [...args];
|
|
1021
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1022
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1023
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1024
|
+
for (const index of placeholderIndexes) {
|
|
1025
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1026
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return nextArgs;
|
|
1030
|
+
}
|
|
1031
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1032
|
+
let out = "";
|
|
1033
|
+
let placeholder = 0;
|
|
1034
|
+
let inSingle = false;
|
|
1035
|
+
let inDouble = false;
|
|
1036
|
+
let inLineComment = false;
|
|
1037
|
+
let inBlockComment = false;
|
|
1038
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1039
|
+
const ch = sql[i];
|
|
1040
|
+
const next = sql[i + 1];
|
|
1041
|
+
if (inLineComment) {
|
|
1042
|
+
out += ch;
|
|
1043
|
+
if (ch === "\n") inLineComment = false;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (inBlockComment) {
|
|
1047
|
+
out += ch;
|
|
1048
|
+
if (ch === "*" && next === "/") {
|
|
1049
|
+
out += next;
|
|
1050
|
+
inBlockComment = false;
|
|
1051
|
+
i += 1;
|
|
1052
|
+
}
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1056
|
+
out += ch + next;
|
|
1057
|
+
inLineComment = true;
|
|
1058
|
+
i += 1;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1062
|
+
out += ch + next;
|
|
1063
|
+
inBlockComment = true;
|
|
1064
|
+
i += 1;
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1068
|
+
inSingle = !inSingle;
|
|
1069
|
+
out += ch;
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1073
|
+
inDouble = !inDouble;
|
|
1074
|
+
out += ch;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1078
|
+
placeholder += 1;
|
|
1079
|
+
out += `$${placeholder}`;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
out += ch;
|
|
1083
|
+
}
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
function translateStatementForPostgres(stmt) {
|
|
1087
|
+
const normalized = normalizeStatement(stmt);
|
|
1088
|
+
if (normalized.kind === "named") {
|
|
1089
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1090
|
+
}
|
|
1091
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1092
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1093
|
+
return {
|
|
1094
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1095
|
+
args: coercedArgs
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function shouldBypassPostgres(stmt) {
|
|
1099
|
+
const normalized = normalizeStatement(stmt);
|
|
1100
|
+
if (normalized.kind === "named") {
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1104
|
+
}
|
|
1105
|
+
function shouldFallbackOnError(error) {
|
|
1106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1108
|
+
}
|
|
1109
|
+
function isReadQuery(sql) {
|
|
1110
|
+
const trimmed = sql.trimStart();
|
|
1111
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1112
|
+
}
|
|
1113
|
+
function buildRow(row, columns) {
|
|
1114
|
+
const values = columns.map((column) => row[column]);
|
|
1115
|
+
return Object.assign(values, row);
|
|
1116
|
+
}
|
|
1117
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1118
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1119
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1120
|
+
return {
|
|
1121
|
+
columns,
|
|
1122
|
+
columnTypes: columns.map(() => ""),
|
|
1123
|
+
rows: resultRows,
|
|
1124
|
+
rowsAffected,
|
|
1125
|
+
lastInsertRowid: void 0,
|
|
1126
|
+
toJSON() {
|
|
1127
|
+
return {
|
|
1128
|
+
columns,
|
|
1129
|
+
columnTypes: columns.map(() => ""),
|
|
1130
|
+
rows,
|
|
1131
|
+
rowsAffected,
|
|
1132
|
+
lastInsertRowid: void 0
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
async function loadPrismaClient() {
|
|
1138
|
+
if (!prismaClientPromise) {
|
|
1139
|
+
prismaClientPromise = (async () => {
|
|
1140
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1141
|
+
if (explicitPath) {
|
|
1142
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1143
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1144
|
+
if (!PrismaClient2) {
|
|
1145
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1146
|
+
}
|
|
1147
|
+
return new PrismaClient2();
|
|
1148
|
+
}
|
|
1149
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1150
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1151
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1152
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1153
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1154
|
+
if (!PrismaClient) {
|
|
1155
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1156
|
+
}
|
|
1157
|
+
return new PrismaClient();
|
|
1158
|
+
})();
|
|
1159
|
+
}
|
|
1160
|
+
return prismaClientPromise;
|
|
1161
|
+
}
|
|
1162
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1163
|
+
if (!compatibilityBootstrapPromise) {
|
|
1164
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1165
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1166
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1167
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1168
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1169
|
+
relation
|
|
1170
|
+
);
|
|
1171
|
+
if (!rows[0]?.regclass) {
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
await prisma.$executeRawUnsafe(
|
|
1175
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
})();
|
|
1179
|
+
}
|
|
1180
|
+
return compatibilityBootstrapPromise;
|
|
1181
|
+
}
|
|
1182
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1183
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1184
|
+
if (isReadQuery(translated.sql)) {
|
|
1185
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1186
|
+
translated.sql,
|
|
1187
|
+
...translated.args
|
|
1188
|
+
);
|
|
1189
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1190
|
+
}
|
|
1191
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1192
|
+
return buildResultSet([], rowsAffected);
|
|
1193
|
+
}
|
|
1194
|
+
function splitSqlStatements(sql) {
|
|
1195
|
+
const parts = [];
|
|
1196
|
+
let current = "";
|
|
1197
|
+
let inSingle = false;
|
|
1198
|
+
let inDouble = false;
|
|
1199
|
+
let inLineComment = false;
|
|
1200
|
+
let inBlockComment = false;
|
|
1201
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1202
|
+
const ch = sql[i];
|
|
1203
|
+
const next = sql[i + 1];
|
|
1204
|
+
if (inLineComment) {
|
|
1205
|
+
current += ch;
|
|
1206
|
+
if (ch === "\n") inLineComment = false;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (inBlockComment) {
|
|
1210
|
+
current += ch;
|
|
1211
|
+
if (ch === "*" && next === "/") {
|
|
1212
|
+
current += next;
|
|
1213
|
+
inBlockComment = false;
|
|
1214
|
+
i += 1;
|
|
1215
|
+
}
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1219
|
+
current += ch + next;
|
|
1220
|
+
inLineComment = true;
|
|
1221
|
+
i += 1;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1225
|
+
current += ch + next;
|
|
1226
|
+
inBlockComment = true;
|
|
1227
|
+
i += 1;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1231
|
+
inSingle = !inSingle;
|
|
1232
|
+
current += ch;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1236
|
+
inDouble = !inDouble;
|
|
1237
|
+
current += ch;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1241
|
+
if (current.trim()) {
|
|
1242
|
+
parts.push(current.trim());
|
|
1243
|
+
}
|
|
1244
|
+
current = "";
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
current += ch;
|
|
1248
|
+
}
|
|
1249
|
+
if (current.trim()) {
|
|
1250
|
+
parts.push(current.trim());
|
|
1251
|
+
}
|
|
1252
|
+
return parts;
|
|
1253
|
+
}
|
|
1254
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1255
|
+
const prisma = await loadPrismaClient();
|
|
1256
|
+
await ensureCompatibilityViews(prisma);
|
|
1257
|
+
let closed = false;
|
|
1258
|
+
let adapter;
|
|
1259
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1260
|
+
if (!fallbackClient) {
|
|
1261
|
+
if (error) throw error;
|
|
1262
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1263
|
+
}
|
|
1264
|
+
if (error) {
|
|
1265
|
+
process.stderr.write(
|
|
1266
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1267
|
+
`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
return fallbackClient.execute(stmt);
|
|
1271
|
+
};
|
|
1272
|
+
adapter = {
|
|
1273
|
+
async execute(stmt) {
|
|
1274
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1275
|
+
return fallbackExecute(stmt);
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
if (shouldFallbackOnError(error)) {
|
|
1281
|
+
return fallbackExecute(stmt, error);
|
|
1282
|
+
}
|
|
1283
|
+
throw error;
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
async batch(stmts, mode) {
|
|
1287
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1288
|
+
if (!fallbackClient) {
|
|
1289
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1290
|
+
}
|
|
1291
|
+
return fallbackClient.batch(stmts, mode);
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
if (prisma.$transaction) {
|
|
1295
|
+
return await prisma.$transaction(async (tx) => {
|
|
1296
|
+
const results2 = [];
|
|
1297
|
+
for (const stmt of stmts) {
|
|
1298
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1299
|
+
}
|
|
1300
|
+
return results2;
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
const results = [];
|
|
1304
|
+
for (const stmt of stmts) {
|
|
1305
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1306
|
+
}
|
|
1307
|
+
return results;
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1310
|
+
process.stderr.write(
|
|
1311
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1312
|
+
`
|
|
1313
|
+
);
|
|
1314
|
+
return fallbackClient.batch(stmts, mode);
|
|
1315
|
+
}
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
async migrate(stmts) {
|
|
1320
|
+
if (fallbackClient) {
|
|
1321
|
+
return fallbackClient.migrate(stmts);
|
|
1322
|
+
}
|
|
1323
|
+
return adapter.batch(stmts, "deferred");
|
|
1324
|
+
},
|
|
1325
|
+
async transaction(mode) {
|
|
1326
|
+
if (!fallbackClient) {
|
|
1327
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1328
|
+
}
|
|
1329
|
+
return fallbackClient.transaction(mode);
|
|
1330
|
+
},
|
|
1331
|
+
async executeMultiple(sql) {
|
|
1332
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1333
|
+
return fallbackClient.executeMultiple(sql);
|
|
1334
|
+
}
|
|
1335
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1336
|
+
await adapter.execute(statement);
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
async sync() {
|
|
1340
|
+
if (fallbackClient) {
|
|
1341
|
+
return fallbackClient.sync();
|
|
1342
|
+
}
|
|
1343
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1344
|
+
},
|
|
1345
|
+
close() {
|
|
1346
|
+
closed = true;
|
|
1347
|
+
prismaClientPromise = null;
|
|
1348
|
+
compatibilityBootstrapPromise = null;
|
|
1349
|
+
void prisma.$disconnect?.();
|
|
1350
|
+
},
|
|
1351
|
+
get closed() {
|
|
1352
|
+
return closed;
|
|
1353
|
+
},
|
|
1354
|
+
get protocol() {
|
|
1355
|
+
return "prisma-postgres";
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
return adapter;
|
|
1359
|
+
}
|
|
1360
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1361
|
+
var init_database_adapter = __esm({
|
|
1362
|
+
"src/lib/database-adapter.ts"() {
|
|
1363
|
+
"use strict";
|
|
1364
|
+
VIEW_MAPPINGS = [
|
|
1365
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1366
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1367
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1368
|
+
{ view: "entities", source: "memory.entities" },
|
|
1369
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1370
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1371
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1372
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1373
|
+
{ view: "messages", source: "memory.messages" },
|
|
1374
|
+
{ view: "users", source: "wiki.users" },
|
|
1375
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1376
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1377
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1378
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1379
|
+
];
|
|
1380
|
+
UPSERT_KEYS = {
|
|
1381
|
+
memories: ["id"],
|
|
1382
|
+
tasks: ["id"],
|
|
1383
|
+
behaviors: ["id"],
|
|
1384
|
+
entities: ["id"],
|
|
1385
|
+
relationships: ["id"],
|
|
1386
|
+
entity_aliases: ["alias"],
|
|
1387
|
+
notifications: ["id"],
|
|
1388
|
+
messages: ["id"],
|
|
1389
|
+
users: ["id"],
|
|
1390
|
+
workspaces: ["id"],
|
|
1391
|
+
workspace_users: ["id"],
|
|
1392
|
+
documents: ["id"],
|
|
1393
|
+
chats: ["id"]
|
|
1394
|
+
};
|
|
1395
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1396
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1397
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1398
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1399
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1400
|
+
};
|
|
1401
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1402
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1403
|
+
);
|
|
1404
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1405
|
+
/\bPRAGMA\b/i,
|
|
1406
|
+
/\bsqlite_master\b/i,
|
|
1407
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1408
|
+
/\bMATCH\b/i,
|
|
1409
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1410
|
+
/\bjson_extract\s*\(/i,
|
|
1411
|
+
/\bjulianday\s*\(/i,
|
|
1412
|
+
/\bstrftime\s*\(/i,
|
|
1413
|
+
/\blast_insert_rowid\s*\(/i
|
|
1414
|
+
];
|
|
1415
|
+
prismaClientPromise = null;
|
|
1416
|
+
compatibilityBootstrapPromise = null;
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
// src/lib/daemon-auth.ts
|
|
1421
|
+
import crypto from "crypto";
|
|
1422
|
+
import path8 from "path";
|
|
1423
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1424
|
+
function normalizeToken(token) {
|
|
1425
|
+
if (!token) return null;
|
|
1426
|
+
const trimmed = token.trim();
|
|
1427
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1428
|
+
}
|
|
1429
|
+
function readDaemonToken() {
|
|
1430
|
+
try {
|
|
1431
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1432
|
+
return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function ensureDaemonToken(seed) {
|
|
1438
|
+
const existing = readDaemonToken();
|
|
1439
|
+
if (existing) return existing;
|
|
1440
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1441
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1442
|
+
writeFileSync6(DAEMON_TOKEN_PATH, `${token}
|
|
1443
|
+
`, "utf8");
|
|
1444
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1445
|
+
return token;
|
|
1446
|
+
}
|
|
1447
|
+
var DAEMON_TOKEN_PATH;
|
|
1448
|
+
var init_daemon_auth = __esm({
|
|
1449
|
+
"src/lib/daemon-auth.ts"() {
|
|
1450
|
+
"use strict";
|
|
1451
|
+
init_config();
|
|
1452
|
+
init_secure_files();
|
|
1453
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
794
1457
|
// src/lib/exe-daemon-client.ts
|
|
795
1458
|
import net from "net";
|
|
796
|
-
import
|
|
1459
|
+
import os6 from "os";
|
|
797
1460
|
import { spawn } from "child_process";
|
|
798
1461
|
import { randomUUID } from "crypto";
|
|
799
|
-
import { existsSync as
|
|
800
|
-
import
|
|
1462
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
1463
|
+
import path9 from "path";
|
|
801
1464
|
import { fileURLToPath } from "url";
|
|
802
1465
|
function handleData(chunk) {
|
|
803
1466
|
_buffer += chunk.toString();
|
|
@@ -825,9 +1488,9 @@ function handleData(chunk) {
|
|
|
825
1488
|
}
|
|
826
1489
|
}
|
|
827
1490
|
function cleanupStaleFiles() {
|
|
828
|
-
if (
|
|
1491
|
+
if (existsSync8(PID_PATH)) {
|
|
829
1492
|
try {
|
|
830
|
-
const pid = parseInt(
|
|
1493
|
+
const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
|
|
831
1494
|
if (pid > 0) {
|
|
832
1495
|
try {
|
|
833
1496
|
process.kill(pid, 0);
|
|
@@ -848,17 +1511,17 @@ function cleanupStaleFiles() {
|
|
|
848
1511
|
}
|
|
849
1512
|
}
|
|
850
1513
|
function findPackageRoot() {
|
|
851
|
-
let dir =
|
|
852
|
-
const { root } =
|
|
1514
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1515
|
+
const { root } = path9.parse(dir);
|
|
853
1516
|
while (dir !== root) {
|
|
854
|
-
if (
|
|
855
|
-
dir =
|
|
1517
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1518
|
+
dir = path9.dirname(dir);
|
|
856
1519
|
}
|
|
857
1520
|
return null;
|
|
858
1521
|
}
|
|
859
1522
|
function spawnDaemon() {
|
|
860
|
-
const freeGB =
|
|
861
|
-
const totalGB =
|
|
1523
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1524
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
862
1525
|
if (totalGB <= 8) {
|
|
863
1526
|
process.stderr.write(
|
|
864
1527
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -878,16 +1541,17 @@ function spawnDaemon() {
|
|
|
878
1541
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
879
1542
|
return;
|
|
880
1543
|
}
|
|
881
|
-
const daemonPath =
|
|
882
|
-
if (!
|
|
1544
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1545
|
+
if (!existsSync8(daemonPath)) {
|
|
883
1546
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
884
1547
|
`);
|
|
885
1548
|
return;
|
|
886
1549
|
}
|
|
887
1550
|
const resolvedPath = daemonPath;
|
|
1551
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
888
1552
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
889
1553
|
`);
|
|
890
|
-
const logPath =
|
|
1554
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
891
1555
|
let stderrFd = "ignore";
|
|
892
1556
|
try {
|
|
893
1557
|
stderrFd = openSync(logPath, "a");
|
|
@@ -905,7 +1569,8 @@ function spawnDaemon() {
|
|
|
905
1569
|
TMUX_PANE: void 0,
|
|
906
1570
|
// Prevents resolveExeSession() from scoping to one session
|
|
907
1571
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
908
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1572
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1573
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
909
1574
|
}
|
|
910
1575
|
});
|
|
911
1576
|
child.unref();
|
|
@@ -1012,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1012
1677
|
return;
|
|
1013
1678
|
}
|
|
1014
1679
|
const id = randomUUID();
|
|
1680
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1015
1681
|
const timer = setTimeout(() => {
|
|
1016
1682
|
_pending.delete(id);
|
|
1017
1683
|
resolve({ error: "Request timeout" });
|
|
1018
1684
|
}, timeoutMs);
|
|
1019
1685
|
_pending.set(id, { resolve, timer });
|
|
1020
1686
|
try {
|
|
1021
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1687
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1022
1688
|
} catch {
|
|
1023
1689
|
clearTimeout(timer);
|
|
1024
1690
|
_pending.delete(id);
|
|
@@ -1029,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1029
1695
|
function isClientConnected() {
|
|
1030
1696
|
return _connected;
|
|
1031
1697
|
}
|
|
1032
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1698
|
+
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;
|
|
1033
1699
|
var init_exe_daemon_client = __esm({
|
|
1034
1700
|
"src/lib/exe-daemon-client.ts"() {
|
|
1035
1701
|
"use strict";
|
|
1036
1702
|
init_config();
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1703
|
+
init_daemon_auth();
|
|
1704
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1705
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1706
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1040
1707
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1041
1708
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1042
1709
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1710
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1043
1711
|
_socket = null;
|
|
1044
1712
|
_connected = false;
|
|
1045
1713
|
_buffer = "";
|
|
@@ -1118,7 +1786,7 @@ __export(db_daemon_client_exports, {
|
|
|
1118
1786
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1119
1787
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1120
1788
|
});
|
|
1121
|
-
function
|
|
1789
|
+
function normalizeStatement2(stmt) {
|
|
1122
1790
|
if (typeof stmt === "string") {
|
|
1123
1791
|
return { sql: stmt, args: [] };
|
|
1124
1792
|
}
|
|
@@ -1142,7 +1810,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1142
1810
|
if (!_useDaemon || !isClientConnected()) {
|
|
1143
1811
|
return fallbackClient.execute(stmt);
|
|
1144
1812
|
}
|
|
1145
|
-
const { sql, args } =
|
|
1813
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1146
1814
|
const response = await sendDaemonRequest({
|
|
1147
1815
|
type: "db-execute",
|
|
1148
1816
|
sql,
|
|
@@ -1167,7 +1835,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1167
1835
|
if (!_useDaemon || !isClientConnected()) {
|
|
1168
1836
|
return fallbackClient.batch(stmts, mode);
|
|
1169
1837
|
}
|
|
1170
|
-
const statements = stmts.map(
|
|
1838
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1171
1839
|
const response = await sendDaemonRequest({
|
|
1172
1840
|
type: "db-batch",
|
|
1173
1841
|
statements,
|
|
@@ -1262,6 +1930,18 @@ __export(database_exports, {
|
|
|
1262
1930
|
});
|
|
1263
1931
|
import { createClient } from "@libsql/client";
|
|
1264
1932
|
async function initDatabase(config) {
|
|
1933
|
+
if (_walCheckpointTimer) {
|
|
1934
|
+
clearInterval(_walCheckpointTimer);
|
|
1935
|
+
_walCheckpointTimer = null;
|
|
1936
|
+
}
|
|
1937
|
+
if (_daemonClient) {
|
|
1938
|
+
_daemonClient.close();
|
|
1939
|
+
_daemonClient = null;
|
|
1940
|
+
}
|
|
1941
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1942
|
+
_adapterClient.close();
|
|
1943
|
+
}
|
|
1944
|
+
_adapterClient = null;
|
|
1265
1945
|
if (_client) {
|
|
1266
1946
|
_client.close();
|
|
1267
1947
|
_client = null;
|
|
@@ -1275,6 +1955,7 @@ async function initDatabase(config) {
|
|
|
1275
1955
|
}
|
|
1276
1956
|
_client = createClient(opts);
|
|
1277
1957
|
_resilientClient = wrapWithRetry(_client);
|
|
1958
|
+
_adapterClient = _resilientClient;
|
|
1278
1959
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1279
1960
|
});
|
|
1280
1961
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1285,14 +1966,20 @@ async function initDatabase(config) {
|
|
|
1285
1966
|
});
|
|
1286
1967
|
}, 3e4);
|
|
1287
1968
|
_walCheckpointTimer.unref();
|
|
1969
|
+
if (process.env.DATABASE_URL) {
|
|
1970
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1971
|
+
}
|
|
1288
1972
|
}
|
|
1289
1973
|
function isInitialized() {
|
|
1290
|
-
return _client !== null;
|
|
1974
|
+
return _adapterClient !== null || _client !== null;
|
|
1291
1975
|
}
|
|
1292
1976
|
function getClient() {
|
|
1293
|
-
if (!
|
|
1977
|
+
if (!_adapterClient) {
|
|
1294
1978
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1295
1979
|
}
|
|
1980
|
+
if (process.env.DATABASE_URL) {
|
|
1981
|
+
return _adapterClient;
|
|
1982
|
+
}
|
|
1296
1983
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1297
1984
|
return _resilientClient;
|
|
1298
1985
|
}
|
|
@@ -1302,6 +1989,7 @@ function getClient() {
|
|
|
1302
1989
|
return _resilientClient;
|
|
1303
1990
|
}
|
|
1304
1991
|
async function initDaemonClient() {
|
|
1992
|
+
if (process.env.DATABASE_URL) return;
|
|
1305
1993
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1306
1994
|
if (!_resilientClient) return;
|
|
1307
1995
|
try {
|
|
@@ -1598,6 +2286,7 @@ async function ensureSchema() {
|
|
|
1598
2286
|
project TEXT NOT NULL,
|
|
1599
2287
|
summary TEXT NOT NULL,
|
|
1600
2288
|
task_file TEXT,
|
|
2289
|
+
session_scope TEXT,
|
|
1601
2290
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1602
2291
|
created_at TEXT NOT NULL
|
|
1603
2292
|
);
|
|
@@ -1606,7 +2295,7 @@ async function ensureSchema() {
|
|
|
1606
2295
|
ON notifications(read);
|
|
1607
2296
|
|
|
1608
2297
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1609
|
-
ON notifications(agent_id);
|
|
2298
|
+
ON notifications(agent_id, session_scope);
|
|
1610
2299
|
|
|
1611
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1612
2301
|
ON notifications(task_file);
|
|
@@ -1644,6 +2333,7 @@ async function ensureSchema() {
|
|
|
1644
2333
|
target_agent TEXT NOT NULL,
|
|
1645
2334
|
target_project TEXT,
|
|
1646
2335
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2336
|
+
session_scope TEXT,
|
|
1647
2337
|
content TEXT NOT NULL,
|
|
1648
2338
|
priority TEXT DEFAULT 'normal',
|
|
1649
2339
|
status TEXT DEFAULT 'pending',
|
|
@@ -1657,10 +2347,31 @@ async function ensureSchema() {
|
|
|
1657
2347
|
);
|
|
1658
2348
|
|
|
1659
2349
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1660
|
-
ON messages(target_agent, status);
|
|
2350
|
+
ON messages(target_agent, session_scope, status);
|
|
1661
2351
|
|
|
1662
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1663
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2353
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2354
|
+
`);
|
|
2355
|
+
try {
|
|
2356
|
+
await client.execute({
|
|
2357
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2358
|
+
args: []
|
|
2359
|
+
});
|
|
2360
|
+
} catch {
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
await client.execute({
|
|
2364
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2365
|
+
args: []
|
|
2366
|
+
});
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
await client.executeMultiple(`
|
|
2370
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2371
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2372
|
+
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2374
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1664
2375
|
`);
|
|
1665
2376
|
try {
|
|
1666
2377
|
await client.execute({
|
|
@@ -2244,46 +2955,66 @@ async function ensureSchema() {
|
|
|
2244
2955
|
} catch {
|
|
2245
2956
|
}
|
|
2246
2957
|
}
|
|
2958
|
+
try {
|
|
2959
|
+
await client.execute({
|
|
2960
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2961
|
+
args: []
|
|
2962
|
+
});
|
|
2963
|
+
} catch {
|
|
2964
|
+
}
|
|
2247
2965
|
}
|
|
2248
2966
|
async function disposeDatabase() {
|
|
2967
|
+
if (_walCheckpointTimer) {
|
|
2968
|
+
clearInterval(_walCheckpointTimer);
|
|
2969
|
+
_walCheckpointTimer = null;
|
|
2970
|
+
}
|
|
2249
2971
|
if (_daemonClient) {
|
|
2250
2972
|
_daemonClient.close();
|
|
2251
2973
|
_daemonClient = null;
|
|
2252
2974
|
}
|
|
2975
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2976
|
+
_adapterClient.close();
|
|
2977
|
+
}
|
|
2978
|
+
_adapterClient = null;
|
|
2253
2979
|
if (_client) {
|
|
2254
2980
|
_client.close();
|
|
2255
2981
|
_client = null;
|
|
2256
2982
|
_resilientClient = null;
|
|
2257
2983
|
}
|
|
2258
2984
|
}
|
|
2259
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2985
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2260
2986
|
var init_database = __esm({
|
|
2261
2987
|
"src/lib/database.ts"() {
|
|
2262
2988
|
"use strict";
|
|
2263
2989
|
init_db_retry();
|
|
2264
2990
|
init_employees();
|
|
2991
|
+
init_database_adapter();
|
|
2265
2992
|
_client = null;
|
|
2266
2993
|
_resilientClient = null;
|
|
2267
2994
|
_walCheckpointTimer = null;
|
|
2268
2995
|
_daemonClient = null;
|
|
2996
|
+
_adapterClient = null;
|
|
2269
2997
|
initTurso = initDatabase;
|
|
2270
2998
|
disposeTurso = disposeDatabase;
|
|
2271
2999
|
}
|
|
2272
3000
|
});
|
|
2273
3001
|
|
|
2274
3002
|
// src/lib/license.ts
|
|
2275
|
-
import { readFileSync as
|
|
3003
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2276
3004
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2277
|
-
import
|
|
3005
|
+
import { createRequire as createRequire2 } from "module";
|
|
3006
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3007
|
+
import os7 from "os";
|
|
3008
|
+
import path10 from "path";
|
|
2278
3009
|
import { jwtVerify, importSPKI } from "jose";
|
|
2279
3010
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2280
3011
|
var init_license = __esm({
|
|
2281
3012
|
"src/lib/license.ts"() {
|
|
2282
3013
|
"use strict";
|
|
2283
3014
|
init_config();
|
|
2284
|
-
LICENSE_PATH =
|
|
2285
|
-
CACHE_PATH =
|
|
2286
|
-
DEVICE_ID_PATH =
|
|
3015
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3016
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3017
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2287
3018
|
PLAN_LIMITS = {
|
|
2288
3019
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2289
3020
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2295,12 +3026,12 @@ var init_license = __esm({
|
|
|
2295
3026
|
});
|
|
2296
3027
|
|
|
2297
3028
|
// src/lib/plan-limits.ts
|
|
2298
|
-
import { readFileSync as
|
|
2299
|
-
import
|
|
3029
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3030
|
+
import path11 from "path";
|
|
2300
3031
|
function getLicenseSync() {
|
|
2301
3032
|
try {
|
|
2302
|
-
if (!
|
|
2303
|
-
const raw = JSON.parse(
|
|
3033
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3034
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
2304
3035
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2305
3036
|
const parts = raw.token.split(".");
|
|
2306
3037
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2338,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2338
3069
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2339
3070
|
let count = 0;
|
|
2340
3071
|
try {
|
|
2341
|
-
if (
|
|
2342
|
-
const raw =
|
|
3072
|
+
if (existsSync10(filePath)) {
|
|
3073
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2343
3074
|
const employees = JSON.parse(raw);
|
|
2344
3075
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2345
3076
|
}
|
|
@@ -2368,29 +3099,30 @@ var init_plan_limits = __esm({
|
|
|
2368
3099
|
this.name = "PlanLimitError";
|
|
2369
3100
|
}
|
|
2370
3101
|
};
|
|
2371
|
-
CACHE_PATH2 =
|
|
3102
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2372
3103
|
}
|
|
2373
3104
|
});
|
|
2374
3105
|
|
|
2375
3106
|
// src/lib/notifications.ts
|
|
2376
|
-
import
|
|
2377
|
-
import
|
|
2378
|
-
import
|
|
3107
|
+
import crypto2 from "crypto";
|
|
3108
|
+
import path12 from "path";
|
|
3109
|
+
import os8 from "os";
|
|
2379
3110
|
import {
|
|
2380
|
-
readFileSync as
|
|
3111
|
+
readFileSync as readFileSync11,
|
|
2381
3112
|
readdirSync as readdirSync2,
|
|
2382
3113
|
unlinkSync as unlinkSync4,
|
|
2383
|
-
existsSync as
|
|
3114
|
+
existsSync as existsSync11,
|
|
2384
3115
|
rmdirSync
|
|
2385
3116
|
} from "fs";
|
|
2386
3117
|
async function writeNotification(notification) {
|
|
2387
3118
|
try {
|
|
2388
3119
|
const client = getClient();
|
|
2389
|
-
const id =
|
|
3120
|
+
const id = crypto2.randomUUID();
|
|
2390
3121
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3122
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2391
3123
|
await client.execute({
|
|
2392
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2393
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3124
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3125
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2394
3126
|
args: [
|
|
2395
3127
|
id,
|
|
2396
3128
|
notification.agentId,
|
|
@@ -2399,6 +3131,7 @@ async function writeNotification(notification) {
|
|
|
2399
3131
|
notification.project,
|
|
2400
3132
|
notification.summary,
|
|
2401
3133
|
notification.taskFile ?? null,
|
|
3134
|
+
sessionScope,
|
|
2402
3135
|
now
|
|
2403
3136
|
]
|
|
2404
3137
|
});
|
|
@@ -2407,12 +3140,14 @@ async function writeNotification(notification) {
|
|
|
2407
3140
|
`);
|
|
2408
3141
|
}
|
|
2409
3142
|
}
|
|
2410
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3143
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2411
3144
|
try {
|
|
2412
3145
|
const client = getClient();
|
|
3146
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2413
3147
|
await client.execute({
|
|
2414
|
-
sql:
|
|
2415
|
-
|
|
3148
|
+
sql: `UPDATE notifications SET read = 1
|
|
3149
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3150
|
+
args: [taskFile, ...scope.args]
|
|
2416
3151
|
});
|
|
2417
3152
|
} catch {
|
|
2418
3153
|
}
|
|
@@ -2421,11 +3156,12 @@ var init_notifications = __esm({
|
|
|
2421
3156
|
"src/lib/notifications.ts"() {
|
|
2422
3157
|
"use strict";
|
|
2423
3158
|
init_database();
|
|
3159
|
+
init_task_scope();
|
|
2424
3160
|
}
|
|
2425
3161
|
});
|
|
2426
3162
|
|
|
2427
3163
|
// src/lib/session-kill-telemetry.ts
|
|
2428
|
-
import
|
|
3164
|
+
import crypto3 from "crypto";
|
|
2429
3165
|
async function recordSessionKill(input2) {
|
|
2430
3166
|
try {
|
|
2431
3167
|
const client = getClient();
|
|
@@ -2435,7 +3171,7 @@ async function recordSessionKill(input2) {
|
|
|
2435
3171
|
ticks_idle, estimated_tokens_saved)
|
|
2436
3172
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2437
3173
|
args: [
|
|
2438
|
-
|
|
3174
|
+
crypto3.randomUUID(),
|
|
2439
3175
|
input2.sessionName,
|
|
2440
3176
|
input2.agentId,
|
|
2441
3177
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2514,12 +3250,12 @@ var init_state_bus = __esm({
|
|
|
2514
3250
|
});
|
|
2515
3251
|
|
|
2516
3252
|
// src/lib/tasks-crud.ts
|
|
2517
|
-
import
|
|
2518
|
-
import
|
|
2519
|
-
import
|
|
3253
|
+
import crypto4 from "crypto";
|
|
3254
|
+
import path13 from "path";
|
|
3255
|
+
import os9 from "os";
|
|
2520
3256
|
import { execSync as execSync5 } from "child_process";
|
|
2521
3257
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2522
|
-
import { existsSync as
|
|
3258
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
2523
3259
|
async function writeCheckpoint(input2) {
|
|
2524
3260
|
const client = getClient();
|
|
2525
3261
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -2635,7 +3371,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2635
3371
|
}
|
|
2636
3372
|
async function createTaskCore(input2) {
|
|
2637
3373
|
const client = getClient();
|
|
2638
|
-
const id =
|
|
3374
|
+
const id = crypto4.randomUUID();
|
|
2639
3375
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2640
3376
|
const slug = slugify(input2.title);
|
|
2641
3377
|
let earlySessionScope = null;
|
|
@@ -2694,8 +3430,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2694
3430
|
}
|
|
2695
3431
|
if (input2.baseDir) {
|
|
2696
3432
|
try {
|
|
2697
|
-
await mkdir3(
|
|
2698
|
-
await mkdir3(
|
|
3433
|
+
await mkdir3(path13.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
3434
|
+
await mkdir3(path13.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
2699
3435
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
2700
3436
|
await ensureGitignoreExe(input2.baseDir);
|
|
2701
3437
|
} catch {
|
|
@@ -2731,13 +3467,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2731
3467
|
});
|
|
2732
3468
|
if (input2.baseDir) {
|
|
2733
3469
|
try {
|
|
2734
|
-
const EXE_OS_DIR =
|
|
2735
|
-
const mdPath =
|
|
2736
|
-
const mdDir =
|
|
2737
|
-
if (!
|
|
3470
|
+
const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
|
|
3471
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3472
|
+
const mdDir = path13.dirname(mdPath);
|
|
3473
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2738
3474
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
2739
3475
|
const mdContent = `# ${input2.title}
|
|
2740
3476
|
|
|
3477
|
+
## MANDATORY: When done
|
|
3478
|
+
|
|
3479
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3480
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3481
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3482
|
+
|
|
2741
3483
|
**ID:** ${id}
|
|
2742
3484
|
**Status:** ${initialStatus}
|
|
2743
3485
|
**Priority:** ${input2.priority}
|
|
@@ -2751,12 +3493,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2751
3493
|
## Context
|
|
2752
3494
|
|
|
2753
3495
|
${input2.context}
|
|
2754
|
-
|
|
2755
|
-
## MANDATORY: When done
|
|
2756
|
-
|
|
2757
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2758
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2759
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2760
3496
|
`;
|
|
2761
3497
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2762
3498
|
} catch (err) {
|
|
@@ -3005,7 +3741,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3005
3741
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3006
3742
|
} catch {
|
|
3007
3743
|
}
|
|
3008
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
3744
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3009
3745
|
try {
|
|
3010
3746
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3011
3747
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3034,9 +3770,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3034
3770
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3035
3771
|
}
|
|
3036
3772
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3037
|
-
const archPath =
|
|
3773
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3038
3774
|
try {
|
|
3039
|
-
if (
|
|
3775
|
+
if (existsSync12(archPath)) return;
|
|
3040
3776
|
const template = [
|
|
3041
3777
|
`# ${projectName} \u2014 System Architecture`,
|
|
3042
3778
|
"",
|
|
@@ -3069,10 +3805,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3069
3805
|
}
|
|
3070
3806
|
}
|
|
3071
3807
|
async function ensureGitignoreExe(baseDir) {
|
|
3072
|
-
const gitignorePath =
|
|
3808
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3073
3809
|
try {
|
|
3074
|
-
if (
|
|
3075
|
-
const content =
|
|
3810
|
+
if (existsSync12(gitignorePath)) {
|
|
3811
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
3076
3812
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3077
3813
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3078
3814
|
} else {
|
|
@@ -3103,58 +3839,42 @@ var init_tasks_crud = __esm({
|
|
|
3103
3839
|
});
|
|
3104
3840
|
|
|
3105
3841
|
// src/lib/tasks-review.ts
|
|
3106
|
-
import
|
|
3107
|
-
import { existsSync as
|
|
3842
|
+
import path14 from "path";
|
|
3843
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
3108
3844
|
async function countPendingReviews(sessionScope) {
|
|
3109
3845
|
const client = getClient();
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
args: [sessionScope]
|
|
3114
|
-
});
|
|
3115
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3116
|
-
}
|
|
3846
|
+
const scope = strictSessionScopeFilter(
|
|
3847
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3848
|
+
);
|
|
3117
3849
|
const result = await client.execute({
|
|
3118
|
-
sql:
|
|
3119
|
-
|
|
3850
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3851
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3852
|
+
args: [...scope.args]
|
|
3120
3853
|
});
|
|
3121
3854
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3122
3855
|
}
|
|
3123
3856
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3124
3857
|
const client = getClient();
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3129
|
-
AND session_scope = ?`,
|
|
3130
|
-
args: [sinceIso, sessionScope]
|
|
3131
|
-
});
|
|
3132
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3133
|
-
}
|
|
3858
|
+
const scope = strictSessionScopeFilter(
|
|
3859
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3860
|
+
);
|
|
3134
3861
|
const result = await client.execute({
|
|
3135
3862
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3136
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3137
|
-
args: [sinceIso]
|
|
3863
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3864
|
+
args: [sinceIso, ...scope.args]
|
|
3138
3865
|
});
|
|
3139
3866
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3140
3867
|
}
|
|
3141
3868
|
async function listPendingReviews(limit, sessionScope) {
|
|
3142
3869
|
const client = getClient();
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
WHERE status = 'needs_review'
|
|
3147
|
-
AND session_scope = ?
|
|
3148
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3149
|
-
args: [sessionScope, limit]
|
|
3150
|
-
});
|
|
3151
|
-
return result2.rows;
|
|
3152
|
-
}
|
|
3870
|
+
const scope = strictSessionScopeFilter(
|
|
3871
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3872
|
+
);
|
|
3153
3873
|
const result = await client.execute({
|
|
3154
3874
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3155
|
-
WHERE status = 'needs_review'
|
|
3875
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3156
3876
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3157
|
-
args: [limit]
|
|
3877
|
+
args: [...scope.args, limit]
|
|
3158
3878
|
});
|
|
3159
3879
|
return result.rows;
|
|
3160
3880
|
}
|
|
@@ -3166,7 +3886,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3166
3886
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3167
3887
|
AND assigned_by = 'system'
|
|
3168
3888
|
AND title LIKE 'Review:%'
|
|
3169
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3889
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3170
3890
|
args: [now]
|
|
3171
3891
|
});
|
|
3172
3892
|
const r1b = await client.execute({
|
|
@@ -3285,11 +4005,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3285
4005
|
);
|
|
3286
4006
|
}
|
|
3287
4007
|
try {
|
|
3288
|
-
const cacheDir =
|
|
3289
|
-
if (
|
|
4008
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4009
|
+
if (existsSync13(cacheDir)) {
|
|
3290
4010
|
for (const f of readdirSync3(cacheDir)) {
|
|
3291
4011
|
if (f.startsWith("review-notified-")) {
|
|
3292
|
-
unlinkSync5(
|
|
4012
|
+
unlinkSync5(path14.join(cacheDir, f));
|
|
3293
4013
|
}
|
|
3294
4014
|
}
|
|
3295
4015
|
}
|
|
@@ -3306,11 +4026,12 @@ var init_tasks_review = __esm({
|
|
|
3306
4026
|
init_tmux_routing();
|
|
3307
4027
|
init_session_key();
|
|
3308
4028
|
init_state_bus();
|
|
4029
|
+
init_task_scope();
|
|
3309
4030
|
}
|
|
3310
4031
|
});
|
|
3311
4032
|
|
|
3312
4033
|
// src/lib/tasks-chain.ts
|
|
3313
|
-
import
|
|
4034
|
+
import path15 from "path";
|
|
3314
4035
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3315
4036
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3316
4037
|
const client = getClient();
|
|
@@ -3327,7 +4048,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3327
4048
|
});
|
|
3328
4049
|
for (const ur of unblockedRows.rows) {
|
|
3329
4050
|
try {
|
|
3330
|
-
const ubFile =
|
|
4051
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3331
4052
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3332
4053
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3333
4054
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3362,7 +4083,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3362
4083
|
const scScope = sessionScopeFilter();
|
|
3363
4084
|
const remaining = await client.execute({
|
|
3364
4085
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3365
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4086
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3366
4087
|
args: [parentTaskId, ...scScope.args]
|
|
3367
4088
|
});
|
|
3368
4089
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3396,7 +4117,7 @@ var init_tasks_chain = __esm({
|
|
|
3396
4117
|
|
|
3397
4118
|
// src/lib/project-name.ts
|
|
3398
4119
|
import { execSync as execSync6 } from "child_process";
|
|
3399
|
-
import
|
|
4120
|
+
import path16 from "path";
|
|
3400
4121
|
function getProjectName(cwd) {
|
|
3401
4122
|
const dir = cwd ?? process.cwd();
|
|
3402
4123
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3409,7 +4130,7 @@ function getProjectName(cwd) {
|
|
|
3409
4130
|
timeout: 2e3,
|
|
3410
4131
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3411
4132
|
}).trim();
|
|
3412
|
-
repoRoot =
|
|
4133
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
3413
4134
|
} catch {
|
|
3414
4135
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3415
4136
|
cwd: dir,
|
|
@@ -3418,11 +4139,11 @@ function getProjectName(cwd) {
|
|
|
3418
4139
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3419
4140
|
}).trim();
|
|
3420
4141
|
}
|
|
3421
|
-
_cached2 =
|
|
4142
|
+
_cached2 = path16.basename(repoRoot);
|
|
3422
4143
|
_cachedCwd = dir;
|
|
3423
4144
|
return _cached2;
|
|
3424
4145
|
} catch {
|
|
3425
|
-
_cached2 =
|
|
4146
|
+
_cached2 = path16.basename(dir);
|
|
3426
4147
|
_cachedCwd = dir;
|
|
3427
4148
|
return _cached2;
|
|
3428
4149
|
}
|
|
@@ -3565,10 +4286,10 @@ var init_tasks_notify = __esm({
|
|
|
3565
4286
|
});
|
|
3566
4287
|
|
|
3567
4288
|
// src/lib/behaviors.ts
|
|
3568
|
-
import
|
|
4289
|
+
import crypto5 from "crypto";
|
|
3569
4290
|
async function storeBehavior(opts) {
|
|
3570
4291
|
const client = getClient();
|
|
3571
|
-
const id =
|
|
4292
|
+
const id = crypto5.randomUUID();
|
|
3572
4293
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3573
4294
|
await client.execute({
|
|
3574
4295
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -3597,7 +4318,7 @@ __export(skill_learning_exports, {
|
|
|
3597
4318
|
storeTrajectory: () => storeTrajectory,
|
|
3598
4319
|
sweepTrajectories: () => sweepTrajectories
|
|
3599
4320
|
});
|
|
3600
|
-
import
|
|
4321
|
+
import crypto6 from "crypto";
|
|
3601
4322
|
async function extractTrajectory(taskId, agentId) {
|
|
3602
4323
|
const client = getClient();
|
|
3603
4324
|
const result = await client.execute({
|
|
@@ -3626,11 +4347,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3626
4347
|
return signature;
|
|
3627
4348
|
}
|
|
3628
4349
|
function hashSignature(signature) {
|
|
3629
|
-
return
|
|
4350
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3630
4351
|
}
|
|
3631
4352
|
async function storeTrajectory(opts) {
|
|
3632
4353
|
const client = getClient();
|
|
3633
|
-
const id =
|
|
4354
|
+
const id = crypto6.randomUUID();
|
|
3634
4355
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3635
4356
|
const signatureHash = hashSignature(opts.signature);
|
|
3636
4357
|
await client.execute({
|
|
@@ -3895,8 +4616,8 @@ __export(tasks_exports, {
|
|
|
3895
4616
|
updateTaskStatus: () => updateTaskStatus,
|
|
3896
4617
|
writeCheckpoint: () => writeCheckpoint
|
|
3897
4618
|
});
|
|
3898
|
-
import
|
|
3899
|
-
import { writeFileSync as
|
|
4619
|
+
import path17 from "path";
|
|
4620
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
3900
4621
|
async function createTask(input2) {
|
|
3901
4622
|
const result = await createTaskCore(input2);
|
|
3902
4623
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3915,12 +4636,12 @@ async function updateTask(input2) {
|
|
|
3915
4636
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
3916
4637
|
try {
|
|
3917
4638
|
const agent = String(row.assigned_to);
|
|
3918
|
-
const cacheDir =
|
|
3919
|
-
const cachePath =
|
|
4639
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4640
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
3920
4641
|
if (input2.status === "in_progress") {
|
|
3921
4642
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3922
|
-
|
|
3923
|
-
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4643
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4644
|
+
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3924
4645
|
try {
|
|
3925
4646
|
unlinkSync6(cachePath);
|
|
3926
4647
|
} catch {
|
|
@@ -3928,10 +4649,10 @@ async function updateTask(input2) {
|
|
|
3928
4649
|
}
|
|
3929
4650
|
} catch {
|
|
3930
4651
|
}
|
|
3931
|
-
if (input2.status === "done") {
|
|
4652
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
3932
4653
|
await cleanupReviewFile(row, taskFile, input2.baseDir);
|
|
3933
4654
|
}
|
|
3934
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
4655
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3935
4656
|
try {
|
|
3936
4657
|
const client = getClient();
|
|
3937
4658
|
const taskTitle = String(row.title);
|
|
@@ -3947,7 +4668,7 @@ async function updateTask(input2) {
|
|
|
3947
4668
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3948
4669
|
try {
|
|
3949
4670
|
const draftClient = getClient();
|
|
3950
|
-
if (input2.status === "done") {
|
|
4671
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
3951
4672
|
await draftClient.execute({
|
|
3952
4673
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3953
4674
|
args: [assignedAgent]
|
|
@@ -3964,7 +4685,7 @@ async function updateTask(input2) {
|
|
|
3964
4685
|
try {
|
|
3965
4686
|
const client = getClient();
|
|
3966
4687
|
const cascaded = await client.execute({
|
|
3967
|
-
sql: `UPDATE tasks SET status = '
|
|
4688
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3968
4689
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3969
4690
|
args: [now, taskId]
|
|
3970
4691
|
});
|
|
@@ -3977,14 +4698,14 @@ async function updateTask(input2) {
|
|
|
3977
4698
|
} catch {
|
|
3978
4699
|
}
|
|
3979
4700
|
}
|
|
3980
|
-
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
4701
|
+
const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
|
|
3981
4702
|
if (isTerminal) {
|
|
3982
4703
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3983
4704
|
if (!isCoordinator) {
|
|
3984
4705
|
notifyTaskDone();
|
|
3985
4706
|
}
|
|
3986
4707
|
await markTaskNotificationsRead(taskFile);
|
|
3987
|
-
if (input2.status === "done") {
|
|
4708
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
3988
4709
|
try {
|
|
3989
4710
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
3990
4711
|
} catch {
|
|
@@ -4004,7 +4725,7 @@ async function updateTask(input2) {
|
|
|
4004
4725
|
}
|
|
4005
4726
|
}
|
|
4006
4727
|
}
|
|
4007
|
-
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4728
|
+
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4008
4729
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4009
4730
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4010
4731
|
taskId,
|
|
@@ -4376,6 +5097,7 @@ __export(tmux_routing_exports, {
|
|
|
4376
5097
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4377
5098
|
isExeSession: () => isExeSession,
|
|
4378
5099
|
isSessionBusy: () => isSessionBusy,
|
|
5100
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4379
5101
|
notifyParentExe: () => notifyParentExe,
|
|
4380
5102
|
parseParentExe: () => parseParentExe,
|
|
4381
5103
|
registerParentExe: () => registerParentExe,
|
|
@@ -4386,13 +5108,13 @@ __export(tmux_routing_exports, {
|
|
|
4386
5108
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4387
5109
|
});
|
|
4388
5110
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4389
|
-
import { readFileSync as
|
|
4390
|
-
import
|
|
4391
|
-
import
|
|
5111
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
5112
|
+
import path18 from "path";
|
|
5113
|
+
import os10 from "os";
|
|
4392
5114
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4393
5115
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
4394
5116
|
function spawnLockPath(sessionName) {
|
|
4395
|
-
return
|
|
5117
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4396
5118
|
}
|
|
4397
5119
|
function isProcessAlive(pid) {
|
|
4398
5120
|
try {
|
|
@@ -4403,13 +5125,13 @@ function isProcessAlive(pid) {
|
|
|
4403
5125
|
}
|
|
4404
5126
|
}
|
|
4405
5127
|
function acquireSpawnLock2(sessionName) {
|
|
4406
|
-
if (!
|
|
5128
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4407
5129
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
4408
5130
|
}
|
|
4409
5131
|
const lockFile = spawnLockPath(sessionName);
|
|
4410
|
-
if (
|
|
5132
|
+
if (existsSync14(lockFile)) {
|
|
4411
5133
|
try {
|
|
4412
|
-
const lock = JSON.parse(
|
|
5134
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
4413
5135
|
const age = Date.now() - lock.timestamp;
|
|
4414
5136
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4415
5137
|
return false;
|
|
@@ -4417,7 +5139,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4417
5139
|
} catch {
|
|
4418
5140
|
}
|
|
4419
5141
|
}
|
|
4420
|
-
|
|
5142
|
+
writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4421
5143
|
return true;
|
|
4422
5144
|
}
|
|
4423
5145
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4429,13 +5151,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4429
5151
|
function resolveBehaviorsExporterScript() {
|
|
4430
5152
|
try {
|
|
4431
5153
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4432
|
-
const scriptPath =
|
|
4433
|
-
|
|
5154
|
+
const scriptPath = path18.join(
|
|
5155
|
+
path18.dirname(thisFile),
|
|
4434
5156
|
"..",
|
|
4435
5157
|
"bin",
|
|
4436
5158
|
"exe-export-behaviors.js"
|
|
4437
5159
|
);
|
|
4438
|
-
return
|
|
5160
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4439
5161
|
} catch {
|
|
4440
5162
|
return null;
|
|
4441
5163
|
}
|
|
@@ -4501,12 +5223,12 @@ function extractRootExe(name) {
|
|
|
4501
5223
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4502
5224
|
}
|
|
4503
5225
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4504
|
-
if (!
|
|
5226
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4505
5227
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
4506
5228
|
}
|
|
4507
5229
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4508
|
-
const filePath =
|
|
4509
|
-
|
|
5230
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5231
|
+
writeFileSync9(filePath, JSON.stringify({
|
|
4510
5232
|
parentExe: rootExe,
|
|
4511
5233
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4512
5234
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4514,7 +5236,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4514
5236
|
}
|
|
4515
5237
|
function getParentExe(sessionKey) {
|
|
4516
5238
|
try {
|
|
4517
|
-
const data = JSON.parse(
|
|
5239
|
+
const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4518
5240
|
return data.parentExe || null;
|
|
4519
5241
|
} catch {
|
|
4520
5242
|
return null;
|
|
@@ -4522,8 +5244,8 @@ function getParentExe(sessionKey) {
|
|
|
4522
5244
|
}
|
|
4523
5245
|
function getDispatchedBy(sessionKey) {
|
|
4524
5246
|
try {
|
|
4525
|
-
const data = JSON.parse(
|
|
4526
|
-
|
|
5247
|
+
const data = JSON.parse(readFileSync13(
|
|
5248
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4527
5249
|
"utf8"
|
|
4528
5250
|
));
|
|
4529
5251
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4593,8 +5315,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4593
5315
|
}
|
|
4594
5316
|
function readDebounceState() {
|
|
4595
5317
|
try {
|
|
4596
|
-
if (!
|
|
4597
|
-
const raw = JSON.parse(
|
|
5318
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5319
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
4598
5320
|
const state = {};
|
|
4599
5321
|
for (const [key, val] of Object.entries(raw)) {
|
|
4600
5322
|
if (typeof val === "number") {
|
|
@@ -4610,8 +5332,8 @@ function readDebounceState() {
|
|
|
4610
5332
|
}
|
|
4611
5333
|
function writeDebounceState(state) {
|
|
4612
5334
|
try {
|
|
4613
|
-
if (!
|
|
4614
|
-
|
|
5335
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5336
|
+
writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4615
5337
|
} catch {
|
|
4616
5338
|
}
|
|
4617
5339
|
}
|
|
@@ -4709,8 +5431,8 @@ function sendIntercom(targetSession) {
|
|
|
4709
5431
|
try {
|
|
4710
5432
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4711
5433
|
const agent = baseAgentName(rawAgent);
|
|
4712
|
-
const markerPath =
|
|
4713
|
-
if (
|
|
5434
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5435
|
+
if (existsSync14(markerPath)) {
|
|
4714
5436
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4715
5437
|
return "debounced";
|
|
4716
5438
|
}
|
|
@@ -4719,8 +5441,8 @@ function sendIntercom(targetSession) {
|
|
|
4719
5441
|
try {
|
|
4720
5442
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4721
5443
|
const agent = baseAgentName(rawAgent);
|
|
4722
|
-
const taskDir =
|
|
4723
|
-
if (
|
|
5444
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5445
|
+
if (existsSync14(taskDir)) {
|
|
4724
5446
|
const files = readdirSync4(taskDir).filter(
|
|
4725
5447
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4726
5448
|
);
|
|
@@ -4780,6 +5502,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4780
5502
|
}
|
|
4781
5503
|
return true;
|
|
4782
5504
|
}
|
|
5505
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5506
|
+
const transport = getTransport();
|
|
5507
|
+
try {
|
|
5508
|
+
const sessions = transport.listSessions();
|
|
5509
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5510
|
+
execSync7(
|
|
5511
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5512
|
+
{ timeout: 3e3 }
|
|
5513
|
+
);
|
|
5514
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5515
|
+
return true;
|
|
5516
|
+
} catch {
|
|
5517
|
+
return false;
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
4783
5520
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4784
5521
|
if (isCoordinatorName(employeeName)) {
|
|
4785
5522
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4853,26 +5590,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4853
5590
|
const transport = getTransport();
|
|
4854
5591
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4855
5592
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4856
|
-
const logDir =
|
|
4857
|
-
const logFile =
|
|
4858
|
-
if (!
|
|
5593
|
+
const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5594
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5595
|
+
if (!existsSync14(logDir)) {
|
|
4859
5596
|
mkdirSync7(logDir, { recursive: true });
|
|
4860
5597
|
}
|
|
4861
5598
|
transport.kill(sessionName);
|
|
4862
5599
|
let cleanupSuffix = "";
|
|
4863
5600
|
try {
|
|
4864
5601
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4865
|
-
const cleanupScript =
|
|
4866
|
-
if (
|
|
5602
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5603
|
+
if (existsSync14(cleanupScript)) {
|
|
4867
5604
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4868
5605
|
}
|
|
4869
5606
|
} catch {
|
|
4870
5607
|
}
|
|
4871
5608
|
try {
|
|
4872
|
-
const claudeJsonPath =
|
|
5609
|
+
const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
|
|
4873
5610
|
let claudeJson = {};
|
|
4874
5611
|
try {
|
|
4875
|
-
claudeJson = JSON.parse(
|
|
5612
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
4876
5613
|
} catch {
|
|
4877
5614
|
}
|
|
4878
5615
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4880,17 +5617,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4880
5617
|
const trustDir = opts?.cwd ?? projectDir;
|
|
4881
5618
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4882
5619
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4883
|
-
|
|
5620
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4884
5621
|
} catch {
|
|
4885
5622
|
}
|
|
4886
5623
|
try {
|
|
4887
|
-
const settingsDir =
|
|
5624
|
+
const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
|
|
4888
5625
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4889
|
-
const projSettingsDir =
|
|
4890
|
-
const settingsPath =
|
|
5626
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5627
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
4891
5628
|
let settings = {};
|
|
4892
5629
|
try {
|
|
4893
|
-
settings = JSON.parse(
|
|
5630
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
4894
5631
|
} catch {
|
|
4895
5632
|
}
|
|
4896
5633
|
const perms = settings.permissions ?? {};
|
|
@@ -4919,7 +5656,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4919
5656
|
perms.allow = allow;
|
|
4920
5657
|
settings.permissions = perms;
|
|
4921
5658
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
4922
|
-
|
|
5659
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4923
5660
|
}
|
|
4924
5661
|
} catch {
|
|
4925
5662
|
}
|
|
@@ -4934,8 +5671,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4934
5671
|
let behaviorsFlag = "";
|
|
4935
5672
|
let legacyFallbackWarned = false;
|
|
4936
5673
|
if (!useExeAgent && !useBinSymlink) {
|
|
4937
|
-
const identityPath =
|
|
4938
|
-
|
|
5674
|
+
const identityPath = path18.join(
|
|
5675
|
+
os10.homedir(),
|
|
4939
5676
|
".exe-os",
|
|
4940
5677
|
"identity",
|
|
4941
5678
|
`${employeeName}.md`
|
|
@@ -4944,13 +5681,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4944
5681
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4945
5682
|
if (hasAgentFlag) {
|
|
4946
5683
|
identityFlag = ` --agent ${employeeName}`;
|
|
4947
|
-
} else if (
|
|
5684
|
+
} else if (existsSync14(identityPath)) {
|
|
4948
5685
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4949
5686
|
legacyFallbackWarned = true;
|
|
4950
5687
|
}
|
|
4951
5688
|
const behaviorsFile = exportBehaviorsSync(
|
|
4952
5689
|
employeeName,
|
|
4953
|
-
|
|
5690
|
+
path18.basename(spawnCwd),
|
|
4954
5691
|
sessionName
|
|
4955
5692
|
);
|
|
4956
5693
|
if (behaviorsFile) {
|
|
@@ -4965,16 +5702,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4965
5702
|
}
|
|
4966
5703
|
let sessionContextFlag = "";
|
|
4967
5704
|
try {
|
|
4968
|
-
const ctxDir =
|
|
5705
|
+
const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
4969
5706
|
mkdirSync7(ctxDir, { recursive: true });
|
|
4970
|
-
const ctxFile =
|
|
5707
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4971
5708
|
const ctxContent = [
|
|
4972
5709
|
`## Session Context`,
|
|
4973
5710
|
`You are running in tmux session: ${sessionName}.`,
|
|
4974
5711
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4975
5712
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4976
5713
|
].join("\n");
|
|
4977
|
-
|
|
5714
|
+
writeFileSync9(ctxFile, ctxContent);
|
|
4978
5715
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4979
5716
|
} catch {
|
|
4980
5717
|
}
|
|
@@ -5051,8 +5788,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5051
5788
|
transport.pipeLog(sessionName, logFile);
|
|
5052
5789
|
try {
|
|
5053
5790
|
const mySession = getMySession();
|
|
5054
|
-
const dispatchInfo =
|
|
5055
|
-
|
|
5791
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5792
|
+
writeFileSync9(dispatchInfo, JSON.stringify({
|
|
5056
5793
|
dispatchedBy: mySession,
|
|
5057
5794
|
rootExe: exeSession,
|
|
5058
5795
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5126,15 +5863,15 @@ var init_tmux_routing = __esm({
|
|
|
5126
5863
|
init_intercom_queue();
|
|
5127
5864
|
init_plan_limits();
|
|
5128
5865
|
init_employees();
|
|
5129
|
-
SPAWN_LOCK_DIR =
|
|
5130
|
-
SESSION_CACHE =
|
|
5866
|
+
SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5867
|
+
SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5131
5868
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5132
5869
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5133
5870
|
VERIFY_PANE_LINES = 200;
|
|
5134
5871
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5135
5872
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5136
|
-
INTERCOM_LOG2 =
|
|
5137
|
-
DEBOUNCE_FILE =
|
|
5873
|
+
INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5874
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5138
5875
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5139
5876
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5140
5877
|
}
|
|
@@ -5157,6 +5894,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5157
5894
|
args: [scope]
|
|
5158
5895
|
};
|
|
5159
5896
|
}
|
|
5897
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5898
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5899
|
+
if (!scope) return { sql: "", args: [] };
|
|
5900
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5901
|
+
return {
|
|
5902
|
+
sql: ` AND ${col} = ?`,
|
|
5903
|
+
args: [scope]
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5160
5906
|
var init_task_scope = __esm({
|
|
5161
5907
|
"src/lib/task-scope.ts"() {
|
|
5162
5908
|
"use strict";
|
|
@@ -5175,14 +5921,14 @@ var init_memory = __esm({
|
|
|
5175
5921
|
|
|
5176
5922
|
// src/lib/keychain.ts
|
|
5177
5923
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5178
|
-
import { existsSync as
|
|
5179
|
-
import
|
|
5180
|
-
import
|
|
5924
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5925
|
+
import path19 from "path";
|
|
5926
|
+
import os11 from "os";
|
|
5181
5927
|
function getKeyDir() {
|
|
5182
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5928
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
|
|
5183
5929
|
}
|
|
5184
5930
|
function getKeyPath() {
|
|
5185
|
-
return
|
|
5931
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5186
5932
|
}
|
|
5187
5933
|
async function tryKeytar() {
|
|
5188
5934
|
try {
|
|
@@ -5203,9 +5949,9 @@ async function getMasterKey() {
|
|
|
5203
5949
|
}
|
|
5204
5950
|
}
|
|
5205
5951
|
const keyPath = getKeyPath();
|
|
5206
|
-
if (!
|
|
5952
|
+
if (!existsSync15(keyPath)) {
|
|
5207
5953
|
process.stderr.write(
|
|
5208
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5954
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5209
5955
|
`
|
|
5210
5956
|
);
|
|
5211
5957
|
return null;
|
|
@@ -5235,6 +5981,7 @@ var shard_manager_exports = {};
|
|
|
5235
5981
|
__export(shard_manager_exports, {
|
|
5236
5982
|
disposeShards: () => disposeShards,
|
|
5237
5983
|
ensureShardSchema: () => ensureShardSchema,
|
|
5984
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5238
5985
|
getReadyShardClient: () => getReadyShardClient,
|
|
5239
5986
|
getShardClient: () => getShardClient,
|
|
5240
5987
|
getShardsDir: () => getShardsDir,
|
|
@@ -5243,15 +5990,18 @@ __export(shard_manager_exports, {
|
|
|
5243
5990
|
listShards: () => listShards,
|
|
5244
5991
|
shardExists: () => shardExists
|
|
5245
5992
|
});
|
|
5246
|
-
import
|
|
5247
|
-
import { existsSync as
|
|
5993
|
+
import path20 from "path";
|
|
5994
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
|
|
5248
5995
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5249
5996
|
function initShardManager(encryptionKey) {
|
|
5250
5997
|
_encryptionKey = encryptionKey;
|
|
5251
|
-
if (!
|
|
5998
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5252
5999
|
mkdirSync8(SHARDS_DIR, { recursive: true });
|
|
5253
6000
|
}
|
|
5254
6001
|
_shardingEnabled = true;
|
|
6002
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6003
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6004
|
+
_evictionTimer.unref();
|
|
5255
6005
|
}
|
|
5256
6006
|
function isShardingEnabled() {
|
|
5257
6007
|
return _shardingEnabled;
|
|
@@ -5268,21 +6018,28 @@ function getShardClient(projectName) {
|
|
|
5268
6018
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5269
6019
|
}
|
|
5270
6020
|
const cached = _shards.get(safeName);
|
|
5271
|
-
if (cached)
|
|
5272
|
-
|
|
6021
|
+
if (cached) {
|
|
6022
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6023
|
+
return cached;
|
|
6024
|
+
}
|
|
6025
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6026
|
+
evictLRU();
|
|
6027
|
+
}
|
|
6028
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5273
6029
|
const client = createClient2({
|
|
5274
6030
|
url: `file:${dbPath}`,
|
|
5275
6031
|
encryptionKey: _encryptionKey
|
|
5276
6032
|
});
|
|
5277
6033
|
_shards.set(safeName, client);
|
|
6034
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5278
6035
|
return client;
|
|
5279
6036
|
}
|
|
5280
6037
|
function shardExists(projectName) {
|
|
5281
6038
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5282
|
-
return
|
|
6039
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
5283
6040
|
}
|
|
5284
6041
|
function listShards() {
|
|
5285
|
-
if (!
|
|
6042
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5286
6043
|
return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5287
6044
|
}
|
|
5288
6045
|
async function ensureShardSchema(client) {
|
|
@@ -5334,6 +6091,8 @@ async function ensureShardSchema(client) {
|
|
|
5334
6091
|
for (const col of [
|
|
5335
6092
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5336
6093
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6094
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6095
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5337
6096
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5338
6097
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5339
6098
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5356,7 +6115,23 @@ async function ensureShardSchema(client) {
|
|
|
5356
6115
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5357
6116
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5358
6117
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5359
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6118
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6119
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6120
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6121
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6122
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6123
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6124
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6125
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6126
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6127
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6128
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6129
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6130
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6131
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6132
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6133
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6134
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5360
6135
|
]) {
|
|
5361
6136
|
try {
|
|
5362
6137
|
await client.execute(col);
|
|
@@ -5455,21 +6230,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5455
6230
|
await ensureShardSchema(client);
|
|
5456
6231
|
return client;
|
|
5457
6232
|
}
|
|
6233
|
+
function evictLRU() {
|
|
6234
|
+
let oldest = null;
|
|
6235
|
+
let oldestTime = Infinity;
|
|
6236
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6237
|
+
if (time < oldestTime) {
|
|
6238
|
+
oldestTime = time;
|
|
6239
|
+
oldest = name;
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
if (oldest) {
|
|
6243
|
+
const client = _shards.get(oldest);
|
|
6244
|
+
if (client) {
|
|
6245
|
+
client.close();
|
|
6246
|
+
}
|
|
6247
|
+
_shards.delete(oldest);
|
|
6248
|
+
_shardLastAccess.delete(oldest);
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
function evictIdleShards() {
|
|
6252
|
+
const now = Date.now();
|
|
6253
|
+
const toEvict = [];
|
|
6254
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6255
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6256
|
+
toEvict.push(name);
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
for (const name of toEvict) {
|
|
6260
|
+
const client = _shards.get(name);
|
|
6261
|
+
if (client) {
|
|
6262
|
+
client.close();
|
|
6263
|
+
}
|
|
6264
|
+
_shards.delete(name);
|
|
6265
|
+
_shardLastAccess.delete(name);
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
function getOpenShardCount() {
|
|
6269
|
+
return _shards.size;
|
|
6270
|
+
}
|
|
5458
6271
|
function disposeShards() {
|
|
6272
|
+
if (_evictionTimer) {
|
|
6273
|
+
clearInterval(_evictionTimer);
|
|
6274
|
+
_evictionTimer = null;
|
|
6275
|
+
}
|
|
5459
6276
|
for (const [, client] of _shards) {
|
|
5460
6277
|
client.close();
|
|
5461
6278
|
}
|
|
5462
6279
|
_shards.clear();
|
|
6280
|
+
_shardLastAccess.clear();
|
|
5463
6281
|
_shardingEnabled = false;
|
|
5464
6282
|
_encryptionKey = null;
|
|
5465
6283
|
}
|
|
5466
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6284
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5467
6285
|
var init_shard_manager = __esm({
|
|
5468
6286
|
"src/lib/shard-manager.ts"() {
|
|
5469
6287
|
"use strict";
|
|
5470
6288
|
init_config();
|
|
5471
|
-
SHARDS_DIR =
|
|
6289
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6290
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6291
|
+
MAX_OPEN_SHARDS = 10;
|
|
6292
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5472
6293
|
_shards = /* @__PURE__ */ new Map();
|
|
6294
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6295
|
+
_evictionTimer = null;
|
|
5473
6296
|
_encryptionKey = null;
|
|
5474
6297
|
_shardingEnabled = false;
|
|
5475
6298
|
}
|
|
@@ -6233,13 +7056,13 @@ var init_store = __esm({
|
|
|
6233
7056
|
});
|
|
6234
7057
|
|
|
6235
7058
|
// src/adapters/claude/hooks/pre-compact.ts
|
|
6236
|
-
import
|
|
7059
|
+
import crypto7 from "crypto";
|
|
6237
7060
|
|
|
6238
7061
|
// src/lib/active-agent.ts
|
|
6239
7062
|
init_config();
|
|
6240
7063
|
init_session_key();
|
|
6241
7064
|
init_employees();
|
|
6242
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
7065
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
6243
7066
|
import { execSync as execSync3 } from "child_process";
|
|
6244
7067
|
import path3 from "path";
|
|
6245
7068
|
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|
|
@@ -6406,7 +7229,7 @@ ${taskLines}`);
|
|
|
6406
7229
|
recoveryLines.push(`Files: ${lastCheckpoint.files_touched.join(", ")}`);
|
|
6407
7230
|
}
|
|
6408
7231
|
await writeMemory2({
|
|
6409
|
-
id:
|
|
7232
|
+
id: crypto7.randomUUID(),
|
|
6410
7233
|
agent_id: agent.agentId,
|
|
6411
7234
|
agent_role: agent.agentRole,
|
|
6412
7235
|
session_id: payload.session_id,
|