@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";
|
|
@@ -302,7 +342,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
|
302
342
|
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
303
343
|
}
|
|
304
344
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
305
|
-
if (!
|
|
345
|
+
if (!existsSync3(employeesPath)) return [];
|
|
306
346
|
try {
|
|
307
347
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
308
348
|
} catch {
|
|
@@ -326,7 +366,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
326
366
|
if (!emp) return false;
|
|
327
367
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
328
368
|
}
|
|
329
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
369
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
330
370
|
var init_employees = __esm({
|
|
331
371
|
"src/lib/employees.ts"() {
|
|
332
372
|
"use strict";
|
|
@@ -335,17 +375,18 @@ var init_employees = __esm({
|
|
|
335
375
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
336
376
|
COORDINATOR_ROLE = "COO";
|
|
337
377
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
378
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
338
379
|
}
|
|
339
380
|
});
|
|
340
381
|
|
|
341
382
|
// src/lib/session-registry.ts
|
|
342
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as
|
|
383
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
343
384
|
import path4 from "path";
|
|
344
385
|
import os3 from "os";
|
|
345
386
|
function registerSession(entry) {
|
|
346
387
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
347
|
-
if (!
|
|
348
|
-
|
|
388
|
+
if (!existsSync4(dir)) {
|
|
389
|
+
mkdirSync3(dir, { recursive: true });
|
|
349
390
|
}
|
|
350
391
|
const sessions = listSessions();
|
|
351
392
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -580,10 +621,10 @@ var init_runtime_table = __esm({
|
|
|
580
621
|
});
|
|
581
622
|
|
|
582
623
|
// src/lib/agent-config.ts
|
|
583
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
624
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
584
625
|
import path5 from "path";
|
|
585
626
|
function loadAgentConfig() {
|
|
586
|
-
if (!
|
|
627
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
587
628
|
try {
|
|
588
629
|
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
589
630
|
} catch {
|
|
@@ -604,6 +645,7 @@ var init_agent_config = __esm({
|
|
|
604
645
|
"use strict";
|
|
605
646
|
init_config();
|
|
606
647
|
init_runtime_table();
|
|
648
|
+
init_secure_files();
|
|
607
649
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
608
650
|
DEFAULT_MODELS = {
|
|
609
651
|
claude: "claude-opus-4",
|
|
@@ -622,16 +664,16 @@ __export(intercom_queue_exports, {
|
|
|
622
664
|
queueIntercom: () => queueIntercom,
|
|
623
665
|
readQueue: () => readQueue
|
|
624
666
|
});
|
|
625
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as
|
|
667
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
626
668
|
import path6 from "path";
|
|
627
669
|
import os4 from "os";
|
|
628
670
|
function ensureDir() {
|
|
629
671
|
const dir = path6.dirname(QUEUE_PATH);
|
|
630
|
-
if (!
|
|
672
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
631
673
|
}
|
|
632
674
|
function readQueue() {
|
|
633
675
|
try {
|
|
634
|
-
if (!
|
|
676
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
635
677
|
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
636
678
|
} catch {
|
|
637
679
|
return [];
|
|
@@ -794,13 +836,634 @@ var init_db_retry = __esm({
|
|
|
794
836
|
}
|
|
795
837
|
});
|
|
796
838
|
|
|
839
|
+
// src/lib/database-adapter.ts
|
|
840
|
+
import os5 from "os";
|
|
841
|
+
import path7 from "path";
|
|
842
|
+
import { createRequire } from "module";
|
|
843
|
+
import { pathToFileURL } from "url";
|
|
844
|
+
function quotedIdentifier(identifier) {
|
|
845
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
846
|
+
}
|
|
847
|
+
function unqualifiedTableName(name) {
|
|
848
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
849
|
+
const parts = raw.split(".");
|
|
850
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
851
|
+
}
|
|
852
|
+
function stripTrailingSemicolon(sql) {
|
|
853
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
854
|
+
}
|
|
855
|
+
function appendClause(sql, clause) {
|
|
856
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
857
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
858
|
+
if (!returningMatch) {
|
|
859
|
+
return `${trimmed}${clause}`;
|
|
860
|
+
}
|
|
861
|
+
const idx = returningMatch.index;
|
|
862
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
863
|
+
}
|
|
864
|
+
function normalizeStatement(stmt) {
|
|
865
|
+
if (typeof stmt === "string") {
|
|
866
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
867
|
+
}
|
|
868
|
+
const sql = stmt.sql;
|
|
869
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
870
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
871
|
+
}
|
|
872
|
+
return { kind: "named", sql, args: stmt.args };
|
|
873
|
+
}
|
|
874
|
+
function rewriteBooleanLiterals(sql) {
|
|
875
|
+
let out = sql;
|
|
876
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
877
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
878
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
879
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
880
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
881
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
882
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
883
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
884
|
+
}
|
|
885
|
+
return out;
|
|
886
|
+
}
|
|
887
|
+
function rewriteInsertOrIgnore(sql) {
|
|
888
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
889
|
+
return sql;
|
|
890
|
+
}
|
|
891
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
892
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
893
|
+
}
|
|
894
|
+
function rewriteInsertOrReplace(sql) {
|
|
895
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
896
|
+
if (!match) {
|
|
897
|
+
return sql;
|
|
898
|
+
}
|
|
899
|
+
const rawTable = match[1];
|
|
900
|
+
const rawColumns = match[2];
|
|
901
|
+
const remainder = match[3];
|
|
902
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
903
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
904
|
+
if (!conflictKeys?.length) {
|
|
905
|
+
return sql;
|
|
906
|
+
}
|
|
907
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
908
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
909
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
910
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
911
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
912
|
+
}
|
|
913
|
+
function rewriteSql(sql) {
|
|
914
|
+
let out = sql;
|
|
915
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
916
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
917
|
+
out = rewriteBooleanLiterals(out);
|
|
918
|
+
out = rewriteInsertOrReplace(out);
|
|
919
|
+
out = rewriteInsertOrIgnore(out);
|
|
920
|
+
return stripTrailingSemicolon(out);
|
|
921
|
+
}
|
|
922
|
+
function toBoolean(value) {
|
|
923
|
+
if (value === null || value === void 0) return value;
|
|
924
|
+
if (typeof value === "boolean") return value;
|
|
925
|
+
if (typeof value === "number") return value !== 0;
|
|
926
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
927
|
+
if (typeof value === "string") {
|
|
928
|
+
const normalized = value.trim().toLowerCase();
|
|
929
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
930
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
931
|
+
}
|
|
932
|
+
return Boolean(value);
|
|
933
|
+
}
|
|
934
|
+
function countQuestionMarks(sql, end) {
|
|
935
|
+
let count = 0;
|
|
936
|
+
let inSingle = false;
|
|
937
|
+
let inDouble = false;
|
|
938
|
+
let inLineComment = false;
|
|
939
|
+
let inBlockComment = false;
|
|
940
|
+
for (let i = 0; i < end; i++) {
|
|
941
|
+
const ch = sql[i];
|
|
942
|
+
const next = sql[i + 1];
|
|
943
|
+
if (inLineComment) {
|
|
944
|
+
if (ch === "\n") inLineComment = false;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (inBlockComment) {
|
|
948
|
+
if (ch === "*" && next === "/") {
|
|
949
|
+
inBlockComment = false;
|
|
950
|
+
i += 1;
|
|
951
|
+
}
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
955
|
+
inLineComment = true;
|
|
956
|
+
i += 1;
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
960
|
+
inBlockComment = true;
|
|
961
|
+
i += 1;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
965
|
+
inSingle = !inSingle;
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
969
|
+
inDouble = !inDouble;
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
973
|
+
count += 1;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return count;
|
|
977
|
+
}
|
|
978
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
979
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
980
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
981
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
982
|
+
for (const match of sql.matchAll(pattern)) {
|
|
983
|
+
const matchText = match[0];
|
|
984
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
985
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return indexes;
|
|
989
|
+
}
|
|
990
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
991
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
992
|
+
if (!match) return;
|
|
993
|
+
const rawTable = match[1];
|
|
994
|
+
const rawColumns = match[2];
|
|
995
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
996
|
+
if (!boolColumns?.size) return;
|
|
997
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
998
|
+
for (const [index, column] of columns.entries()) {
|
|
999
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1000
|
+
args[index] = toBoolean(args[index]);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1005
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1006
|
+
if (!match) return;
|
|
1007
|
+
const rawTable = match[1];
|
|
1008
|
+
const setClause = match[2];
|
|
1009
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1010
|
+
if (!boolColumns?.size) return;
|
|
1011
|
+
const assignments = setClause.split(",");
|
|
1012
|
+
let placeholderIndex = 0;
|
|
1013
|
+
for (const assignment of assignments) {
|
|
1014
|
+
if (!assignment.includes("?")) continue;
|
|
1015
|
+
placeholderIndex += 1;
|
|
1016
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1017
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1018
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function coerceBooleanArgs(sql, args) {
|
|
1023
|
+
const nextArgs = [...args];
|
|
1024
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1025
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1026
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1027
|
+
for (const index of placeholderIndexes) {
|
|
1028
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1029
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return nextArgs;
|
|
1033
|
+
}
|
|
1034
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1035
|
+
let out = "";
|
|
1036
|
+
let placeholder = 0;
|
|
1037
|
+
let inSingle = false;
|
|
1038
|
+
let inDouble = false;
|
|
1039
|
+
let inLineComment = false;
|
|
1040
|
+
let inBlockComment = false;
|
|
1041
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1042
|
+
const ch = sql[i];
|
|
1043
|
+
const next = sql[i + 1];
|
|
1044
|
+
if (inLineComment) {
|
|
1045
|
+
out += ch;
|
|
1046
|
+
if (ch === "\n") inLineComment = false;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (inBlockComment) {
|
|
1050
|
+
out += ch;
|
|
1051
|
+
if (ch === "*" && next === "/") {
|
|
1052
|
+
out += next;
|
|
1053
|
+
inBlockComment = false;
|
|
1054
|
+
i += 1;
|
|
1055
|
+
}
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1059
|
+
out += ch + next;
|
|
1060
|
+
inLineComment = true;
|
|
1061
|
+
i += 1;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1065
|
+
out += ch + next;
|
|
1066
|
+
inBlockComment = true;
|
|
1067
|
+
i += 1;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1071
|
+
inSingle = !inSingle;
|
|
1072
|
+
out += ch;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1076
|
+
inDouble = !inDouble;
|
|
1077
|
+
out += ch;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1081
|
+
placeholder += 1;
|
|
1082
|
+
out += `$${placeholder}`;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
out += ch;
|
|
1086
|
+
}
|
|
1087
|
+
return out;
|
|
1088
|
+
}
|
|
1089
|
+
function translateStatementForPostgres(stmt) {
|
|
1090
|
+
const normalized = normalizeStatement(stmt);
|
|
1091
|
+
if (normalized.kind === "named") {
|
|
1092
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1093
|
+
}
|
|
1094
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1095
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1096
|
+
return {
|
|
1097
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1098
|
+
args: coercedArgs
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function shouldBypassPostgres(stmt) {
|
|
1102
|
+
const normalized = normalizeStatement(stmt);
|
|
1103
|
+
if (normalized.kind === "named") {
|
|
1104
|
+
return true;
|
|
1105
|
+
}
|
|
1106
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1107
|
+
}
|
|
1108
|
+
function shouldFallbackOnError(error) {
|
|
1109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1110
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1111
|
+
}
|
|
1112
|
+
function isReadQuery(sql) {
|
|
1113
|
+
const trimmed = sql.trimStart();
|
|
1114
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1115
|
+
}
|
|
1116
|
+
function buildRow(row, columns) {
|
|
1117
|
+
const values = columns.map((column) => row[column]);
|
|
1118
|
+
return Object.assign(values, row);
|
|
1119
|
+
}
|
|
1120
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1121
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1122
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1123
|
+
return {
|
|
1124
|
+
columns,
|
|
1125
|
+
columnTypes: columns.map(() => ""),
|
|
1126
|
+
rows: resultRows,
|
|
1127
|
+
rowsAffected,
|
|
1128
|
+
lastInsertRowid: void 0,
|
|
1129
|
+
toJSON() {
|
|
1130
|
+
return {
|
|
1131
|
+
columns,
|
|
1132
|
+
columnTypes: columns.map(() => ""),
|
|
1133
|
+
rows,
|
|
1134
|
+
rowsAffected,
|
|
1135
|
+
lastInsertRowid: void 0
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
async function loadPrismaClient() {
|
|
1141
|
+
if (!prismaClientPromise) {
|
|
1142
|
+
prismaClientPromise = (async () => {
|
|
1143
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1144
|
+
if (explicitPath) {
|
|
1145
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1146
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1147
|
+
if (!PrismaClient2) {
|
|
1148
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1149
|
+
}
|
|
1150
|
+
return new PrismaClient2();
|
|
1151
|
+
}
|
|
1152
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
|
|
1153
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1154
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1155
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1156
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1157
|
+
if (!PrismaClient) {
|
|
1158
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1159
|
+
}
|
|
1160
|
+
return new PrismaClient();
|
|
1161
|
+
})();
|
|
1162
|
+
}
|
|
1163
|
+
return prismaClientPromise;
|
|
1164
|
+
}
|
|
1165
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1166
|
+
if (!compatibilityBootstrapPromise) {
|
|
1167
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1168
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1169
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1170
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1171
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1172
|
+
relation
|
|
1173
|
+
);
|
|
1174
|
+
if (!rows[0]?.regclass) {
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
await prisma.$executeRawUnsafe(
|
|
1178
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
})();
|
|
1182
|
+
}
|
|
1183
|
+
return compatibilityBootstrapPromise;
|
|
1184
|
+
}
|
|
1185
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1186
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1187
|
+
if (isReadQuery(translated.sql)) {
|
|
1188
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1189
|
+
translated.sql,
|
|
1190
|
+
...translated.args
|
|
1191
|
+
);
|
|
1192
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1193
|
+
}
|
|
1194
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1195
|
+
return buildResultSet([], rowsAffected);
|
|
1196
|
+
}
|
|
1197
|
+
function splitSqlStatements(sql) {
|
|
1198
|
+
const parts = [];
|
|
1199
|
+
let current = "";
|
|
1200
|
+
let inSingle = false;
|
|
1201
|
+
let inDouble = false;
|
|
1202
|
+
let inLineComment = false;
|
|
1203
|
+
let inBlockComment = false;
|
|
1204
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1205
|
+
const ch = sql[i];
|
|
1206
|
+
const next = sql[i + 1];
|
|
1207
|
+
if (inLineComment) {
|
|
1208
|
+
current += ch;
|
|
1209
|
+
if (ch === "\n") inLineComment = false;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (inBlockComment) {
|
|
1213
|
+
current += ch;
|
|
1214
|
+
if (ch === "*" && next === "/") {
|
|
1215
|
+
current += next;
|
|
1216
|
+
inBlockComment = false;
|
|
1217
|
+
i += 1;
|
|
1218
|
+
}
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1222
|
+
current += ch + next;
|
|
1223
|
+
inLineComment = true;
|
|
1224
|
+
i += 1;
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1228
|
+
current += ch + next;
|
|
1229
|
+
inBlockComment = true;
|
|
1230
|
+
i += 1;
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1234
|
+
inSingle = !inSingle;
|
|
1235
|
+
current += ch;
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1239
|
+
inDouble = !inDouble;
|
|
1240
|
+
current += ch;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1244
|
+
if (current.trim()) {
|
|
1245
|
+
parts.push(current.trim());
|
|
1246
|
+
}
|
|
1247
|
+
current = "";
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
current += ch;
|
|
1251
|
+
}
|
|
1252
|
+
if (current.trim()) {
|
|
1253
|
+
parts.push(current.trim());
|
|
1254
|
+
}
|
|
1255
|
+
return parts;
|
|
1256
|
+
}
|
|
1257
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1258
|
+
const prisma = await loadPrismaClient();
|
|
1259
|
+
await ensureCompatibilityViews(prisma);
|
|
1260
|
+
let closed = false;
|
|
1261
|
+
let adapter;
|
|
1262
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1263
|
+
if (!fallbackClient) {
|
|
1264
|
+
if (error) throw error;
|
|
1265
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1266
|
+
}
|
|
1267
|
+
if (error) {
|
|
1268
|
+
process.stderr.write(
|
|
1269
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1270
|
+
`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
return fallbackClient.execute(stmt);
|
|
1274
|
+
};
|
|
1275
|
+
adapter = {
|
|
1276
|
+
async execute(stmt) {
|
|
1277
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1278
|
+
return fallbackExecute(stmt);
|
|
1279
|
+
}
|
|
1280
|
+
try {
|
|
1281
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
if (shouldFallbackOnError(error)) {
|
|
1284
|
+
return fallbackExecute(stmt, error);
|
|
1285
|
+
}
|
|
1286
|
+
throw error;
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
async batch(stmts, mode) {
|
|
1290
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1291
|
+
if (!fallbackClient) {
|
|
1292
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1293
|
+
}
|
|
1294
|
+
return fallbackClient.batch(stmts, mode);
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
if (prisma.$transaction) {
|
|
1298
|
+
return await prisma.$transaction(async (tx) => {
|
|
1299
|
+
const results2 = [];
|
|
1300
|
+
for (const stmt of stmts) {
|
|
1301
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1302
|
+
}
|
|
1303
|
+
return results2;
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
const results = [];
|
|
1307
|
+
for (const stmt of stmts) {
|
|
1308
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1309
|
+
}
|
|
1310
|
+
return results;
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1313
|
+
process.stderr.write(
|
|
1314
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1315
|
+
`
|
|
1316
|
+
);
|
|
1317
|
+
return fallbackClient.batch(stmts, mode);
|
|
1318
|
+
}
|
|
1319
|
+
throw error;
|
|
1320
|
+
}
|
|
1321
|
+
},
|
|
1322
|
+
async migrate(stmts) {
|
|
1323
|
+
if (fallbackClient) {
|
|
1324
|
+
return fallbackClient.migrate(stmts);
|
|
1325
|
+
}
|
|
1326
|
+
return adapter.batch(stmts, "deferred");
|
|
1327
|
+
},
|
|
1328
|
+
async transaction(mode) {
|
|
1329
|
+
if (!fallbackClient) {
|
|
1330
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1331
|
+
}
|
|
1332
|
+
return fallbackClient.transaction(mode);
|
|
1333
|
+
},
|
|
1334
|
+
async executeMultiple(sql) {
|
|
1335
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1336
|
+
return fallbackClient.executeMultiple(sql);
|
|
1337
|
+
}
|
|
1338
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1339
|
+
await adapter.execute(statement);
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
async sync() {
|
|
1343
|
+
if (fallbackClient) {
|
|
1344
|
+
return fallbackClient.sync();
|
|
1345
|
+
}
|
|
1346
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1347
|
+
},
|
|
1348
|
+
close() {
|
|
1349
|
+
closed = true;
|
|
1350
|
+
prismaClientPromise = null;
|
|
1351
|
+
compatibilityBootstrapPromise = null;
|
|
1352
|
+
void prisma.$disconnect?.();
|
|
1353
|
+
},
|
|
1354
|
+
get closed() {
|
|
1355
|
+
return closed;
|
|
1356
|
+
},
|
|
1357
|
+
get protocol() {
|
|
1358
|
+
return "prisma-postgres";
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
return adapter;
|
|
1362
|
+
}
|
|
1363
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1364
|
+
var init_database_adapter = __esm({
|
|
1365
|
+
"src/lib/database-adapter.ts"() {
|
|
1366
|
+
"use strict";
|
|
1367
|
+
VIEW_MAPPINGS = [
|
|
1368
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1369
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1370
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1371
|
+
{ view: "entities", source: "memory.entities" },
|
|
1372
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1373
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1374
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1375
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1376
|
+
{ view: "messages", source: "memory.messages" },
|
|
1377
|
+
{ view: "users", source: "wiki.users" },
|
|
1378
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1379
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1380
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1381
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1382
|
+
];
|
|
1383
|
+
UPSERT_KEYS = {
|
|
1384
|
+
memories: ["id"],
|
|
1385
|
+
tasks: ["id"],
|
|
1386
|
+
behaviors: ["id"],
|
|
1387
|
+
entities: ["id"],
|
|
1388
|
+
relationships: ["id"],
|
|
1389
|
+
entity_aliases: ["alias"],
|
|
1390
|
+
notifications: ["id"],
|
|
1391
|
+
messages: ["id"],
|
|
1392
|
+
users: ["id"],
|
|
1393
|
+
workspaces: ["id"],
|
|
1394
|
+
workspace_users: ["id"],
|
|
1395
|
+
documents: ["id"],
|
|
1396
|
+
chats: ["id"]
|
|
1397
|
+
};
|
|
1398
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1399
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1400
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1401
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1402
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1403
|
+
};
|
|
1404
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1405
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1406
|
+
);
|
|
1407
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1408
|
+
/\bPRAGMA\b/i,
|
|
1409
|
+
/\bsqlite_master\b/i,
|
|
1410
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1411
|
+
/\bMATCH\b/i,
|
|
1412
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1413
|
+
/\bjson_extract\s*\(/i,
|
|
1414
|
+
/\bjulianday\s*\(/i,
|
|
1415
|
+
/\bstrftime\s*\(/i,
|
|
1416
|
+
/\blast_insert_rowid\s*\(/i
|
|
1417
|
+
];
|
|
1418
|
+
prismaClientPromise = null;
|
|
1419
|
+
compatibilityBootstrapPromise = null;
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
// src/lib/daemon-auth.ts
|
|
1424
|
+
import crypto from "crypto";
|
|
1425
|
+
import path8 from "path";
|
|
1426
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1427
|
+
function normalizeToken(token) {
|
|
1428
|
+
if (!token) return null;
|
|
1429
|
+
const trimmed = token.trim();
|
|
1430
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1431
|
+
}
|
|
1432
|
+
function readDaemonToken() {
|
|
1433
|
+
try {
|
|
1434
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1435
|
+
return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
|
|
1436
|
+
} catch {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function ensureDaemonToken(seed) {
|
|
1441
|
+
const existing = readDaemonToken();
|
|
1442
|
+
if (existing) return existing;
|
|
1443
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1444
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1445
|
+
writeFileSync6(DAEMON_TOKEN_PATH, `${token}
|
|
1446
|
+
`, "utf8");
|
|
1447
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1448
|
+
return token;
|
|
1449
|
+
}
|
|
1450
|
+
var DAEMON_TOKEN_PATH;
|
|
1451
|
+
var init_daemon_auth = __esm({
|
|
1452
|
+
"src/lib/daemon-auth.ts"() {
|
|
1453
|
+
"use strict";
|
|
1454
|
+
init_config();
|
|
1455
|
+
init_secure_files();
|
|
1456
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
|
|
797
1460
|
// src/lib/exe-daemon-client.ts
|
|
798
1461
|
import net from "net";
|
|
799
|
-
import
|
|
1462
|
+
import os6 from "os";
|
|
800
1463
|
import { spawn } from "child_process";
|
|
801
1464
|
import { randomUUID } from "crypto";
|
|
802
|
-
import { existsSync as
|
|
803
|
-
import
|
|
1465
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
|
|
1466
|
+
import path9 from "path";
|
|
804
1467
|
import { fileURLToPath } from "url";
|
|
805
1468
|
function handleData(chunk) {
|
|
806
1469
|
_buffer += chunk.toString();
|
|
@@ -828,9 +1491,9 @@ function handleData(chunk) {
|
|
|
828
1491
|
}
|
|
829
1492
|
}
|
|
830
1493
|
function cleanupStaleFiles() {
|
|
831
|
-
if (
|
|
1494
|
+
if (existsSync8(PID_PATH)) {
|
|
832
1495
|
try {
|
|
833
|
-
const pid = parseInt(
|
|
1496
|
+
const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
|
|
834
1497
|
if (pid > 0) {
|
|
835
1498
|
try {
|
|
836
1499
|
process.kill(pid, 0);
|
|
@@ -851,17 +1514,17 @@ function cleanupStaleFiles() {
|
|
|
851
1514
|
}
|
|
852
1515
|
}
|
|
853
1516
|
function findPackageRoot() {
|
|
854
|
-
let dir =
|
|
855
|
-
const { root } =
|
|
1517
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1518
|
+
const { root } = path9.parse(dir);
|
|
856
1519
|
while (dir !== root) {
|
|
857
|
-
if (
|
|
858
|
-
dir =
|
|
1520
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1521
|
+
dir = path9.dirname(dir);
|
|
859
1522
|
}
|
|
860
1523
|
return null;
|
|
861
1524
|
}
|
|
862
1525
|
function spawnDaemon() {
|
|
863
|
-
const freeGB =
|
|
864
|
-
const totalGB =
|
|
1526
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1527
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
865
1528
|
if (totalGB <= 8) {
|
|
866
1529
|
process.stderr.write(
|
|
867
1530
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -881,16 +1544,17 @@ function spawnDaemon() {
|
|
|
881
1544
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
882
1545
|
return;
|
|
883
1546
|
}
|
|
884
|
-
const daemonPath =
|
|
885
|
-
if (!
|
|
1547
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1548
|
+
if (!existsSync8(daemonPath)) {
|
|
886
1549
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
887
1550
|
`);
|
|
888
1551
|
return;
|
|
889
1552
|
}
|
|
890
1553
|
const resolvedPath = daemonPath;
|
|
1554
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
891
1555
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
892
1556
|
`);
|
|
893
|
-
const logPath =
|
|
1557
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
894
1558
|
let stderrFd = "ignore";
|
|
895
1559
|
try {
|
|
896
1560
|
stderrFd = openSync(logPath, "a");
|
|
@@ -908,7 +1572,8 @@ function spawnDaemon() {
|
|
|
908
1572
|
TMUX_PANE: void 0,
|
|
909
1573
|
// Prevents resolveExeSession() from scoping to one session
|
|
910
1574
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
911
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1575
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1576
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
912
1577
|
}
|
|
913
1578
|
});
|
|
914
1579
|
child.unref();
|
|
@@ -1015,13 +1680,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1015
1680
|
return;
|
|
1016
1681
|
}
|
|
1017
1682
|
const id = randomUUID();
|
|
1683
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1018
1684
|
const timer = setTimeout(() => {
|
|
1019
1685
|
_pending.delete(id);
|
|
1020
1686
|
resolve({ error: "Request timeout" });
|
|
1021
1687
|
}, timeoutMs);
|
|
1022
1688
|
_pending.set(id, { resolve, timer });
|
|
1023
1689
|
try {
|
|
1024
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1690
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1025
1691
|
} catch {
|
|
1026
1692
|
clearTimeout(timer);
|
|
1027
1693
|
_pending.delete(id);
|
|
@@ -1032,17 +1698,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1032
1698
|
function isClientConnected() {
|
|
1033
1699
|
return _connected;
|
|
1034
1700
|
}
|
|
1035
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1701
|
+
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;
|
|
1036
1702
|
var init_exe_daemon_client = __esm({
|
|
1037
1703
|
"src/lib/exe-daemon-client.ts"() {
|
|
1038
1704
|
"use strict";
|
|
1039
1705
|
init_config();
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1706
|
+
init_daemon_auth();
|
|
1707
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1708
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1709
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1043
1710
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1044
1711
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1045
1712
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1713
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1046
1714
|
_socket = null;
|
|
1047
1715
|
_connected = false;
|
|
1048
1716
|
_buffer = "";
|
|
@@ -1121,7 +1789,7 @@ __export(db_daemon_client_exports, {
|
|
|
1121
1789
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1122
1790
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1123
1791
|
});
|
|
1124
|
-
function
|
|
1792
|
+
function normalizeStatement2(stmt) {
|
|
1125
1793
|
if (typeof stmt === "string") {
|
|
1126
1794
|
return { sql: stmt, args: [] };
|
|
1127
1795
|
}
|
|
@@ -1145,7 +1813,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1145
1813
|
if (!_useDaemon || !isClientConnected()) {
|
|
1146
1814
|
return fallbackClient.execute(stmt);
|
|
1147
1815
|
}
|
|
1148
|
-
const { sql, args } =
|
|
1816
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1149
1817
|
const response = await sendDaemonRequest({
|
|
1150
1818
|
type: "db-execute",
|
|
1151
1819
|
sql,
|
|
@@ -1170,7 +1838,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1170
1838
|
if (!_useDaemon || !isClientConnected()) {
|
|
1171
1839
|
return fallbackClient.batch(stmts, mode);
|
|
1172
1840
|
}
|
|
1173
|
-
const statements = stmts.map(
|
|
1841
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1174
1842
|
const response = await sendDaemonRequest({
|
|
1175
1843
|
type: "db-batch",
|
|
1176
1844
|
statements,
|
|
@@ -1265,6 +1933,18 @@ __export(database_exports, {
|
|
|
1265
1933
|
});
|
|
1266
1934
|
import { createClient } from "@libsql/client";
|
|
1267
1935
|
async function initDatabase(config) {
|
|
1936
|
+
if (_walCheckpointTimer) {
|
|
1937
|
+
clearInterval(_walCheckpointTimer);
|
|
1938
|
+
_walCheckpointTimer = null;
|
|
1939
|
+
}
|
|
1940
|
+
if (_daemonClient) {
|
|
1941
|
+
_daemonClient.close();
|
|
1942
|
+
_daemonClient = null;
|
|
1943
|
+
}
|
|
1944
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1945
|
+
_adapterClient.close();
|
|
1946
|
+
}
|
|
1947
|
+
_adapterClient = null;
|
|
1268
1948
|
if (_client) {
|
|
1269
1949
|
_client.close();
|
|
1270
1950
|
_client = null;
|
|
@@ -1278,6 +1958,7 @@ async function initDatabase(config) {
|
|
|
1278
1958
|
}
|
|
1279
1959
|
_client = createClient(opts);
|
|
1280
1960
|
_resilientClient = wrapWithRetry(_client);
|
|
1961
|
+
_adapterClient = _resilientClient;
|
|
1281
1962
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1282
1963
|
});
|
|
1283
1964
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1288,14 +1969,20 @@ async function initDatabase(config) {
|
|
|
1288
1969
|
});
|
|
1289
1970
|
}, 3e4);
|
|
1290
1971
|
_walCheckpointTimer.unref();
|
|
1972
|
+
if (process.env.DATABASE_URL) {
|
|
1973
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1974
|
+
}
|
|
1291
1975
|
}
|
|
1292
1976
|
function isInitialized() {
|
|
1293
|
-
return _client !== null;
|
|
1977
|
+
return _adapterClient !== null || _client !== null;
|
|
1294
1978
|
}
|
|
1295
1979
|
function getClient() {
|
|
1296
|
-
if (!
|
|
1980
|
+
if (!_adapterClient) {
|
|
1297
1981
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1298
1982
|
}
|
|
1983
|
+
if (process.env.DATABASE_URL) {
|
|
1984
|
+
return _adapterClient;
|
|
1985
|
+
}
|
|
1299
1986
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1300
1987
|
return _resilientClient;
|
|
1301
1988
|
}
|
|
@@ -1305,6 +1992,7 @@ function getClient() {
|
|
|
1305
1992
|
return _resilientClient;
|
|
1306
1993
|
}
|
|
1307
1994
|
async function initDaemonClient() {
|
|
1995
|
+
if (process.env.DATABASE_URL) return;
|
|
1308
1996
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1309
1997
|
if (!_resilientClient) return;
|
|
1310
1998
|
try {
|
|
@@ -1601,6 +2289,7 @@ async function ensureSchema() {
|
|
|
1601
2289
|
project TEXT NOT NULL,
|
|
1602
2290
|
summary TEXT NOT NULL,
|
|
1603
2291
|
task_file TEXT,
|
|
2292
|
+
session_scope TEXT,
|
|
1604
2293
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1605
2294
|
created_at TEXT NOT NULL
|
|
1606
2295
|
);
|
|
@@ -1609,7 +2298,7 @@ async function ensureSchema() {
|
|
|
1609
2298
|
ON notifications(read);
|
|
1610
2299
|
|
|
1611
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1612
|
-
ON notifications(agent_id);
|
|
2301
|
+
ON notifications(agent_id, session_scope);
|
|
1613
2302
|
|
|
1614
2303
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1615
2304
|
ON notifications(task_file);
|
|
@@ -1647,6 +2336,7 @@ async function ensureSchema() {
|
|
|
1647
2336
|
target_agent TEXT NOT NULL,
|
|
1648
2337
|
target_project TEXT,
|
|
1649
2338
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2339
|
+
session_scope TEXT,
|
|
1650
2340
|
content TEXT NOT NULL,
|
|
1651
2341
|
priority TEXT DEFAULT 'normal',
|
|
1652
2342
|
status TEXT DEFAULT 'pending',
|
|
@@ -1660,10 +2350,31 @@ async function ensureSchema() {
|
|
|
1660
2350
|
);
|
|
1661
2351
|
|
|
1662
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1663
|
-
ON messages(target_agent, status);
|
|
2353
|
+
ON messages(target_agent, session_scope, status);
|
|
1664
2354
|
|
|
1665
2355
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1666
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2356
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2357
|
+
`);
|
|
2358
|
+
try {
|
|
2359
|
+
await client.execute({
|
|
2360
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2361
|
+
args: []
|
|
2362
|
+
});
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
try {
|
|
2366
|
+
await client.execute({
|
|
2367
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2368
|
+
args: []
|
|
2369
|
+
});
|
|
2370
|
+
} catch {
|
|
2371
|
+
}
|
|
2372
|
+
await client.executeMultiple(`
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2374
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2375
|
+
|
|
2376
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2377
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1667
2378
|
`);
|
|
1668
2379
|
try {
|
|
1669
2380
|
await client.execute({
|
|
@@ -2247,46 +2958,66 @@ async function ensureSchema() {
|
|
|
2247
2958
|
} catch {
|
|
2248
2959
|
}
|
|
2249
2960
|
}
|
|
2961
|
+
try {
|
|
2962
|
+
await client.execute({
|
|
2963
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2964
|
+
args: []
|
|
2965
|
+
});
|
|
2966
|
+
} catch {
|
|
2967
|
+
}
|
|
2250
2968
|
}
|
|
2251
2969
|
async function disposeDatabase() {
|
|
2970
|
+
if (_walCheckpointTimer) {
|
|
2971
|
+
clearInterval(_walCheckpointTimer);
|
|
2972
|
+
_walCheckpointTimer = null;
|
|
2973
|
+
}
|
|
2252
2974
|
if (_daemonClient) {
|
|
2253
2975
|
_daemonClient.close();
|
|
2254
2976
|
_daemonClient = null;
|
|
2255
2977
|
}
|
|
2978
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2979
|
+
_adapterClient.close();
|
|
2980
|
+
}
|
|
2981
|
+
_adapterClient = null;
|
|
2256
2982
|
if (_client) {
|
|
2257
2983
|
_client.close();
|
|
2258
2984
|
_client = null;
|
|
2259
2985
|
_resilientClient = null;
|
|
2260
2986
|
}
|
|
2261
2987
|
}
|
|
2262
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2988
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2263
2989
|
var init_database = __esm({
|
|
2264
2990
|
"src/lib/database.ts"() {
|
|
2265
2991
|
"use strict";
|
|
2266
2992
|
init_db_retry();
|
|
2267
2993
|
init_employees();
|
|
2994
|
+
init_database_adapter();
|
|
2268
2995
|
_client = null;
|
|
2269
2996
|
_resilientClient = null;
|
|
2270
2997
|
_walCheckpointTimer = null;
|
|
2271
2998
|
_daemonClient = null;
|
|
2999
|
+
_adapterClient = null;
|
|
2272
3000
|
initTurso = initDatabase;
|
|
2273
3001
|
disposeTurso = disposeDatabase;
|
|
2274
3002
|
}
|
|
2275
3003
|
});
|
|
2276
3004
|
|
|
2277
3005
|
// src/lib/license.ts
|
|
2278
|
-
import { readFileSync as
|
|
3006
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
2279
3007
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2280
|
-
import
|
|
3008
|
+
import { createRequire as createRequire2 } from "module";
|
|
3009
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3010
|
+
import os7 from "os";
|
|
3011
|
+
import path10 from "path";
|
|
2281
3012
|
import { jwtVerify, importSPKI } from "jose";
|
|
2282
3013
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2283
3014
|
var init_license = __esm({
|
|
2284
3015
|
"src/lib/license.ts"() {
|
|
2285
3016
|
"use strict";
|
|
2286
3017
|
init_config();
|
|
2287
|
-
LICENSE_PATH =
|
|
2288
|
-
CACHE_PATH =
|
|
2289
|
-
DEVICE_ID_PATH =
|
|
3018
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3019
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3020
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2290
3021
|
PLAN_LIMITS = {
|
|
2291
3022
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2292
3023
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2298,12 +3029,12 @@ var init_license = __esm({
|
|
|
2298
3029
|
});
|
|
2299
3030
|
|
|
2300
3031
|
// src/lib/plan-limits.ts
|
|
2301
|
-
import { readFileSync as
|
|
2302
|
-
import
|
|
3032
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3033
|
+
import path11 from "path";
|
|
2303
3034
|
function getLicenseSync() {
|
|
2304
3035
|
try {
|
|
2305
|
-
if (!
|
|
2306
|
-
const raw = JSON.parse(
|
|
3036
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3037
|
+
const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
|
|
2307
3038
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2308
3039
|
const parts = raw.token.split(".");
|
|
2309
3040
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2341,8 +3072,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2341
3072
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2342
3073
|
let count = 0;
|
|
2343
3074
|
try {
|
|
2344
|
-
if (
|
|
2345
|
-
const raw =
|
|
3075
|
+
if (existsSync10(filePath)) {
|
|
3076
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
2346
3077
|
const employees = JSON.parse(raw);
|
|
2347
3078
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2348
3079
|
}
|
|
@@ -2371,7 +3102,7 @@ var init_plan_limits = __esm({
|
|
|
2371
3102
|
this.name = "PlanLimitError";
|
|
2372
3103
|
}
|
|
2373
3104
|
};
|
|
2374
|
-
CACHE_PATH2 =
|
|
3105
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
2375
3106
|
}
|
|
2376
3107
|
});
|
|
2377
3108
|
|
|
@@ -2387,24 +3118,25 @@ __export(notifications_exports, {
|
|
|
2387
3118
|
readUnreadNotifications: () => readUnreadNotifications,
|
|
2388
3119
|
writeNotification: () => writeNotification
|
|
2389
3120
|
});
|
|
2390
|
-
import
|
|
2391
|
-
import
|
|
2392
|
-
import
|
|
3121
|
+
import crypto2 from "crypto";
|
|
3122
|
+
import path12 from "path";
|
|
3123
|
+
import os8 from "os";
|
|
2393
3124
|
import {
|
|
2394
|
-
readFileSync as
|
|
3125
|
+
readFileSync as readFileSync11,
|
|
2395
3126
|
readdirSync as readdirSync2,
|
|
2396
3127
|
unlinkSync as unlinkSync4,
|
|
2397
|
-
existsSync as
|
|
3128
|
+
existsSync as existsSync11,
|
|
2398
3129
|
rmdirSync
|
|
2399
3130
|
} from "fs";
|
|
2400
3131
|
async function writeNotification(notification) {
|
|
2401
3132
|
try {
|
|
2402
3133
|
const client = getClient();
|
|
2403
|
-
const id =
|
|
3134
|
+
const id = crypto2.randomUUID();
|
|
2404
3135
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3136
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2405
3137
|
await client.execute({
|
|
2406
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2407
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3138
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2408
3140
|
args: [
|
|
2409
3141
|
id,
|
|
2410
3142
|
notification.agentId,
|
|
@@ -2413,6 +3145,7 @@ async function writeNotification(notification) {
|
|
|
2413
3145
|
notification.project,
|
|
2414
3146
|
notification.summary,
|
|
2415
3147
|
notification.taskFile ?? null,
|
|
3148
|
+
sessionScope,
|
|
2416
3149
|
now
|
|
2417
3150
|
]
|
|
2418
3151
|
});
|
|
@@ -2421,21 +3154,22 @@ async function writeNotification(notification) {
|
|
|
2421
3154
|
`);
|
|
2422
3155
|
}
|
|
2423
3156
|
}
|
|
2424
|
-
async function readUnreadNotifications(agentFilter) {
|
|
3157
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
2425
3158
|
try {
|
|
2426
3159
|
const client = getClient();
|
|
2427
3160
|
const conditions = ["read = 0"];
|
|
2428
3161
|
const args = [];
|
|
3162
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2429
3163
|
if (agentFilter) {
|
|
2430
3164
|
conditions.push("agent_id = ?");
|
|
2431
3165
|
args.push(agentFilter);
|
|
2432
3166
|
}
|
|
2433
3167
|
const result = await client.execute({
|
|
2434
|
-
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
|
|
3168
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
2435
3169
|
FROM notifications
|
|
2436
|
-
WHERE ${conditions.join(" AND ")}
|
|
3170
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
2437
3171
|
ORDER BY created_at ASC`,
|
|
2438
|
-
args
|
|
3172
|
+
args: [...args, ...scope.args]
|
|
2439
3173
|
});
|
|
2440
3174
|
return result.rows.map((r) => ({
|
|
2441
3175
|
id: String(r.id),
|
|
@@ -2445,6 +3179,7 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
2445
3179
|
project: String(r.project),
|
|
2446
3180
|
summary: String(r.summary),
|
|
2447
3181
|
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
3182
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
2448
3183
|
timestamp: String(r.created_at),
|
|
2449
3184
|
read: false
|
|
2450
3185
|
}));
|
|
@@ -2452,54 +3187,60 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
2452
3187
|
return [];
|
|
2453
3188
|
}
|
|
2454
3189
|
}
|
|
2455
|
-
async function markAsRead(ids) {
|
|
3190
|
+
async function markAsRead(ids, sessionScope) {
|
|
2456
3191
|
if (ids.length === 0) return;
|
|
2457
3192
|
try {
|
|
2458
3193
|
const client = getClient();
|
|
2459
3194
|
const placeholders = ids.map(() => "?").join(", ");
|
|
3195
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2460
3196
|
await client.execute({
|
|
2461
|
-
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
|
|
2462
|
-
args: ids
|
|
3197
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
3198
|
+
args: [...ids, ...scope.args]
|
|
2463
3199
|
});
|
|
2464
3200
|
} catch {
|
|
2465
3201
|
}
|
|
2466
3202
|
}
|
|
2467
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3203
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2468
3204
|
try {
|
|
2469
3205
|
const client = getClient();
|
|
3206
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2470
3207
|
await client.execute({
|
|
2471
|
-
sql:
|
|
2472
|
-
|
|
3208
|
+
sql: `UPDATE notifications SET read = 1
|
|
3209
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3210
|
+
args: [taskFile, ...scope.args]
|
|
2473
3211
|
});
|
|
2474
3212
|
} catch {
|
|
2475
3213
|
}
|
|
2476
3214
|
}
|
|
2477
|
-
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
|
|
3215
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
2478
3216
|
try {
|
|
2479
3217
|
const client = getClient();
|
|
2480
3218
|
const cutoff = new Date(
|
|
2481
3219
|
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
2482
3220
|
).toISOString();
|
|
3221
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2483
3222
|
const result = await client.execute({
|
|
2484
|
-
sql:
|
|
2485
|
-
args: [cutoff]
|
|
3223
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
3224
|
+
args: [cutoff, ...scope.args]
|
|
2486
3225
|
});
|
|
2487
3226
|
return result.rowsAffected;
|
|
2488
3227
|
} catch {
|
|
2489
3228
|
return 0;
|
|
2490
3229
|
}
|
|
2491
3230
|
}
|
|
2492
|
-
async function markDoneTaskNotificationsAsRead() {
|
|
3231
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
2493
3232
|
try {
|
|
2494
3233
|
const client = getClient();
|
|
3234
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2495
3235
|
const result = await client.execute({
|
|
2496
3236
|
sql: `UPDATE notifications SET read = 1
|
|
2497
3237
|
WHERE read = 0
|
|
2498
3238
|
AND task_file IS NOT NULL
|
|
3239
|
+
${scope.sql}
|
|
2499
3240
|
AND task_file IN (
|
|
2500
|
-
SELECT task_file FROM tasks WHERE status = 'done'
|
|
3241
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
2501
3242
|
)`,
|
|
2502
|
-
args: []
|
|
3243
|
+
args: [...scope.args, ...scope.args]
|
|
2503
3244
|
});
|
|
2504
3245
|
return result.rowsAffected;
|
|
2505
3246
|
} catch {
|
|
@@ -2530,9 +3271,9 @@ function formatNotifications(notifications) {
|
|
|
2530
3271
|
return lines.join("\n");
|
|
2531
3272
|
}
|
|
2532
3273
|
async function migrateJsonNotifications() {
|
|
2533
|
-
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR ||
|
|
2534
|
-
const notifDir =
|
|
2535
|
-
if (!
|
|
3274
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os8.homedir(), ".exe-os");
|
|
3275
|
+
const notifDir = path12.join(base, "notifications");
|
|
3276
|
+
if (!existsSync11(notifDir)) return 0;
|
|
2536
3277
|
let migrated = 0;
|
|
2537
3278
|
try {
|
|
2538
3279
|
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
@@ -2540,19 +3281,20 @@ async function migrateJsonNotifications() {
|
|
|
2540
3281
|
const client = getClient();
|
|
2541
3282
|
for (const file of files) {
|
|
2542
3283
|
try {
|
|
2543
|
-
const filePath =
|
|
2544
|
-
const data = JSON.parse(
|
|
3284
|
+
const filePath = path12.join(notifDir, file);
|
|
3285
|
+
const data = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
2545
3286
|
await client.execute({
|
|
2546
|
-
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2547
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3287
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3288
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2548
3289
|
args: [
|
|
2549
|
-
|
|
3290
|
+
crypto2.randomUUID(),
|
|
2550
3291
|
data.agentId ?? "unknown",
|
|
2551
3292
|
data.agentRole ?? "unknown",
|
|
2552
3293
|
data.event ?? "session_summary",
|
|
2553
3294
|
data.project ?? "unknown",
|
|
2554
3295
|
data.summary ?? "",
|
|
2555
3296
|
data.taskFile ?? null,
|
|
3297
|
+
null,
|
|
2556
3298
|
data.read ? 1 : 0,
|
|
2557
3299
|
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2558
3300
|
]
|
|
@@ -2606,12 +3348,13 @@ var init_notifications = __esm({
|
|
|
2606
3348
|
"src/lib/notifications.ts"() {
|
|
2607
3349
|
"use strict";
|
|
2608
3350
|
init_database();
|
|
3351
|
+
init_task_scope();
|
|
2609
3352
|
CLEANUP_DAYS = 7;
|
|
2610
3353
|
}
|
|
2611
3354
|
});
|
|
2612
3355
|
|
|
2613
3356
|
// src/lib/session-kill-telemetry.ts
|
|
2614
|
-
import
|
|
3357
|
+
import crypto3 from "crypto";
|
|
2615
3358
|
async function recordSessionKill(input2) {
|
|
2616
3359
|
try {
|
|
2617
3360
|
const client = getClient();
|
|
@@ -2621,7 +3364,7 @@ async function recordSessionKill(input2) {
|
|
|
2621
3364
|
ticks_idle, estimated_tokens_saved)
|
|
2622
3365
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2623
3366
|
args: [
|
|
2624
|
-
|
|
3367
|
+
crypto3.randomUUID(),
|
|
2625
3368
|
input2.sessionName,
|
|
2626
3369
|
input2.agentId,
|
|
2627
3370
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2716,12 +3459,12 @@ __export(tasks_crud_exports, {
|
|
|
2716
3459
|
updateTaskStatus: () => updateTaskStatus,
|
|
2717
3460
|
writeCheckpoint: () => writeCheckpoint
|
|
2718
3461
|
});
|
|
2719
|
-
import
|
|
2720
|
-
import
|
|
2721
|
-
import
|
|
3462
|
+
import crypto4 from "crypto";
|
|
3463
|
+
import path13 from "path";
|
|
3464
|
+
import os9 from "os";
|
|
2722
3465
|
import { execSync as execSync5 } from "child_process";
|
|
2723
3466
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2724
|
-
import { existsSync as
|
|
3467
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
2725
3468
|
async function writeCheckpoint(input2) {
|
|
2726
3469
|
const client = getClient();
|
|
2727
3470
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -2837,7 +3580,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2837
3580
|
}
|
|
2838
3581
|
async function createTaskCore(input2) {
|
|
2839
3582
|
const client = getClient();
|
|
2840
|
-
const id =
|
|
3583
|
+
const id = crypto4.randomUUID();
|
|
2841
3584
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2842
3585
|
const slug = slugify(input2.title);
|
|
2843
3586
|
let earlySessionScope = null;
|
|
@@ -2896,8 +3639,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2896
3639
|
}
|
|
2897
3640
|
if (input2.baseDir) {
|
|
2898
3641
|
try {
|
|
2899
|
-
await mkdir3(
|
|
2900
|
-
await mkdir3(
|
|
3642
|
+
await mkdir3(path13.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
3643
|
+
await mkdir3(path13.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
2901
3644
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
2902
3645
|
await ensureGitignoreExe(input2.baseDir);
|
|
2903
3646
|
} catch {
|
|
@@ -2933,13 +3676,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2933
3676
|
});
|
|
2934
3677
|
if (input2.baseDir) {
|
|
2935
3678
|
try {
|
|
2936
|
-
const EXE_OS_DIR =
|
|
2937
|
-
const mdPath =
|
|
2938
|
-
const mdDir =
|
|
2939
|
-
if (!
|
|
3679
|
+
const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
|
|
3680
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3681
|
+
const mdDir = path13.dirname(mdPath);
|
|
3682
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2940
3683
|
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
2941
3684
|
const mdContent = `# ${input2.title}
|
|
2942
3685
|
|
|
3686
|
+
## MANDATORY: When done
|
|
3687
|
+
|
|
3688
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3689
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3690
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3691
|
+
|
|
2943
3692
|
**ID:** ${id}
|
|
2944
3693
|
**Status:** ${initialStatus}
|
|
2945
3694
|
**Priority:** ${input2.priority}
|
|
@@ -2953,12 +3702,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2953
3702
|
## Context
|
|
2954
3703
|
|
|
2955
3704
|
${input2.context}
|
|
2956
|
-
|
|
2957
|
-
## MANDATORY: When done
|
|
2958
|
-
|
|
2959
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2960
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2961
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2962
3705
|
`;
|
|
2963
3706
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2964
3707
|
} catch (err) {
|
|
@@ -3207,7 +3950,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3207
3950
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3208
3951
|
} catch {
|
|
3209
3952
|
}
|
|
3210
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
3953
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
3211
3954
|
try {
|
|
3212
3955
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3213
3956
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3236,9 +3979,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3236
3979
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3237
3980
|
}
|
|
3238
3981
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3239
|
-
const archPath =
|
|
3982
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3240
3983
|
try {
|
|
3241
|
-
if (
|
|
3984
|
+
if (existsSync12(archPath)) return;
|
|
3242
3985
|
const template = [
|
|
3243
3986
|
`# ${projectName} \u2014 System Architecture`,
|
|
3244
3987
|
"",
|
|
@@ -3271,10 +4014,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3271
4014
|
}
|
|
3272
4015
|
}
|
|
3273
4016
|
async function ensureGitignoreExe(baseDir) {
|
|
3274
|
-
const gitignorePath =
|
|
4017
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3275
4018
|
try {
|
|
3276
|
-
if (
|
|
3277
|
-
const content =
|
|
4019
|
+
if (existsSync12(gitignorePath)) {
|
|
4020
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
3278
4021
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3279
4022
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3280
4023
|
} else {
|
|
@@ -3305,58 +4048,42 @@ var init_tasks_crud = __esm({
|
|
|
3305
4048
|
});
|
|
3306
4049
|
|
|
3307
4050
|
// src/lib/tasks-review.ts
|
|
3308
|
-
import
|
|
3309
|
-
import { existsSync as
|
|
4051
|
+
import path14 from "path";
|
|
4052
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
3310
4053
|
async function countPendingReviews(sessionScope) {
|
|
3311
4054
|
const client = getClient();
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
args: [sessionScope]
|
|
3316
|
-
});
|
|
3317
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3318
|
-
}
|
|
4055
|
+
const scope = strictSessionScopeFilter(
|
|
4056
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4057
|
+
);
|
|
3319
4058
|
const result = await client.execute({
|
|
3320
|
-
sql:
|
|
3321
|
-
|
|
4059
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4060
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
4061
|
+
args: [...scope.args]
|
|
3322
4062
|
});
|
|
3323
4063
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3324
4064
|
}
|
|
3325
4065
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3326
4066
|
const client = getClient();
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3331
|
-
AND session_scope = ?`,
|
|
3332
|
-
args: [sinceIso, sessionScope]
|
|
3333
|
-
});
|
|
3334
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3335
|
-
}
|
|
4067
|
+
const scope = strictSessionScopeFilter(
|
|
4068
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4069
|
+
);
|
|
3336
4070
|
const result = await client.execute({
|
|
3337
4071
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3338
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3339
|
-
args: [sinceIso]
|
|
4072
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
4073
|
+
args: [sinceIso, ...scope.args]
|
|
3340
4074
|
});
|
|
3341
4075
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3342
4076
|
}
|
|
3343
4077
|
async function listPendingReviews(limit, sessionScope) {
|
|
3344
4078
|
const client = getClient();
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
WHERE status = 'needs_review'
|
|
3349
|
-
AND session_scope = ?
|
|
3350
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3351
|
-
args: [sessionScope, limit]
|
|
3352
|
-
});
|
|
3353
|
-
return result2.rows;
|
|
3354
|
-
}
|
|
4079
|
+
const scope = strictSessionScopeFilter(
|
|
4080
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
4081
|
+
);
|
|
3355
4082
|
const result = await client.execute({
|
|
3356
4083
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3357
|
-
WHERE status = 'needs_review'
|
|
4084
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3358
4085
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3359
|
-
args: [limit]
|
|
4086
|
+
args: [...scope.args, limit]
|
|
3360
4087
|
});
|
|
3361
4088
|
return result.rows;
|
|
3362
4089
|
}
|
|
@@ -3368,7 +4095,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3368
4095
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3369
4096
|
AND assigned_by = 'system'
|
|
3370
4097
|
AND title LIKE 'Review:%'
|
|
3371
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
4098
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3372
4099
|
args: [now]
|
|
3373
4100
|
});
|
|
3374
4101
|
const r1b = await client.execute({
|
|
@@ -3487,11 +4214,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3487
4214
|
);
|
|
3488
4215
|
}
|
|
3489
4216
|
try {
|
|
3490
|
-
const cacheDir =
|
|
3491
|
-
if (
|
|
4217
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4218
|
+
if (existsSync13(cacheDir)) {
|
|
3492
4219
|
for (const f of readdirSync3(cacheDir)) {
|
|
3493
4220
|
if (f.startsWith("review-notified-")) {
|
|
3494
|
-
unlinkSync5(
|
|
4221
|
+
unlinkSync5(path14.join(cacheDir, f));
|
|
3495
4222
|
}
|
|
3496
4223
|
}
|
|
3497
4224
|
}
|
|
@@ -3508,11 +4235,12 @@ var init_tasks_review = __esm({
|
|
|
3508
4235
|
init_tmux_routing();
|
|
3509
4236
|
init_session_key();
|
|
3510
4237
|
init_state_bus();
|
|
4238
|
+
init_task_scope();
|
|
3511
4239
|
}
|
|
3512
4240
|
});
|
|
3513
4241
|
|
|
3514
4242
|
// src/lib/tasks-chain.ts
|
|
3515
|
-
import
|
|
4243
|
+
import path15 from "path";
|
|
3516
4244
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3517
4245
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3518
4246
|
const client = getClient();
|
|
@@ -3529,7 +4257,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3529
4257
|
});
|
|
3530
4258
|
for (const ur of unblockedRows.rows) {
|
|
3531
4259
|
try {
|
|
3532
|
-
const ubFile =
|
|
4260
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3533
4261
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3534
4262
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3535
4263
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3564,7 +4292,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3564
4292
|
const scScope = sessionScopeFilter();
|
|
3565
4293
|
const remaining = await client.execute({
|
|
3566
4294
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3567
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4295
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3568
4296
|
args: [parentTaskId, ...scScope.args]
|
|
3569
4297
|
});
|
|
3570
4298
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3598,7 +4326,7 @@ var init_tasks_chain = __esm({
|
|
|
3598
4326
|
|
|
3599
4327
|
// src/lib/project-name.ts
|
|
3600
4328
|
import { execSync as execSync6 } from "child_process";
|
|
3601
|
-
import
|
|
4329
|
+
import path16 from "path";
|
|
3602
4330
|
function getProjectName(cwd) {
|
|
3603
4331
|
const dir = cwd ?? process.cwd();
|
|
3604
4332
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3611,7 +4339,7 @@ function getProjectName(cwd) {
|
|
|
3611
4339
|
timeout: 2e3,
|
|
3612
4340
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3613
4341
|
}).trim();
|
|
3614
|
-
repoRoot =
|
|
4342
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
3615
4343
|
} catch {
|
|
3616
4344
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3617
4345
|
cwd: dir,
|
|
@@ -3620,11 +4348,11 @@ function getProjectName(cwd) {
|
|
|
3620
4348
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3621
4349
|
}).trim();
|
|
3622
4350
|
}
|
|
3623
|
-
_cached2 =
|
|
4351
|
+
_cached2 = path16.basename(repoRoot);
|
|
3624
4352
|
_cachedCwd = dir;
|
|
3625
4353
|
return _cached2;
|
|
3626
4354
|
} catch {
|
|
3627
|
-
_cached2 =
|
|
4355
|
+
_cached2 = path16.basename(dir);
|
|
3628
4356
|
_cachedCwd = dir;
|
|
3629
4357
|
return _cached2;
|
|
3630
4358
|
}
|
|
@@ -3767,10 +4495,10 @@ var init_tasks_notify = __esm({
|
|
|
3767
4495
|
});
|
|
3768
4496
|
|
|
3769
4497
|
// src/lib/behaviors.ts
|
|
3770
|
-
import
|
|
4498
|
+
import crypto5 from "crypto";
|
|
3771
4499
|
async function storeBehavior(opts) {
|
|
3772
4500
|
const client = getClient();
|
|
3773
|
-
const id =
|
|
4501
|
+
const id = crypto5.randomUUID();
|
|
3774
4502
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3775
4503
|
await client.execute({
|
|
3776
4504
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -3799,7 +4527,7 @@ __export(skill_learning_exports, {
|
|
|
3799
4527
|
storeTrajectory: () => storeTrajectory,
|
|
3800
4528
|
sweepTrajectories: () => sweepTrajectories
|
|
3801
4529
|
});
|
|
3802
|
-
import
|
|
4530
|
+
import crypto6 from "crypto";
|
|
3803
4531
|
async function extractTrajectory(taskId, agentId) {
|
|
3804
4532
|
const client = getClient();
|
|
3805
4533
|
const result = await client.execute({
|
|
@@ -3828,11 +4556,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3828
4556
|
return signature;
|
|
3829
4557
|
}
|
|
3830
4558
|
function hashSignature(signature) {
|
|
3831
|
-
return
|
|
4559
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3832
4560
|
}
|
|
3833
4561
|
async function storeTrajectory(opts) {
|
|
3834
4562
|
const client = getClient();
|
|
3835
|
-
const id =
|
|
4563
|
+
const id = crypto6.randomUUID();
|
|
3836
4564
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3837
4565
|
const signatureHash = hashSignature(opts.signature);
|
|
3838
4566
|
await client.execute({
|
|
@@ -4097,8 +4825,8 @@ __export(tasks_exports, {
|
|
|
4097
4825
|
updateTaskStatus: () => updateTaskStatus,
|
|
4098
4826
|
writeCheckpoint: () => writeCheckpoint
|
|
4099
4827
|
});
|
|
4100
|
-
import
|
|
4101
|
-
import { writeFileSync as
|
|
4828
|
+
import path17 from "path";
|
|
4829
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
4102
4830
|
async function createTask(input2) {
|
|
4103
4831
|
const result = await createTaskCore(input2);
|
|
4104
4832
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4117,12 +4845,12 @@ async function updateTask(input2) {
|
|
|
4117
4845
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
4118
4846
|
try {
|
|
4119
4847
|
const agent = String(row.assigned_to);
|
|
4120
|
-
const cacheDir =
|
|
4121
|
-
const cachePath =
|
|
4848
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4849
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4122
4850
|
if (input2.status === "in_progress") {
|
|
4123
4851
|
mkdirSync6(cacheDir, { recursive: true });
|
|
4124
|
-
|
|
4125
|
-
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4852
|
+
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4853
|
+
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4126
4854
|
try {
|
|
4127
4855
|
unlinkSync6(cachePath);
|
|
4128
4856
|
} catch {
|
|
@@ -4130,10 +4858,10 @@ async function updateTask(input2) {
|
|
|
4130
4858
|
}
|
|
4131
4859
|
} catch {
|
|
4132
4860
|
}
|
|
4133
|
-
if (input2.status === "done") {
|
|
4861
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4134
4862
|
await cleanupReviewFile(row, taskFile, input2.baseDir);
|
|
4135
4863
|
}
|
|
4136
|
-
if (input2.status === "done" || input2.status === "cancelled") {
|
|
4864
|
+
if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
|
|
4137
4865
|
try {
|
|
4138
4866
|
const client = getClient();
|
|
4139
4867
|
const taskTitle = String(row.title);
|
|
@@ -4149,7 +4877,7 @@ async function updateTask(input2) {
|
|
|
4149
4877
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4150
4878
|
try {
|
|
4151
4879
|
const draftClient = getClient();
|
|
4152
|
-
if (input2.status === "done") {
|
|
4880
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4153
4881
|
await draftClient.execute({
|
|
4154
4882
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4155
4883
|
args: [assignedAgent]
|
|
@@ -4166,7 +4894,7 @@ async function updateTask(input2) {
|
|
|
4166
4894
|
try {
|
|
4167
4895
|
const client = getClient();
|
|
4168
4896
|
const cascaded = await client.execute({
|
|
4169
|
-
sql: `UPDATE tasks SET status = '
|
|
4897
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4170
4898
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4171
4899
|
args: [now, taskId]
|
|
4172
4900
|
});
|
|
@@ -4179,14 +4907,14 @@ async function updateTask(input2) {
|
|
|
4179
4907
|
} catch {
|
|
4180
4908
|
}
|
|
4181
4909
|
}
|
|
4182
|
-
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
4910
|
+
const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
|
|
4183
4911
|
if (isTerminal) {
|
|
4184
4912
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4185
4913
|
if (!isCoordinator) {
|
|
4186
4914
|
notifyTaskDone();
|
|
4187
4915
|
}
|
|
4188
4916
|
await markTaskNotificationsRead(taskFile);
|
|
4189
|
-
if (input2.status === "done") {
|
|
4917
|
+
if (input2.status === "done" || input2.status === "closed") {
|
|
4190
4918
|
try {
|
|
4191
4919
|
await cascadeUnblock(taskId, input2.baseDir, now);
|
|
4192
4920
|
} catch {
|
|
@@ -4206,7 +4934,7 @@ async function updateTask(input2) {
|
|
|
4206
4934
|
}
|
|
4207
4935
|
}
|
|
4208
4936
|
}
|
|
4209
|
-
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4937
|
+
if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4210
4938
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4211
4939
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4212
4940
|
taskId,
|
|
@@ -4578,6 +5306,7 @@ __export(tmux_routing_exports, {
|
|
|
4578
5306
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4579
5307
|
isExeSession: () => isExeSession,
|
|
4580
5308
|
isSessionBusy: () => isSessionBusy,
|
|
5309
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4581
5310
|
notifyParentExe: () => notifyParentExe,
|
|
4582
5311
|
parseParentExe: () => parseParentExe,
|
|
4583
5312
|
registerParentExe: () => registerParentExe,
|
|
@@ -4588,13 +5317,13 @@ __export(tmux_routing_exports, {
|
|
|
4588
5317
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4589
5318
|
});
|
|
4590
5319
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4591
|
-
import { readFileSync as
|
|
4592
|
-
import
|
|
4593
|
-
import
|
|
5320
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
5321
|
+
import path18 from "path";
|
|
5322
|
+
import os10 from "os";
|
|
4594
5323
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4595
5324
|
import { unlinkSync as unlinkSync7 } from "fs";
|
|
4596
5325
|
function spawnLockPath(sessionName) {
|
|
4597
|
-
return
|
|
5326
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4598
5327
|
}
|
|
4599
5328
|
function isProcessAlive(pid) {
|
|
4600
5329
|
try {
|
|
@@ -4605,13 +5334,13 @@ function isProcessAlive(pid) {
|
|
|
4605
5334
|
}
|
|
4606
5335
|
}
|
|
4607
5336
|
function acquireSpawnLock2(sessionName) {
|
|
4608
|
-
if (!
|
|
5337
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4609
5338
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
4610
5339
|
}
|
|
4611
5340
|
const lockFile = spawnLockPath(sessionName);
|
|
4612
|
-
if (
|
|
5341
|
+
if (existsSync14(lockFile)) {
|
|
4613
5342
|
try {
|
|
4614
|
-
const lock = JSON.parse(
|
|
5343
|
+
const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
|
|
4615
5344
|
const age = Date.now() - lock.timestamp;
|
|
4616
5345
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4617
5346
|
return false;
|
|
@@ -4619,7 +5348,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4619
5348
|
} catch {
|
|
4620
5349
|
}
|
|
4621
5350
|
}
|
|
4622
|
-
|
|
5351
|
+
writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4623
5352
|
return true;
|
|
4624
5353
|
}
|
|
4625
5354
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4631,13 +5360,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4631
5360
|
function resolveBehaviorsExporterScript() {
|
|
4632
5361
|
try {
|
|
4633
5362
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4634
|
-
const scriptPath =
|
|
4635
|
-
|
|
5363
|
+
const scriptPath = path18.join(
|
|
5364
|
+
path18.dirname(thisFile),
|
|
4636
5365
|
"..",
|
|
4637
5366
|
"bin",
|
|
4638
5367
|
"exe-export-behaviors.js"
|
|
4639
5368
|
);
|
|
4640
|
-
return
|
|
5369
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4641
5370
|
} catch {
|
|
4642
5371
|
return null;
|
|
4643
5372
|
}
|
|
@@ -4703,12 +5432,12 @@ function extractRootExe(name) {
|
|
|
4703
5432
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4704
5433
|
}
|
|
4705
5434
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4706
|
-
if (!
|
|
5435
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4707
5436
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
4708
5437
|
}
|
|
4709
5438
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4710
|
-
const filePath =
|
|
4711
|
-
|
|
5439
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5440
|
+
writeFileSync9(filePath, JSON.stringify({
|
|
4712
5441
|
parentExe: rootExe,
|
|
4713
5442
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4714
5443
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4716,7 +5445,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4716
5445
|
}
|
|
4717
5446
|
function getParentExe(sessionKey) {
|
|
4718
5447
|
try {
|
|
4719
|
-
const data = JSON.parse(
|
|
5448
|
+
const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4720
5449
|
return data.parentExe || null;
|
|
4721
5450
|
} catch {
|
|
4722
5451
|
return null;
|
|
@@ -4724,8 +5453,8 @@ function getParentExe(sessionKey) {
|
|
|
4724
5453
|
}
|
|
4725
5454
|
function getDispatchedBy(sessionKey) {
|
|
4726
5455
|
try {
|
|
4727
|
-
const data = JSON.parse(
|
|
4728
|
-
|
|
5456
|
+
const data = JSON.parse(readFileSync13(
|
|
5457
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4729
5458
|
"utf8"
|
|
4730
5459
|
));
|
|
4731
5460
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4795,8 +5524,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4795
5524
|
}
|
|
4796
5525
|
function readDebounceState() {
|
|
4797
5526
|
try {
|
|
4798
|
-
if (!
|
|
4799
|
-
const raw = JSON.parse(
|
|
5527
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5528
|
+
const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
|
|
4800
5529
|
const state = {};
|
|
4801
5530
|
for (const [key, val] of Object.entries(raw)) {
|
|
4802
5531
|
if (typeof val === "number") {
|
|
@@ -4812,8 +5541,8 @@ function readDebounceState() {
|
|
|
4812
5541
|
}
|
|
4813
5542
|
function writeDebounceState(state) {
|
|
4814
5543
|
try {
|
|
4815
|
-
if (!
|
|
4816
|
-
|
|
5544
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5545
|
+
writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4817
5546
|
} catch {
|
|
4818
5547
|
}
|
|
4819
5548
|
}
|
|
@@ -4911,8 +5640,8 @@ function sendIntercom(targetSession) {
|
|
|
4911
5640
|
try {
|
|
4912
5641
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4913
5642
|
const agent = baseAgentName(rawAgent);
|
|
4914
|
-
const markerPath =
|
|
4915
|
-
if (
|
|
5643
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5644
|
+
if (existsSync14(markerPath)) {
|
|
4916
5645
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4917
5646
|
return "debounced";
|
|
4918
5647
|
}
|
|
@@ -4921,8 +5650,8 @@ function sendIntercom(targetSession) {
|
|
|
4921
5650
|
try {
|
|
4922
5651
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4923
5652
|
const agent = baseAgentName(rawAgent);
|
|
4924
|
-
const taskDir =
|
|
4925
|
-
if (
|
|
5653
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5654
|
+
if (existsSync14(taskDir)) {
|
|
4926
5655
|
const files = readdirSync4(taskDir).filter(
|
|
4927
5656
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4928
5657
|
);
|
|
@@ -4982,6 +5711,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4982
5711
|
}
|
|
4983
5712
|
return true;
|
|
4984
5713
|
}
|
|
5714
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5715
|
+
const transport = getTransport();
|
|
5716
|
+
try {
|
|
5717
|
+
const sessions = transport.listSessions();
|
|
5718
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5719
|
+
execSync7(
|
|
5720
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5721
|
+
{ timeout: 3e3 }
|
|
5722
|
+
);
|
|
5723
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5724
|
+
return true;
|
|
5725
|
+
} catch {
|
|
5726
|
+
return false;
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
4985
5729
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4986
5730
|
if (isCoordinatorName(employeeName)) {
|
|
4987
5731
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -5055,26 +5799,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5055
5799
|
const transport = getTransport();
|
|
5056
5800
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5057
5801
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5058
|
-
const logDir =
|
|
5059
|
-
const logFile =
|
|
5060
|
-
if (!
|
|
5802
|
+
const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5803
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5804
|
+
if (!existsSync14(logDir)) {
|
|
5061
5805
|
mkdirSync7(logDir, { recursive: true });
|
|
5062
5806
|
}
|
|
5063
5807
|
transport.kill(sessionName);
|
|
5064
5808
|
let cleanupSuffix = "";
|
|
5065
5809
|
try {
|
|
5066
5810
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5067
|
-
const cleanupScript =
|
|
5068
|
-
if (
|
|
5811
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5812
|
+
if (existsSync14(cleanupScript)) {
|
|
5069
5813
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5070
5814
|
}
|
|
5071
5815
|
} catch {
|
|
5072
5816
|
}
|
|
5073
5817
|
try {
|
|
5074
|
-
const claudeJsonPath =
|
|
5818
|
+
const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
|
|
5075
5819
|
let claudeJson = {};
|
|
5076
5820
|
try {
|
|
5077
|
-
claudeJson = JSON.parse(
|
|
5821
|
+
claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
|
|
5078
5822
|
} catch {
|
|
5079
5823
|
}
|
|
5080
5824
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5082,17 +5826,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5082
5826
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5083
5827
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5084
5828
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5085
|
-
|
|
5829
|
+
writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5086
5830
|
} catch {
|
|
5087
5831
|
}
|
|
5088
5832
|
try {
|
|
5089
|
-
const settingsDir =
|
|
5833
|
+
const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
|
|
5090
5834
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5091
|
-
const projSettingsDir =
|
|
5092
|
-
const settingsPath =
|
|
5835
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5836
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5093
5837
|
let settings = {};
|
|
5094
5838
|
try {
|
|
5095
|
-
settings = JSON.parse(
|
|
5839
|
+
settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
|
|
5096
5840
|
} catch {
|
|
5097
5841
|
}
|
|
5098
5842
|
const perms = settings.permissions ?? {};
|
|
@@ -5121,7 +5865,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5121
5865
|
perms.allow = allow;
|
|
5122
5866
|
settings.permissions = perms;
|
|
5123
5867
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
5124
|
-
|
|
5868
|
+
writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5125
5869
|
}
|
|
5126
5870
|
} catch {
|
|
5127
5871
|
}
|
|
@@ -5136,8 +5880,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5136
5880
|
let behaviorsFlag = "";
|
|
5137
5881
|
let legacyFallbackWarned = false;
|
|
5138
5882
|
if (!useExeAgent && !useBinSymlink) {
|
|
5139
|
-
const identityPath =
|
|
5140
|
-
|
|
5883
|
+
const identityPath = path18.join(
|
|
5884
|
+
os10.homedir(),
|
|
5141
5885
|
".exe-os",
|
|
5142
5886
|
"identity",
|
|
5143
5887
|
`${employeeName}.md`
|
|
@@ -5146,13 +5890,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5146
5890
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5147
5891
|
if (hasAgentFlag) {
|
|
5148
5892
|
identityFlag = ` --agent ${employeeName}`;
|
|
5149
|
-
} else if (
|
|
5893
|
+
} else if (existsSync14(identityPath)) {
|
|
5150
5894
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5151
5895
|
legacyFallbackWarned = true;
|
|
5152
5896
|
}
|
|
5153
5897
|
const behaviorsFile = exportBehaviorsSync(
|
|
5154
5898
|
employeeName,
|
|
5155
|
-
|
|
5899
|
+
path18.basename(spawnCwd),
|
|
5156
5900
|
sessionName
|
|
5157
5901
|
);
|
|
5158
5902
|
if (behaviorsFile) {
|
|
@@ -5167,16 +5911,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5167
5911
|
}
|
|
5168
5912
|
let sessionContextFlag = "";
|
|
5169
5913
|
try {
|
|
5170
|
-
const ctxDir =
|
|
5914
|
+
const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5171
5915
|
mkdirSync7(ctxDir, { recursive: true });
|
|
5172
|
-
const ctxFile =
|
|
5916
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5173
5917
|
const ctxContent = [
|
|
5174
5918
|
`## Session Context`,
|
|
5175
5919
|
`You are running in tmux session: ${sessionName}.`,
|
|
5176
5920
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5177
5921
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5178
5922
|
].join("\n");
|
|
5179
|
-
|
|
5923
|
+
writeFileSync9(ctxFile, ctxContent);
|
|
5180
5924
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5181
5925
|
} catch {
|
|
5182
5926
|
}
|
|
@@ -5253,8 +5997,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5253
5997
|
transport.pipeLog(sessionName, logFile);
|
|
5254
5998
|
try {
|
|
5255
5999
|
const mySession = getMySession();
|
|
5256
|
-
const dispatchInfo =
|
|
5257
|
-
|
|
6000
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
6001
|
+
writeFileSync9(dispatchInfo, JSON.stringify({
|
|
5258
6002
|
dispatchedBy: mySession,
|
|
5259
6003
|
rootExe: exeSession,
|
|
5260
6004
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5328,15 +6072,15 @@ var init_tmux_routing = __esm({
|
|
|
5328
6072
|
init_intercom_queue();
|
|
5329
6073
|
init_plan_limits();
|
|
5330
6074
|
init_employees();
|
|
5331
|
-
SPAWN_LOCK_DIR =
|
|
5332
|
-
SESSION_CACHE =
|
|
6075
|
+
SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
6076
|
+
SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5333
6077
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5334
6078
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5335
6079
|
VERIFY_PANE_LINES = 200;
|
|
5336
6080
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5337
6081
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5338
|
-
INTERCOM_LOG2 =
|
|
5339
|
-
DEBOUNCE_FILE =
|
|
6082
|
+
INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
6083
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5340
6084
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5341
6085
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5342
6086
|
}
|
|
@@ -5359,6 +6103,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5359
6103
|
args: [scope]
|
|
5360
6104
|
};
|
|
5361
6105
|
}
|
|
6106
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
6107
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
6108
|
+
if (!scope) return { sql: "", args: [] };
|
|
6109
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
6110
|
+
return {
|
|
6111
|
+
sql: ` AND ${col} = ?`,
|
|
6112
|
+
args: [scope]
|
|
6113
|
+
};
|
|
6114
|
+
}
|
|
5362
6115
|
var init_task_scope = __esm({
|
|
5363
6116
|
"src/lib/task-scope.ts"() {
|
|
5364
6117
|
"use strict";
|
|
@@ -5377,14 +6130,14 @@ var init_memory = __esm({
|
|
|
5377
6130
|
|
|
5378
6131
|
// src/lib/keychain.ts
|
|
5379
6132
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5380
|
-
import { existsSync as
|
|
5381
|
-
import
|
|
5382
|
-
import
|
|
6133
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6134
|
+
import path19 from "path";
|
|
6135
|
+
import os11 from "os";
|
|
5383
6136
|
function getKeyDir() {
|
|
5384
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6137
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
|
|
5385
6138
|
}
|
|
5386
6139
|
function getKeyPath() {
|
|
5387
|
-
return
|
|
6140
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5388
6141
|
}
|
|
5389
6142
|
async function tryKeytar() {
|
|
5390
6143
|
try {
|
|
@@ -5405,9 +6158,9 @@ async function getMasterKey() {
|
|
|
5405
6158
|
}
|
|
5406
6159
|
}
|
|
5407
6160
|
const keyPath = getKeyPath();
|
|
5408
|
-
if (!
|
|
6161
|
+
if (!existsSync15(keyPath)) {
|
|
5409
6162
|
process.stderr.write(
|
|
5410
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6163
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5411
6164
|
`
|
|
5412
6165
|
);
|
|
5413
6166
|
return null;
|
|
@@ -5437,6 +6190,7 @@ var shard_manager_exports = {};
|
|
|
5437
6190
|
__export(shard_manager_exports, {
|
|
5438
6191
|
disposeShards: () => disposeShards,
|
|
5439
6192
|
ensureShardSchema: () => ensureShardSchema,
|
|
6193
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5440
6194
|
getReadyShardClient: () => getReadyShardClient,
|
|
5441
6195
|
getShardClient: () => getShardClient,
|
|
5442
6196
|
getShardsDir: () => getShardsDir,
|
|
@@ -5445,15 +6199,18 @@ __export(shard_manager_exports, {
|
|
|
5445
6199
|
listShards: () => listShards,
|
|
5446
6200
|
shardExists: () => shardExists
|
|
5447
6201
|
});
|
|
5448
|
-
import
|
|
5449
|
-
import { existsSync as
|
|
6202
|
+
import path20 from "path";
|
|
6203
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
|
|
5450
6204
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5451
6205
|
function initShardManager(encryptionKey) {
|
|
5452
6206
|
_encryptionKey = encryptionKey;
|
|
5453
|
-
if (!
|
|
6207
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5454
6208
|
mkdirSync8(SHARDS_DIR, { recursive: true });
|
|
5455
6209
|
}
|
|
5456
6210
|
_shardingEnabled = true;
|
|
6211
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6212
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6213
|
+
_evictionTimer.unref();
|
|
5457
6214
|
}
|
|
5458
6215
|
function isShardingEnabled() {
|
|
5459
6216
|
return _shardingEnabled;
|
|
@@ -5470,21 +6227,28 @@ function getShardClient(projectName) {
|
|
|
5470
6227
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5471
6228
|
}
|
|
5472
6229
|
const cached = _shards.get(safeName);
|
|
5473
|
-
if (cached)
|
|
5474
|
-
|
|
6230
|
+
if (cached) {
|
|
6231
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6232
|
+
return cached;
|
|
6233
|
+
}
|
|
6234
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6235
|
+
evictLRU();
|
|
6236
|
+
}
|
|
6237
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5475
6238
|
const client = createClient2({
|
|
5476
6239
|
url: `file:${dbPath}`,
|
|
5477
6240
|
encryptionKey: _encryptionKey
|
|
5478
6241
|
});
|
|
5479
6242
|
_shards.set(safeName, client);
|
|
6243
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5480
6244
|
return client;
|
|
5481
6245
|
}
|
|
5482
6246
|
function shardExists(projectName) {
|
|
5483
6247
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5484
|
-
return
|
|
6248
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
5485
6249
|
}
|
|
5486
6250
|
function listShards() {
|
|
5487
|
-
if (!
|
|
6251
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5488
6252
|
return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5489
6253
|
}
|
|
5490
6254
|
async function ensureShardSchema(client) {
|
|
@@ -5536,6 +6300,8 @@ async function ensureShardSchema(client) {
|
|
|
5536
6300
|
for (const col of [
|
|
5537
6301
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5538
6302
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6303
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6304
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5539
6305
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5540
6306
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5541
6307
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5558,7 +6324,23 @@ async function ensureShardSchema(client) {
|
|
|
5558
6324
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5559
6325
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5560
6326
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5561
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6327
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6328
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6329
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6330
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6331
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6332
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6333
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6334
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6335
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6336
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6337
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6338
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6339
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6340
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6341
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6342
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6343
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5562
6344
|
]) {
|
|
5563
6345
|
try {
|
|
5564
6346
|
await client.execute(col);
|
|
@@ -5657,21 +6439,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5657
6439
|
await ensureShardSchema(client);
|
|
5658
6440
|
return client;
|
|
5659
6441
|
}
|
|
6442
|
+
function evictLRU() {
|
|
6443
|
+
let oldest = null;
|
|
6444
|
+
let oldestTime = Infinity;
|
|
6445
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6446
|
+
if (time < oldestTime) {
|
|
6447
|
+
oldestTime = time;
|
|
6448
|
+
oldest = name;
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
if (oldest) {
|
|
6452
|
+
const client = _shards.get(oldest);
|
|
6453
|
+
if (client) {
|
|
6454
|
+
client.close();
|
|
6455
|
+
}
|
|
6456
|
+
_shards.delete(oldest);
|
|
6457
|
+
_shardLastAccess.delete(oldest);
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
function evictIdleShards() {
|
|
6461
|
+
const now = Date.now();
|
|
6462
|
+
const toEvict = [];
|
|
6463
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6464
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6465
|
+
toEvict.push(name);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
for (const name of toEvict) {
|
|
6469
|
+
const client = _shards.get(name);
|
|
6470
|
+
if (client) {
|
|
6471
|
+
client.close();
|
|
6472
|
+
}
|
|
6473
|
+
_shards.delete(name);
|
|
6474
|
+
_shardLastAccess.delete(name);
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
function getOpenShardCount() {
|
|
6478
|
+
return _shards.size;
|
|
6479
|
+
}
|
|
5660
6480
|
function disposeShards() {
|
|
6481
|
+
if (_evictionTimer) {
|
|
6482
|
+
clearInterval(_evictionTimer);
|
|
6483
|
+
_evictionTimer = null;
|
|
6484
|
+
}
|
|
5661
6485
|
for (const [, client] of _shards) {
|
|
5662
6486
|
client.close();
|
|
5663
6487
|
}
|
|
5664
6488
|
_shards.clear();
|
|
6489
|
+
_shardLastAccess.clear();
|
|
5665
6490
|
_shardingEnabled = false;
|
|
5666
6491
|
_encryptionKey = null;
|
|
5667
6492
|
}
|
|
5668
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6493
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5669
6494
|
var init_shard_manager = __esm({
|
|
5670
6495
|
"src/lib/shard-manager.ts"() {
|
|
5671
6496
|
"use strict";
|
|
5672
6497
|
init_config();
|
|
5673
|
-
SHARDS_DIR =
|
|
6498
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6499
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6500
|
+
MAX_OPEN_SHARDS = 10;
|
|
6501
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5674
6502
|
_shards = /* @__PURE__ */ new Map();
|
|
6503
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6504
|
+
_evictionTimer = null;
|
|
5675
6505
|
_encryptionKey = null;
|
|
5676
6506
|
_shardingEnabled = false;
|
|
5677
6507
|
}
|
|
@@ -6656,7 +7486,7 @@ var init_git_task_sweep = __esm({
|
|
|
6656
7486
|
init_config();
|
|
6657
7487
|
init_session_key();
|
|
6658
7488
|
init_employees();
|
|
6659
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
7489
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
6660
7490
|
import { execSync as execSync3 } from "child_process";
|
|
6661
7491
|
import path3 from "path";
|
|
6662
7492
|
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|
|
@@ -6787,7 +7617,7 @@ process.stdin.on("end", async () => {
|
|
|
6787
7617
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
6788
7618
|
const { randomUUID: randomUUID4 } = await import("crypto");
|
|
6789
7619
|
const client = getClient2();
|
|
6790
|
-
const seScope =
|
|
7620
|
+
const seScope = strictSessionScopeFilter();
|
|
6791
7621
|
const orphanResult = await client.execute({
|
|
6792
7622
|
sql: `SELECT title, status FROM tasks WHERE assigned_to = ? AND status IN ('open', 'in_progress')${seScope.sql}`,
|
|
6793
7623
|
args: [agent.agentId, ...seScope.args]
|
|
@@ -6831,8 +7661,10 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
|
|
|
6831
7661
|
let context;
|
|
6832
7662
|
try {
|
|
6833
7663
|
const ctxResult = await client.execute({
|
|
6834
|
-
sql:
|
|
6835
|
-
|
|
7664
|
+
sql: `SELECT id, context FROM tasks
|
|
7665
|
+
WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}
|
|
7666
|
+
LIMIT 1`,
|
|
7667
|
+
args: [title, agent.agentId, ...seScope.args]
|
|
6836
7668
|
});
|
|
6837
7669
|
if (ctxResult.rows.length > 0) {
|
|
6838
7670
|
context = ctxResult.rows[0].context ? String(ctxResult.rows[0].context) : void 0;
|
|
@@ -6843,12 +7675,14 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
|
|
|
6843
7675
|
const match = findBestMatch2(taskForMatch, commits);
|
|
6844
7676
|
if (match) {
|
|
6845
7677
|
await client.execute({
|
|
6846
|
-
sql:
|
|
7678
|
+
sql: `UPDATE tasks SET status = 'done', result = ?, updated_at = ?
|
|
7679
|
+
WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}`,
|
|
6847
7680
|
args: [
|
|
6848
7681
|
`Auto-closed: session ended but matching commit ${match.commit.hash} found (score: ${match.score.toFixed(2)}). Message: "${match.commit.message}"`,
|
|
6849
7682
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
6850
7683
|
title,
|
|
6851
|
-
agent.agentId
|
|
7684
|
+
agent.agentId,
|
|
7685
|
+
...seScope.args
|
|
6852
7686
|
]
|
|
6853
7687
|
});
|
|
6854
7688
|
autoClosed.push(`"${title}" \u2192 commit ${match.commit.hash}`);
|