@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
package/dist/runtime/index.js
CHANGED
|
@@ -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 path2 from "path";
|
|
32
70
|
import os2 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 = path2.join(os2.homedir(), ".exe-os");
|
|
37
75
|
const legacyDir = path2.join(os2.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 = path2.join(dir, "config.json");
|
|
103
|
-
if (!
|
|
141
|
+
if (!existsSync2(configPath)) {
|
|
104
142
|
return { ...DEFAULT_CONFIG, dbPath: path2.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 = path2.join(EXE_AI_DIR, "memories.db");
|
|
137
177
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -210,7 +250,7 @@ var init_config = __esm({
|
|
|
210
250
|
|
|
211
251
|
// src/lib/employees.ts
|
|
212
252
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
213
|
-
import { existsSync as
|
|
253
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
214
254
|
import { execSync } from "child_process";
|
|
215
255
|
import path3 from "path";
|
|
216
256
|
import os3 from "os";
|
|
@@ -231,7 +271,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
231
271
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
232
272
|
}
|
|
233
273
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
234
|
-
if (!
|
|
274
|
+
if (!existsSync3(employeesPath)) return [];
|
|
235
275
|
try {
|
|
236
276
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
237
277
|
} catch {
|
|
@@ -255,7 +295,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
255
295
|
if (!emp) return false;
|
|
256
296
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
257
297
|
}
|
|
258
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
298
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
259
299
|
var init_employees = __esm({
|
|
260
300
|
"src/lib/employees.ts"() {
|
|
261
301
|
"use strict";
|
|
@@ -264,6 +304,7 @@ var init_employees = __esm({
|
|
|
264
304
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
265
305
|
COORDINATOR_ROLE = "COO";
|
|
266
306
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
307
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
267
308
|
}
|
|
268
309
|
});
|
|
269
310
|
|
|
@@ -274,14 +315,14 @@ __export(session_registry_exports, {
|
|
|
274
315
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
275
316
|
registerSession: () => registerSession
|
|
276
317
|
});
|
|
277
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as
|
|
318
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
278
319
|
import { execSync as execSync2 } from "child_process";
|
|
279
320
|
import path4 from "path";
|
|
280
321
|
import os4 from "os";
|
|
281
322
|
function registerSession(entry) {
|
|
282
323
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
283
|
-
if (!
|
|
284
|
-
|
|
324
|
+
if (!existsSync4(dir)) {
|
|
325
|
+
mkdirSync2(dir, { recursive: true });
|
|
285
326
|
}
|
|
286
327
|
const sessions = listSessions();
|
|
287
328
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -613,10 +654,10 @@ var init_runtime_table = __esm({
|
|
|
613
654
|
});
|
|
614
655
|
|
|
615
656
|
// src/lib/agent-config.ts
|
|
616
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as
|
|
657
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
617
658
|
import path5 from "path";
|
|
618
659
|
function loadAgentConfig() {
|
|
619
|
-
if (!
|
|
660
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
620
661
|
try {
|
|
621
662
|
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
622
663
|
} catch {
|
|
@@ -637,6 +678,7 @@ var init_agent_config = __esm({
|
|
|
637
678
|
"use strict";
|
|
638
679
|
init_config();
|
|
639
680
|
init_runtime_table();
|
|
681
|
+
init_secure_files();
|
|
640
682
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
641
683
|
DEFAULT_MODELS = {
|
|
642
684
|
claude: "claude-opus-4",
|
|
@@ -655,16 +697,16 @@ __export(intercom_queue_exports, {
|
|
|
655
697
|
queueIntercom: () => queueIntercom,
|
|
656
698
|
readQueue: () => readQueue
|
|
657
699
|
});
|
|
658
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as
|
|
700
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
659
701
|
import path6 from "path";
|
|
660
702
|
import os5 from "os";
|
|
661
703
|
function ensureDir() {
|
|
662
704
|
const dir = path6.dirname(QUEUE_PATH);
|
|
663
|
-
if (!
|
|
705
|
+
if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
|
|
664
706
|
}
|
|
665
707
|
function readQueue() {
|
|
666
708
|
try {
|
|
667
|
-
if (!
|
|
709
|
+
if (!existsSync6(QUEUE_PATH)) return [];
|
|
668
710
|
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
669
711
|
} catch {
|
|
670
712
|
return [];
|
|
@@ -827,13 +869,634 @@ var init_db_retry = __esm({
|
|
|
827
869
|
}
|
|
828
870
|
});
|
|
829
871
|
|
|
872
|
+
// src/lib/database-adapter.ts
|
|
873
|
+
import os6 from "os";
|
|
874
|
+
import path7 from "path";
|
|
875
|
+
import { createRequire } from "module";
|
|
876
|
+
import { pathToFileURL } from "url";
|
|
877
|
+
function quotedIdentifier(identifier) {
|
|
878
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
879
|
+
}
|
|
880
|
+
function unqualifiedTableName(name) {
|
|
881
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
882
|
+
const parts = raw.split(".");
|
|
883
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
884
|
+
}
|
|
885
|
+
function stripTrailingSemicolon(sql) {
|
|
886
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
887
|
+
}
|
|
888
|
+
function appendClause(sql, clause) {
|
|
889
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
890
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
891
|
+
if (!returningMatch) {
|
|
892
|
+
return `${trimmed}${clause}`;
|
|
893
|
+
}
|
|
894
|
+
const idx = returningMatch.index;
|
|
895
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
896
|
+
}
|
|
897
|
+
function normalizeStatement(stmt) {
|
|
898
|
+
if (typeof stmt === "string") {
|
|
899
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
900
|
+
}
|
|
901
|
+
const sql = stmt.sql;
|
|
902
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
903
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
904
|
+
}
|
|
905
|
+
return { kind: "named", sql, args: stmt.args };
|
|
906
|
+
}
|
|
907
|
+
function rewriteBooleanLiterals(sql) {
|
|
908
|
+
let out = sql;
|
|
909
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
910
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
911
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
912
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
913
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
914
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
915
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
916
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
917
|
+
}
|
|
918
|
+
return out;
|
|
919
|
+
}
|
|
920
|
+
function rewriteInsertOrIgnore(sql) {
|
|
921
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
922
|
+
return sql;
|
|
923
|
+
}
|
|
924
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
925
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
926
|
+
}
|
|
927
|
+
function rewriteInsertOrReplace(sql) {
|
|
928
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
929
|
+
if (!match) {
|
|
930
|
+
return sql;
|
|
931
|
+
}
|
|
932
|
+
const rawTable = match[1];
|
|
933
|
+
const rawColumns = match[2];
|
|
934
|
+
const remainder = match[3];
|
|
935
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
936
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
937
|
+
if (!conflictKeys?.length) {
|
|
938
|
+
return sql;
|
|
939
|
+
}
|
|
940
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
941
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
942
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
943
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
944
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
945
|
+
}
|
|
946
|
+
function rewriteSql(sql) {
|
|
947
|
+
let out = sql;
|
|
948
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
949
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
950
|
+
out = rewriteBooleanLiterals(out);
|
|
951
|
+
out = rewriteInsertOrReplace(out);
|
|
952
|
+
out = rewriteInsertOrIgnore(out);
|
|
953
|
+
return stripTrailingSemicolon(out);
|
|
954
|
+
}
|
|
955
|
+
function toBoolean(value) {
|
|
956
|
+
if (value === null || value === void 0) return value;
|
|
957
|
+
if (typeof value === "boolean") return value;
|
|
958
|
+
if (typeof value === "number") return value !== 0;
|
|
959
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
960
|
+
if (typeof value === "string") {
|
|
961
|
+
const normalized = value.trim().toLowerCase();
|
|
962
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
963
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
964
|
+
}
|
|
965
|
+
return Boolean(value);
|
|
966
|
+
}
|
|
967
|
+
function countQuestionMarks(sql, end) {
|
|
968
|
+
let count = 0;
|
|
969
|
+
let inSingle = false;
|
|
970
|
+
let inDouble = false;
|
|
971
|
+
let inLineComment = false;
|
|
972
|
+
let inBlockComment = false;
|
|
973
|
+
for (let i = 0; i < end; i++) {
|
|
974
|
+
const ch = sql[i];
|
|
975
|
+
const next = sql[i + 1];
|
|
976
|
+
if (inLineComment) {
|
|
977
|
+
if (ch === "\n") inLineComment = false;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (inBlockComment) {
|
|
981
|
+
if (ch === "*" && next === "/") {
|
|
982
|
+
inBlockComment = false;
|
|
983
|
+
i += 1;
|
|
984
|
+
}
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
988
|
+
inLineComment = true;
|
|
989
|
+
i += 1;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
993
|
+
inBlockComment = true;
|
|
994
|
+
i += 1;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
998
|
+
inSingle = !inSingle;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1002
|
+
inDouble = !inDouble;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1006
|
+
count += 1;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return count;
|
|
1010
|
+
}
|
|
1011
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1012
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1013
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1014
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1015
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1016
|
+
const matchText = match[0];
|
|
1017
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1018
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return indexes;
|
|
1022
|
+
}
|
|
1023
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1024
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1025
|
+
if (!match) return;
|
|
1026
|
+
const rawTable = match[1];
|
|
1027
|
+
const rawColumns = match[2];
|
|
1028
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1029
|
+
if (!boolColumns?.size) return;
|
|
1030
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1031
|
+
for (const [index, column] of columns.entries()) {
|
|
1032
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1033
|
+
args[index] = toBoolean(args[index]);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1038
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1039
|
+
if (!match) return;
|
|
1040
|
+
const rawTable = match[1];
|
|
1041
|
+
const setClause = match[2];
|
|
1042
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1043
|
+
if (!boolColumns?.size) return;
|
|
1044
|
+
const assignments = setClause.split(",");
|
|
1045
|
+
let placeholderIndex = 0;
|
|
1046
|
+
for (const assignment of assignments) {
|
|
1047
|
+
if (!assignment.includes("?")) continue;
|
|
1048
|
+
placeholderIndex += 1;
|
|
1049
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1050
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1051
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function coerceBooleanArgs(sql, args) {
|
|
1056
|
+
const nextArgs = [...args];
|
|
1057
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1058
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1059
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1060
|
+
for (const index of placeholderIndexes) {
|
|
1061
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1062
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return nextArgs;
|
|
1066
|
+
}
|
|
1067
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1068
|
+
let out = "";
|
|
1069
|
+
let placeholder = 0;
|
|
1070
|
+
let inSingle = false;
|
|
1071
|
+
let inDouble = false;
|
|
1072
|
+
let inLineComment = false;
|
|
1073
|
+
let inBlockComment = false;
|
|
1074
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1075
|
+
const ch = sql[i];
|
|
1076
|
+
const next = sql[i + 1];
|
|
1077
|
+
if (inLineComment) {
|
|
1078
|
+
out += ch;
|
|
1079
|
+
if (ch === "\n") inLineComment = false;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (inBlockComment) {
|
|
1083
|
+
out += ch;
|
|
1084
|
+
if (ch === "*" && next === "/") {
|
|
1085
|
+
out += next;
|
|
1086
|
+
inBlockComment = false;
|
|
1087
|
+
i += 1;
|
|
1088
|
+
}
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1092
|
+
out += ch + next;
|
|
1093
|
+
inLineComment = true;
|
|
1094
|
+
i += 1;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1098
|
+
out += ch + next;
|
|
1099
|
+
inBlockComment = true;
|
|
1100
|
+
i += 1;
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1104
|
+
inSingle = !inSingle;
|
|
1105
|
+
out += ch;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1109
|
+
inDouble = !inDouble;
|
|
1110
|
+
out += ch;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1114
|
+
placeholder += 1;
|
|
1115
|
+
out += `$${placeholder}`;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
out += ch;
|
|
1119
|
+
}
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1122
|
+
function translateStatementForPostgres(stmt) {
|
|
1123
|
+
const normalized = normalizeStatement(stmt);
|
|
1124
|
+
if (normalized.kind === "named") {
|
|
1125
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1126
|
+
}
|
|
1127
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1128
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1129
|
+
return {
|
|
1130
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1131
|
+
args: coercedArgs
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function shouldBypassPostgres(stmt) {
|
|
1135
|
+
const normalized = normalizeStatement(stmt);
|
|
1136
|
+
if (normalized.kind === "named") {
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1140
|
+
}
|
|
1141
|
+
function shouldFallbackOnError(error) {
|
|
1142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1143
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1144
|
+
}
|
|
1145
|
+
function isReadQuery(sql) {
|
|
1146
|
+
const trimmed = sql.trimStart();
|
|
1147
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1148
|
+
}
|
|
1149
|
+
function buildRow(row, columns) {
|
|
1150
|
+
const values = columns.map((column) => row[column]);
|
|
1151
|
+
return Object.assign(values, row);
|
|
1152
|
+
}
|
|
1153
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1154
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1155
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1156
|
+
return {
|
|
1157
|
+
columns,
|
|
1158
|
+
columnTypes: columns.map(() => ""),
|
|
1159
|
+
rows: resultRows,
|
|
1160
|
+
rowsAffected,
|
|
1161
|
+
lastInsertRowid: void 0,
|
|
1162
|
+
toJSON() {
|
|
1163
|
+
return {
|
|
1164
|
+
columns,
|
|
1165
|
+
columnTypes: columns.map(() => ""),
|
|
1166
|
+
rows,
|
|
1167
|
+
rowsAffected,
|
|
1168
|
+
lastInsertRowid: void 0
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
async function loadPrismaClient() {
|
|
1174
|
+
if (!prismaClientPromise) {
|
|
1175
|
+
prismaClientPromise = (async () => {
|
|
1176
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1177
|
+
if (explicitPath) {
|
|
1178
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1179
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1180
|
+
if (!PrismaClient2) {
|
|
1181
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1182
|
+
}
|
|
1183
|
+
return new PrismaClient2();
|
|
1184
|
+
}
|
|
1185
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1186
|
+
const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
|
|
1187
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1188
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1189
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1190
|
+
if (!PrismaClient) {
|
|
1191
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1192
|
+
}
|
|
1193
|
+
return new PrismaClient();
|
|
1194
|
+
})();
|
|
1195
|
+
}
|
|
1196
|
+
return prismaClientPromise;
|
|
1197
|
+
}
|
|
1198
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1199
|
+
if (!compatibilityBootstrapPromise) {
|
|
1200
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1201
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1202
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1203
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1204
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1205
|
+
relation
|
|
1206
|
+
);
|
|
1207
|
+
if (!rows[0]?.regclass) {
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
await prisma.$executeRawUnsafe(
|
|
1211
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
})();
|
|
1215
|
+
}
|
|
1216
|
+
return compatibilityBootstrapPromise;
|
|
1217
|
+
}
|
|
1218
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1219
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1220
|
+
if (isReadQuery(translated.sql)) {
|
|
1221
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1222
|
+
translated.sql,
|
|
1223
|
+
...translated.args
|
|
1224
|
+
);
|
|
1225
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1226
|
+
}
|
|
1227
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1228
|
+
return buildResultSet([], rowsAffected);
|
|
1229
|
+
}
|
|
1230
|
+
function splitSqlStatements(sql) {
|
|
1231
|
+
const parts = [];
|
|
1232
|
+
let current = "";
|
|
1233
|
+
let inSingle = false;
|
|
1234
|
+
let inDouble = false;
|
|
1235
|
+
let inLineComment = false;
|
|
1236
|
+
let inBlockComment = false;
|
|
1237
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1238
|
+
const ch = sql[i];
|
|
1239
|
+
const next = sql[i + 1];
|
|
1240
|
+
if (inLineComment) {
|
|
1241
|
+
current += ch;
|
|
1242
|
+
if (ch === "\n") inLineComment = false;
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
if (inBlockComment) {
|
|
1246
|
+
current += ch;
|
|
1247
|
+
if (ch === "*" && next === "/") {
|
|
1248
|
+
current += next;
|
|
1249
|
+
inBlockComment = false;
|
|
1250
|
+
i += 1;
|
|
1251
|
+
}
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1255
|
+
current += ch + next;
|
|
1256
|
+
inLineComment = true;
|
|
1257
|
+
i += 1;
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1261
|
+
current += ch + next;
|
|
1262
|
+
inBlockComment = true;
|
|
1263
|
+
i += 1;
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1267
|
+
inSingle = !inSingle;
|
|
1268
|
+
current += ch;
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1272
|
+
inDouble = !inDouble;
|
|
1273
|
+
current += ch;
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1277
|
+
if (current.trim()) {
|
|
1278
|
+
parts.push(current.trim());
|
|
1279
|
+
}
|
|
1280
|
+
current = "";
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
current += ch;
|
|
1284
|
+
}
|
|
1285
|
+
if (current.trim()) {
|
|
1286
|
+
parts.push(current.trim());
|
|
1287
|
+
}
|
|
1288
|
+
return parts;
|
|
1289
|
+
}
|
|
1290
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1291
|
+
const prisma = await loadPrismaClient();
|
|
1292
|
+
await ensureCompatibilityViews(prisma);
|
|
1293
|
+
let closed = false;
|
|
1294
|
+
let adapter;
|
|
1295
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1296
|
+
if (!fallbackClient) {
|
|
1297
|
+
if (error) throw error;
|
|
1298
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1299
|
+
}
|
|
1300
|
+
if (error) {
|
|
1301
|
+
process.stderr.write(
|
|
1302
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1303
|
+
`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
return fallbackClient.execute(stmt);
|
|
1307
|
+
};
|
|
1308
|
+
adapter = {
|
|
1309
|
+
async execute(stmt) {
|
|
1310
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1311
|
+
return fallbackExecute(stmt);
|
|
1312
|
+
}
|
|
1313
|
+
try {
|
|
1314
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
if (shouldFallbackOnError(error)) {
|
|
1317
|
+
return fallbackExecute(stmt, error);
|
|
1318
|
+
}
|
|
1319
|
+
throw error;
|
|
1320
|
+
}
|
|
1321
|
+
},
|
|
1322
|
+
async batch(stmts, mode) {
|
|
1323
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1324
|
+
if (!fallbackClient) {
|
|
1325
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1326
|
+
}
|
|
1327
|
+
return fallbackClient.batch(stmts, mode);
|
|
1328
|
+
}
|
|
1329
|
+
try {
|
|
1330
|
+
if (prisma.$transaction) {
|
|
1331
|
+
return await prisma.$transaction(async (tx) => {
|
|
1332
|
+
const results2 = [];
|
|
1333
|
+
for (const stmt of stmts) {
|
|
1334
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1335
|
+
}
|
|
1336
|
+
return results2;
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
const results = [];
|
|
1340
|
+
for (const stmt of stmts) {
|
|
1341
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1342
|
+
}
|
|
1343
|
+
return results;
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1346
|
+
process.stderr.write(
|
|
1347
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1348
|
+
`
|
|
1349
|
+
);
|
|
1350
|
+
return fallbackClient.batch(stmts, mode);
|
|
1351
|
+
}
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
async migrate(stmts) {
|
|
1356
|
+
if (fallbackClient) {
|
|
1357
|
+
return fallbackClient.migrate(stmts);
|
|
1358
|
+
}
|
|
1359
|
+
return adapter.batch(stmts, "deferred");
|
|
1360
|
+
},
|
|
1361
|
+
async transaction(mode) {
|
|
1362
|
+
if (!fallbackClient) {
|
|
1363
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1364
|
+
}
|
|
1365
|
+
return fallbackClient.transaction(mode);
|
|
1366
|
+
},
|
|
1367
|
+
async executeMultiple(sql) {
|
|
1368
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1369
|
+
return fallbackClient.executeMultiple(sql);
|
|
1370
|
+
}
|
|
1371
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1372
|
+
await adapter.execute(statement);
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
async sync() {
|
|
1376
|
+
if (fallbackClient) {
|
|
1377
|
+
return fallbackClient.sync();
|
|
1378
|
+
}
|
|
1379
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1380
|
+
},
|
|
1381
|
+
close() {
|
|
1382
|
+
closed = true;
|
|
1383
|
+
prismaClientPromise = null;
|
|
1384
|
+
compatibilityBootstrapPromise = null;
|
|
1385
|
+
void prisma.$disconnect?.();
|
|
1386
|
+
},
|
|
1387
|
+
get closed() {
|
|
1388
|
+
return closed;
|
|
1389
|
+
},
|
|
1390
|
+
get protocol() {
|
|
1391
|
+
return "prisma-postgres";
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
return adapter;
|
|
1395
|
+
}
|
|
1396
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1397
|
+
var init_database_adapter = __esm({
|
|
1398
|
+
"src/lib/database-adapter.ts"() {
|
|
1399
|
+
"use strict";
|
|
1400
|
+
VIEW_MAPPINGS = [
|
|
1401
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1402
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1403
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1404
|
+
{ view: "entities", source: "memory.entities" },
|
|
1405
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1406
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1407
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1408
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1409
|
+
{ view: "messages", source: "memory.messages" },
|
|
1410
|
+
{ view: "users", source: "wiki.users" },
|
|
1411
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1412
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1413
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1414
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1415
|
+
];
|
|
1416
|
+
UPSERT_KEYS = {
|
|
1417
|
+
memories: ["id"],
|
|
1418
|
+
tasks: ["id"],
|
|
1419
|
+
behaviors: ["id"],
|
|
1420
|
+
entities: ["id"],
|
|
1421
|
+
relationships: ["id"],
|
|
1422
|
+
entity_aliases: ["alias"],
|
|
1423
|
+
notifications: ["id"],
|
|
1424
|
+
messages: ["id"],
|
|
1425
|
+
users: ["id"],
|
|
1426
|
+
workspaces: ["id"],
|
|
1427
|
+
workspace_users: ["id"],
|
|
1428
|
+
documents: ["id"],
|
|
1429
|
+
chats: ["id"]
|
|
1430
|
+
};
|
|
1431
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1432
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1433
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1434
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1435
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1436
|
+
};
|
|
1437
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1438
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1439
|
+
);
|
|
1440
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1441
|
+
/\bPRAGMA\b/i,
|
|
1442
|
+
/\bsqlite_master\b/i,
|
|
1443
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1444
|
+
/\bMATCH\b/i,
|
|
1445
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1446
|
+
/\bjson_extract\s*\(/i,
|
|
1447
|
+
/\bjulianday\s*\(/i,
|
|
1448
|
+
/\bstrftime\s*\(/i,
|
|
1449
|
+
/\blast_insert_rowid\s*\(/i
|
|
1450
|
+
];
|
|
1451
|
+
prismaClientPromise = null;
|
|
1452
|
+
compatibilityBootstrapPromise = null;
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
// src/lib/daemon-auth.ts
|
|
1457
|
+
import crypto from "crypto";
|
|
1458
|
+
import path8 from "path";
|
|
1459
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1460
|
+
function normalizeToken(token) {
|
|
1461
|
+
if (!token) return null;
|
|
1462
|
+
const trimmed = token.trim();
|
|
1463
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1464
|
+
}
|
|
1465
|
+
function readDaemonToken() {
|
|
1466
|
+
try {
|
|
1467
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1468
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1469
|
+
} catch {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function ensureDaemonToken(seed) {
|
|
1474
|
+
const existing = readDaemonToken();
|
|
1475
|
+
if (existing) return existing;
|
|
1476
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1477
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1478
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1479
|
+
`, "utf8");
|
|
1480
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1481
|
+
return token;
|
|
1482
|
+
}
|
|
1483
|
+
var DAEMON_TOKEN_PATH;
|
|
1484
|
+
var init_daemon_auth = __esm({
|
|
1485
|
+
"src/lib/daemon-auth.ts"() {
|
|
1486
|
+
"use strict";
|
|
1487
|
+
init_config();
|
|
1488
|
+
init_secure_files();
|
|
1489
|
+
DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
830
1493
|
// src/lib/exe-daemon-client.ts
|
|
831
1494
|
import net from "net";
|
|
832
|
-
import
|
|
1495
|
+
import os7 from "os";
|
|
833
1496
|
import { spawn } from "child_process";
|
|
834
1497
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
835
|
-
import { existsSync as
|
|
836
|
-
import
|
|
1498
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1499
|
+
import path9 from "path";
|
|
837
1500
|
import { fileURLToPath } from "url";
|
|
838
1501
|
function handleData(chunk) {
|
|
839
1502
|
_buffer += chunk.toString();
|
|
@@ -861,9 +1524,9 @@ function handleData(chunk) {
|
|
|
861
1524
|
}
|
|
862
1525
|
}
|
|
863
1526
|
function cleanupStaleFiles() {
|
|
864
|
-
if (
|
|
1527
|
+
if (existsSync8(PID_PATH)) {
|
|
865
1528
|
try {
|
|
866
|
-
const pid = parseInt(
|
|
1529
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
867
1530
|
if (pid > 0) {
|
|
868
1531
|
try {
|
|
869
1532
|
process.kill(pid, 0);
|
|
@@ -884,17 +1547,17 @@ function cleanupStaleFiles() {
|
|
|
884
1547
|
}
|
|
885
1548
|
}
|
|
886
1549
|
function findPackageRoot() {
|
|
887
|
-
let dir =
|
|
888
|
-
const { root } =
|
|
1550
|
+
let dir = path9.dirname(fileURLToPath(import.meta.url));
|
|
1551
|
+
const { root } = path9.parse(dir);
|
|
889
1552
|
while (dir !== root) {
|
|
890
|
-
if (
|
|
891
|
-
dir =
|
|
1553
|
+
if (existsSync8(path9.join(dir, "package.json"))) return dir;
|
|
1554
|
+
dir = path9.dirname(dir);
|
|
892
1555
|
}
|
|
893
1556
|
return null;
|
|
894
1557
|
}
|
|
895
1558
|
function spawnDaemon() {
|
|
896
|
-
const freeGB =
|
|
897
|
-
const totalGB =
|
|
1559
|
+
const freeGB = os7.freemem() / (1024 * 1024 * 1024);
|
|
1560
|
+
const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
|
|
898
1561
|
if (totalGB <= 8) {
|
|
899
1562
|
process.stderr.write(
|
|
900
1563
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -914,16 +1577,17 @@ function spawnDaemon() {
|
|
|
914
1577
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
915
1578
|
return;
|
|
916
1579
|
}
|
|
917
|
-
const daemonPath =
|
|
918
|
-
if (!
|
|
1580
|
+
const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1581
|
+
if (!existsSync8(daemonPath)) {
|
|
919
1582
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
920
1583
|
`);
|
|
921
1584
|
return;
|
|
922
1585
|
}
|
|
923
1586
|
const resolvedPath = daemonPath;
|
|
1587
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
924
1588
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
925
1589
|
`);
|
|
926
|
-
const logPath =
|
|
1590
|
+
const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
|
|
927
1591
|
let stderrFd = "ignore";
|
|
928
1592
|
try {
|
|
929
1593
|
stderrFd = openSync(logPath, "a");
|
|
@@ -941,7 +1605,8 @@ function spawnDaemon() {
|
|
|
941
1605
|
TMUX_PANE: void 0,
|
|
942
1606
|
// Prevents resolveExeSession() from scoping to one session
|
|
943
1607
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
944
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1608
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1609
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
945
1610
|
}
|
|
946
1611
|
});
|
|
947
1612
|
child.unref();
|
|
@@ -1048,13 +1713,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1048
1713
|
return;
|
|
1049
1714
|
}
|
|
1050
1715
|
const id = randomUUID2();
|
|
1716
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1051
1717
|
const timer = setTimeout(() => {
|
|
1052
1718
|
_pending.delete(id);
|
|
1053
1719
|
resolve({ error: "Request timeout" });
|
|
1054
1720
|
}, timeoutMs);
|
|
1055
1721
|
_pending.set(id, { resolve, timer });
|
|
1056
1722
|
try {
|
|
1057
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1723
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1058
1724
|
} catch {
|
|
1059
1725
|
clearTimeout(timer);
|
|
1060
1726
|
_pending.delete(id);
|
|
@@ -1065,17 +1731,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1065
1731
|
function isClientConnected() {
|
|
1066
1732
|
return _connected;
|
|
1067
1733
|
}
|
|
1068
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1734
|
+
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;
|
|
1069
1735
|
var init_exe_daemon_client = __esm({
|
|
1070
1736
|
"src/lib/exe-daemon-client.ts"() {
|
|
1071
1737
|
"use strict";
|
|
1072
1738
|
init_config();
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1739
|
+
init_daemon_auth();
|
|
1740
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
|
|
1741
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
|
|
1742
|
+
SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1076
1743
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1077
1744
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1078
1745
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1746
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1079
1747
|
_socket = null;
|
|
1080
1748
|
_connected = false;
|
|
1081
1749
|
_buffer = "";
|
|
@@ -1154,7 +1822,7 @@ __export(db_daemon_client_exports, {
|
|
|
1154
1822
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1155
1823
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1156
1824
|
});
|
|
1157
|
-
function
|
|
1825
|
+
function normalizeStatement2(stmt) {
|
|
1158
1826
|
if (typeof stmt === "string") {
|
|
1159
1827
|
return { sql: stmt, args: [] };
|
|
1160
1828
|
}
|
|
@@ -1178,7 +1846,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1178
1846
|
if (!_useDaemon || !isClientConnected()) {
|
|
1179
1847
|
return fallbackClient.execute(stmt);
|
|
1180
1848
|
}
|
|
1181
|
-
const { sql, args } =
|
|
1849
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1182
1850
|
const response = await sendDaemonRequest({
|
|
1183
1851
|
type: "db-execute",
|
|
1184
1852
|
sql,
|
|
@@ -1203,7 +1871,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1203
1871
|
if (!_useDaemon || !isClientConnected()) {
|
|
1204
1872
|
return fallbackClient.batch(stmts, mode);
|
|
1205
1873
|
}
|
|
1206
|
-
const statements = stmts.map(
|
|
1874
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1207
1875
|
const response = await sendDaemonRequest({
|
|
1208
1876
|
type: "db-batch",
|
|
1209
1877
|
statements,
|
|
@@ -1298,6 +1966,18 @@ __export(database_exports, {
|
|
|
1298
1966
|
});
|
|
1299
1967
|
import { createClient } from "@libsql/client";
|
|
1300
1968
|
async function initDatabase(config) {
|
|
1969
|
+
if (_walCheckpointTimer) {
|
|
1970
|
+
clearInterval(_walCheckpointTimer);
|
|
1971
|
+
_walCheckpointTimer = null;
|
|
1972
|
+
}
|
|
1973
|
+
if (_daemonClient) {
|
|
1974
|
+
_daemonClient.close();
|
|
1975
|
+
_daemonClient = null;
|
|
1976
|
+
}
|
|
1977
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1978
|
+
_adapterClient.close();
|
|
1979
|
+
}
|
|
1980
|
+
_adapterClient = null;
|
|
1301
1981
|
if (_client) {
|
|
1302
1982
|
_client.close();
|
|
1303
1983
|
_client = null;
|
|
@@ -1311,6 +1991,7 @@ async function initDatabase(config) {
|
|
|
1311
1991
|
}
|
|
1312
1992
|
_client = createClient(opts);
|
|
1313
1993
|
_resilientClient = wrapWithRetry(_client);
|
|
1994
|
+
_adapterClient = _resilientClient;
|
|
1314
1995
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1315
1996
|
});
|
|
1316
1997
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1321,14 +2002,20 @@ async function initDatabase(config) {
|
|
|
1321
2002
|
});
|
|
1322
2003
|
}, 3e4);
|
|
1323
2004
|
_walCheckpointTimer.unref();
|
|
2005
|
+
if (process.env.DATABASE_URL) {
|
|
2006
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2007
|
+
}
|
|
1324
2008
|
}
|
|
1325
2009
|
function isInitialized() {
|
|
1326
|
-
return _client !== null;
|
|
2010
|
+
return _adapterClient !== null || _client !== null;
|
|
1327
2011
|
}
|
|
1328
2012
|
function getClient() {
|
|
1329
|
-
if (!
|
|
2013
|
+
if (!_adapterClient) {
|
|
1330
2014
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1331
2015
|
}
|
|
2016
|
+
if (process.env.DATABASE_URL) {
|
|
2017
|
+
return _adapterClient;
|
|
2018
|
+
}
|
|
1332
2019
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1333
2020
|
return _resilientClient;
|
|
1334
2021
|
}
|
|
@@ -1338,6 +2025,7 @@ function getClient() {
|
|
|
1338
2025
|
return _resilientClient;
|
|
1339
2026
|
}
|
|
1340
2027
|
async function initDaemonClient() {
|
|
2028
|
+
if (process.env.DATABASE_URL) return;
|
|
1341
2029
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1342
2030
|
if (!_resilientClient) return;
|
|
1343
2031
|
try {
|
|
@@ -1634,6 +2322,7 @@ async function ensureSchema() {
|
|
|
1634
2322
|
project TEXT NOT NULL,
|
|
1635
2323
|
summary TEXT NOT NULL,
|
|
1636
2324
|
task_file TEXT,
|
|
2325
|
+
session_scope TEXT,
|
|
1637
2326
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1638
2327
|
created_at TEXT NOT NULL
|
|
1639
2328
|
);
|
|
@@ -1642,7 +2331,7 @@ async function ensureSchema() {
|
|
|
1642
2331
|
ON notifications(read);
|
|
1643
2332
|
|
|
1644
2333
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1645
|
-
ON notifications(agent_id);
|
|
2334
|
+
ON notifications(agent_id, session_scope);
|
|
1646
2335
|
|
|
1647
2336
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1648
2337
|
ON notifications(task_file);
|
|
@@ -1680,6 +2369,7 @@ async function ensureSchema() {
|
|
|
1680
2369
|
target_agent TEXT NOT NULL,
|
|
1681
2370
|
target_project TEXT,
|
|
1682
2371
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2372
|
+
session_scope TEXT,
|
|
1683
2373
|
content TEXT NOT NULL,
|
|
1684
2374
|
priority TEXT DEFAULT 'normal',
|
|
1685
2375
|
status TEXT DEFAULT 'pending',
|
|
@@ -1693,10 +2383,31 @@ async function ensureSchema() {
|
|
|
1693
2383
|
);
|
|
1694
2384
|
|
|
1695
2385
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1696
|
-
ON messages(target_agent, status);
|
|
2386
|
+
ON messages(target_agent, session_scope, status);
|
|
1697
2387
|
|
|
1698
2388
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1699
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2389
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2390
|
+
`);
|
|
2391
|
+
try {
|
|
2392
|
+
await client.execute({
|
|
2393
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2394
|
+
args: []
|
|
2395
|
+
});
|
|
2396
|
+
} catch {
|
|
2397
|
+
}
|
|
2398
|
+
try {
|
|
2399
|
+
await client.execute({
|
|
2400
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2401
|
+
args: []
|
|
2402
|
+
});
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
await client.executeMultiple(`
|
|
2406
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2407
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2408
|
+
|
|
2409
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2410
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1700
2411
|
`);
|
|
1701
2412
|
try {
|
|
1702
2413
|
await client.execute({
|
|
@@ -2280,46 +2991,66 @@ async function ensureSchema() {
|
|
|
2280
2991
|
} catch {
|
|
2281
2992
|
}
|
|
2282
2993
|
}
|
|
2994
|
+
try {
|
|
2995
|
+
await client.execute({
|
|
2996
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2997
|
+
args: []
|
|
2998
|
+
});
|
|
2999
|
+
} catch {
|
|
3000
|
+
}
|
|
2283
3001
|
}
|
|
2284
3002
|
async function disposeDatabase() {
|
|
3003
|
+
if (_walCheckpointTimer) {
|
|
3004
|
+
clearInterval(_walCheckpointTimer);
|
|
3005
|
+
_walCheckpointTimer = null;
|
|
3006
|
+
}
|
|
2285
3007
|
if (_daemonClient) {
|
|
2286
3008
|
_daemonClient.close();
|
|
2287
3009
|
_daemonClient = null;
|
|
2288
3010
|
}
|
|
3011
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3012
|
+
_adapterClient.close();
|
|
3013
|
+
}
|
|
3014
|
+
_adapterClient = null;
|
|
2289
3015
|
if (_client) {
|
|
2290
3016
|
_client.close();
|
|
2291
3017
|
_client = null;
|
|
2292
3018
|
_resilientClient = null;
|
|
2293
3019
|
}
|
|
2294
3020
|
}
|
|
2295
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3021
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2296
3022
|
var init_database = __esm({
|
|
2297
3023
|
"src/lib/database.ts"() {
|
|
2298
3024
|
"use strict";
|
|
2299
3025
|
init_db_retry();
|
|
2300
3026
|
init_employees();
|
|
3027
|
+
init_database_adapter();
|
|
2301
3028
|
_client = null;
|
|
2302
3029
|
_resilientClient = null;
|
|
2303
3030
|
_walCheckpointTimer = null;
|
|
2304
3031
|
_daemonClient = null;
|
|
3032
|
+
_adapterClient = null;
|
|
2305
3033
|
initTurso = initDatabase;
|
|
2306
3034
|
disposeTurso = disposeDatabase;
|
|
2307
3035
|
}
|
|
2308
3036
|
});
|
|
2309
3037
|
|
|
2310
3038
|
// src/lib/license.ts
|
|
2311
|
-
import { readFileSync as
|
|
3039
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2312
3040
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2313
|
-
import
|
|
3041
|
+
import { createRequire as createRequire2 } from "module";
|
|
3042
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3043
|
+
import os8 from "os";
|
|
3044
|
+
import path10 from "path";
|
|
2314
3045
|
import { jwtVerify, importSPKI } from "jose";
|
|
2315
3046
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2316
3047
|
var init_license = __esm({
|
|
2317
3048
|
"src/lib/license.ts"() {
|
|
2318
3049
|
"use strict";
|
|
2319
3050
|
init_config();
|
|
2320
|
-
LICENSE_PATH =
|
|
2321
|
-
CACHE_PATH =
|
|
2322
|
-
DEVICE_ID_PATH =
|
|
3051
|
+
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3052
|
+
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3053
|
+
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
2323
3054
|
PLAN_LIMITS = {
|
|
2324
3055
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2325
3056
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2331,12 +3062,12 @@ var init_license = __esm({
|
|
|
2331
3062
|
});
|
|
2332
3063
|
|
|
2333
3064
|
// src/lib/plan-limits.ts
|
|
2334
|
-
import { readFileSync as
|
|
2335
|
-
import
|
|
3065
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3066
|
+
import path11 from "path";
|
|
2336
3067
|
function getLicenseSync() {
|
|
2337
3068
|
try {
|
|
2338
|
-
if (!
|
|
2339
|
-
const raw = JSON.parse(
|
|
3069
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3070
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2340
3071
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2341
3072
|
const parts = raw.token.split(".");
|
|
2342
3073
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2374,8 +3105,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2374
3105
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2375
3106
|
let count = 0;
|
|
2376
3107
|
try {
|
|
2377
|
-
if (
|
|
2378
|
-
const raw =
|
|
3108
|
+
if (existsSync10(filePath)) {
|
|
3109
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2379
3110
|
const employees = JSON.parse(raw);
|
|
2380
3111
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2381
3112
|
}
|
|
@@ -2404,29 +3135,69 @@ var init_plan_limits = __esm({
|
|
|
2404
3135
|
this.name = "PlanLimitError";
|
|
2405
3136
|
}
|
|
2406
3137
|
};
|
|
2407
|
-
CACHE_PATH2 =
|
|
3138
|
+
CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
3139
|
+
}
|
|
3140
|
+
});
|
|
3141
|
+
|
|
3142
|
+
// src/lib/task-scope.ts
|
|
3143
|
+
var task_scope_exports = {};
|
|
3144
|
+
__export(task_scope_exports, {
|
|
3145
|
+
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
3146
|
+
sessionScopeFilter: () => sessionScopeFilter,
|
|
3147
|
+
strictSessionScopeFilter: () => strictSessionScopeFilter
|
|
3148
|
+
});
|
|
3149
|
+
function getCurrentSessionScope() {
|
|
3150
|
+
try {
|
|
3151
|
+
return resolveExeSession();
|
|
3152
|
+
} catch {
|
|
3153
|
+
return null;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
3157
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3158
|
+
if (!scope) return { sql: "", args: [] };
|
|
3159
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3160
|
+
return {
|
|
3161
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
3162
|
+
args: [scope]
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
3166
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
3167
|
+
if (!scope) return { sql: "", args: [] };
|
|
3168
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
3169
|
+
return {
|
|
3170
|
+
sql: ` AND ${col} = ?`,
|
|
3171
|
+
args: [scope]
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
var init_task_scope = __esm({
|
|
3175
|
+
"src/lib/task-scope.ts"() {
|
|
3176
|
+
"use strict";
|
|
3177
|
+
init_tmux_routing();
|
|
2408
3178
|
}
|
|
2409
3179
|
});
|
|
2410
3180
|
|
|
2411
3181
|
// src/lib/notifications.ts
|
|
2412
|
-
import
|
|
2413
|
-
import
|
|
2414
|
-
import
|
|
3182
|
+
import crypto2 from "crypto";
|
|
3183
|
+
import path12 from "path";
|
|
3184
|
+
import os9 from "os";
|
|
2415
3185
|
import {
|
|
2416
|
-
readFileSync as
|
|
3186
|
+
readFileSync as readFileSync10,
|
|
2417
3187
|
readdirSync,
|
|
2418
3188
|
unlinkSync as unlinkSync3,
|
|
2419
|
-
existsSync as
|
|
3189
|
+
existsSync as existsSync11,
|
|
2420
3190
|
rmdirSync
|
|
2421
3191
|
} from "fs";
|
|
2422
3192
|
async function writeNotification(notification) {
|
|
2423
3193
|
try {
|
|
2424
3194
|
const client = getClient();
|
|
2425
|
-
const id =
|
|
3195
|
+
const id = crypto2.randomUUID();
|
|
2426
3196
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3197
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2427
3198
|
await client.execute({
|
|
2428
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2429
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3199
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3200
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2430
3201
|
args: [
|
|
2431
3202
|
id,
|
|
2432
3203
|
notification.agentId,
|
|
@@ -2435,6 +3206,7 @@ async function writeNotification(notification) {
|
|
|
2435
3206
|
notification.project,
|
|
2436
3207
|
notification.summary,
|
|
2437
3208
|
notification.taskFile ?? null,
|
|
3209
|
+
sessionScope,
|
|
2438
3210
|
now
|
|
2439
3211
|
]
|
|
2440
3212
|
});
|
|
@@ -2443,12 +3215,14 @@ async function writeNotification(notification) {
|
|
|
2443
3215
|
`);
|
|
2444
3216
|
}
|
|
2445
3217
|
}
|
|
2446
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3218
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2447
3219
|
try {
|
|
2448
3220
|
const client = getClient();
|
|
3221
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2449
3222
|
await client.execute({
|
|
2450
|
-
sql:
|
|
2451
|
-
|
|
3223
|
+
sql: `UPDATE notifications SET read = 1
|
|
3224
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3225
|
+
args: [taskFile, ...scope.args]
|
|
2452
3226
|
});
|
|
2453
3227
|
} catch {
|
|
2454
3228
|
}
|
|
@@ -2457,11 +3231,12 @@ var init_notifications = __esm({
|
|
|
2457
3231
|
"src/lib/notifications.ts"() {
|
|
2458
3232
|
"use strict";
|
|
2459
3233
|
init_database();
|
|
3234
|
+
init_task_scope();
|
|
2460
3235
|
}
|
|
2461
3236
|
});
|
|
2462
3237
|
|
|
2463
3238
|
// src/lib/session-kill-telemetry.ts
|
|
2464
|
-
import
|
|
3239
|
+
import crypto3 from "crypto";
|
|
2465
3240
|
async function recordSessionKill(input) {
|
|
2466
3241
|
try {
|
|
2467
3242
|
const client = getClient();
|
|
@@ -2471,7 +3246,7 @@ async function recordSessionKill(input) {
|
|
|
2471
3246
|
ticks_idle, estimated_tokens_saved)
|
|
2472
3247
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2473
3248
|
args: [
|
|
2474
|
-
|
|
3249
|
+
crypto3.randomUUID(),
|
|
2475
3250
|
input.sessionName,
|
|
2476
3251
|
input.agentId,
|
|
2477
3252
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2494,35 +3269,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
2494
3269
|
}
|
|
2495
3270
|
});
|
|
2496
3271
|
|
|
2497
|
-
// src/lib/task-scope.ts
|
|
2498
|
-
var task_scope_exports = {};
|
|
2499
|
-
__export(task_scope_exports, {
|
|
2500
|
-
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
2501
|
-
sessionScopeFilter: () => sessionScopeFilter
|
|
2502
|
-
});
|
|
2503
|
-
function getCurrentSessionScope() {
|
|
2504
|
-
try {
|
|
2505
|
-
return resolveExeSession();
|
|
2506
|
-
} catch {
|
|
2507
|
-
return null;
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2511
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2512
|
-
if (!scope) return { sql: "", args: [] };
|
|
2513
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2514
|
-
return {
|
|
2515
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2516
|
-
args: [scope]
|
|
2517
|
-
};
|
|
2518
|
-
}
|
|
2519
|
-
var init_task_scope = __esm({
|
|
2520
|
-
"src/lib/task-scope.ts"() {
|
|
2521
|
-
"use strict";
|
|
2522
|
-
init_tmux_routing();
|
|
2523
|
-
}
|
|
2524
|
-
});
|
|
2525
|
-
|
|
2526
3272
|
// src/lib/state-bus.ts
|
|
2527
3273
|
var StateBus, orgBus;
|
|
2528
3274
|
var init_state_bus = __esm({
|
|
@@ -2579,12 +3325,12 @@ var init_state_bus = __esm({
|
|
|
2579
3325
|
});
|
|
2580
3326
|
|
|
2581
3327
|
// src/lib/tasks-crud.ts
|
|
2582
|
-
import
|
|
2583
|
-
import
|
|
2584
|
-
import
|
|
3328
|
+
import crypto4 from "crypto";
|
|
3329
|
+
import path13 from "path";
|
|
3330
|
+
import os10 from "os";
|
|
2585
3331
|
import { execSync as execSync5 } from "child_process";
|
|
2586
3332
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2587
|
-
import { existsSync as
|
|
3333
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2588
3334
|
async function writeCheckpoint(input) {
|
|
2589
3335
|
const client = getClient();
|
|
2590
3336
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2700,7 +3446,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2700
3446
|
}
|
|
2701
3447
|
async function createTaskCore(input) {
|
|
2702
3448
|
const client = getClient();
|
|
2703
|
-
const id =
|
|
3449
|
+
const id = crypto4.randomUUID();
|
|
2704
3450
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2705
3451
|
const slug = slugify(input.title);
|
|
2706
3452
|
let earlySessionScope = null;
|
|
@@ -2759,8 +3505,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2759
3505
|
}
|
|
2760
3506
|
if (input.baseDir) {
|
|
2761
3507
|
try {
|
|
2762
|
-
await mkdir3(
|
|
2763
|
-
await mkdir3(
|
|
3508
|
+
await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3509
|
+
await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2764
3510
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2765
3511
|
await ensureGitignoreExe(input.baseDir);
|
|
2766
3512
|
} catch {
|
|
@@ -2796,13 +3542,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2796
3542
|
});
|
|
2797
3543
|
if (input.baseDir) {
|
|
2798
3544
|
try {
|
|
2799
|
-
const EXE_OS_DIR =
|
|
2800
|
-
const mdPath =
|
|
2801
|
-
const mdDir =
|
|
2802
|
-
if (!
|
|
3545
|
+
const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
|
|
3546
|
+
const mdPath = path13.join(EXE_OS_DIR, taskFile);
|
|
3547
|
+
const mdDir = path13.dirname(mdPath);
|
|
3548
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2803
3549
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2804
3550
|
const mdContent = `# ${input.title}
|
|
2805
3551
|
|
|
3552
|
+
## MANDATORY: When done
|
|
3553
|
+
|
|
3554
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3555
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3556
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3557
|
+
|
|
2806
3558
|
**ID:** ${id}
|
|
2807
3559
|
**Status:** ${initialStatus}
|
|
2808
3560
|
**Priority:** ${input.priority}
|
|
@@ -2816,12 +3568,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2816
3568
|
## Context
|
|
2817
3569
|
|
|
2818
3570
|
${input.context}
|
|
2819
|
-
|
|
2820
|
-
## MANDATORY: When done
|
|
2821
|
-
|
|
2822
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2823
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2824
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2825
3571
|
`;
|
|
2826
3572
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2827
3573
|
} catch (err) {
|
|
@@ -3070,7 +3816,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3070
3816
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3071
3817
|
} catch {
|
|
3072
3818
|
}
|
|
3073
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3819
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3074
3820
|
try {
|
|
3075
3821
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3076
3822
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3099,9 +3845,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3099
3845
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3100
3846
|
}
|
|
3101
3847
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3102
|
-
const archPath =
|
|
3848
|
+
const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3103
3849
|
try {
|
|
3104
|
-
if (
|
|
3850
|
+
if (existsSync12(archPath)) return;
|
|
3105
3851
|
const template = [
|
|
3106
3852
|
`# ${projectName} \u2014 System Architecture`,
|
|
3107
3853
|
"",
|
|
@@ -3134,10 +3880,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3134
3880
|
}
|
|
3135
3881
|
}
|
|
3136
3882
|
async function ensureGitignoreExe(baseDir) {
|
|
3137
|
-
const gitignorePath =
|
|
3883
|
+
const gitignorePath = path13.join(baseDir, ".gitignore");
|
|
3138
3884
|
try {
|
|
3139
|
-
if (
|
|
3140
|
-
const content =
|
|
3885
|
+
if (existsSync12(gitignorePath)) {
|
|
3886
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3141
3887
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3142
3888
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3143
3889
|
} else {
|
|
@@ -3168,58 +3914,42 @@ var init_tasks_crud = __esm({
|
|
|
3168
3914
|
});
|
|
3169
3915
|
|
|
3170
3916
|
// src/lib/tasks-review.ts
|
|
3171
|
-
import
|
|
3172
|
-
import { existsSync as
|
|
3917
|
+
import path14 from "path";
|
|
3918
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3173
3919
|
async function countPendingReviews(sessionScope) {
|
|
3174
3920
|
const client = getClient();
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
args: [sessionScope]
|
|
3179
|
-
});
|
|
3180
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3181
|
-
}
|
|
3921
|
+
const scope = strictSessionScopeFilter(
|
|
3922
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3923
|
+
);
|
|
3182
3924
|
const result = await client.execute({
|
|
3183
|
-
sql:
|
|
3184
|
-
|
|
3925
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3926
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3927
|
+
args: [...scope.args]
|
|
3185
3928
|
});
|
|
3186
3929
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3187
3930
|
}
|
|
3188
3931
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3189
3932
|
const client = getClient();
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3194
|
-
AND session_scope = ?`,
|
|
3195
|
-
args: [sinceIso, sessionScope]
|
|
3196
|
-
});
|
|
3197
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3198
|
-
}
|
|
3933
|
+
const scope = strictSessionScopeFilter(
|
|
3934
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3935
|
+
);
|
|
3199
3936
|
const result = await client.execute({
|
|
3200
3937
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3201
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3202
|
-
args: [sinceIso]
|
|
3938
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3939
|
+
args: [sinceIso, ...scope.args]
|
|
3203
3940
|
});
|
|
3204
3941
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3205
3942
|
}
|
|
3206
3943
|
async function listPendingReviews(limit, sessionScope) {
|
|
3207
3944
|
const client = getClient();
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
WHERE status = 'needs_review'
|
|
3212
|
-
AND session_scope = ?
|
|
3213
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3214
|
-
args: [sessionScope, limit]
|
|
3215
|
-
});
|
|
3216
|
-
return result2.rows;
|
|
3217
|
-
}
|
|
3945
|
+
const scope = strictSessionScopeFilter(
|
|
3946
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3947
|
+
);
|
|
3218
3948
|
const result = await client.execute({
|
|
3219
3949
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3220
|
-
WHERE status = 'needs_review'
|
|
3950
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3221
3951
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3222
|
-
args: [limit]
|
|
3952
|
+
args: [...scope.args, limit]
|
|
3223
3953
|
});
|
|
3224
3954
|
return result.rows;
|
|
3225
3955
|
}
|
|
@@ -3231,7 +3961,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3231
3961
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3232
3962
|
AND assigned_by = 'system'
|
|
3233
3963
|
AND title LIKE 'Review:%'
|
|
3234
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3964
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3235
3965
|
args: [now]
|
|
3236
3966
|
});
|
|
3237
3967
|
const r1b = await client.execute({
|
|
@@ -3350,11 +4080,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3350
4080
|
);
|
|
3351
4081
|
}
|
|
3352
4082
|
try {
|
|
3353
|
-
const cacheDir =
|
|
3354
|
-
if (
|
|
4083
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4084
|
+
if (existsSync13(cacheDir)) {
|
|
3355
4085
|
for (const f of readdirSync2(cacheDir)) {
|
|
3356
4086
|
if (f.startsWith("review-notified-")) {
|
|
3357
|
-
unlinkSync4(
|
|
4087
|
+
unlinkSync4(path14.join(cacheDir, f));
|
|
3358
4088
|
}
|
|
3359
4089
|
}
|
|
3360
4090
|
}
|
|
@@ -3371,11 +4101,12 @@ var init_tasks_review = __esm({
|
|
|
3371
4101
|
init_tmux_routing();
|
|
3372
4102
|
init_session_key();
|
|
3373
4103
|
init_state_bus();
|
|
4104
|
+
init_task_scope();
|
|
3374
4105
|
}
|
|
3375
4106
|
});
|
|
3376
4107
|
|
|
3377
4108
|
// src/lib/tasks-chain.ts
|
|
3378
|
-
import
|
|
4109
|
+
import path15 from "path";
|
|
3379
4110
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3380
4111
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3381
4112
|
const client = getClient();
|
|
@@ -3392,7 +4123,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3392
4123
|
});
|
|
3393
4124
|
for (const ur of unblockedRows.rows) {
|
|
3394
4125
|
try {
|
|
3395
|
-
const ubFile =
|
|
4126
|
+
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
3396
4127
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3397
4128
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3398
4129
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3427,7 +4158,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3427
4158
|
const scScope = sessionScopeFilter();
|
|
3428
4159
|
const remaining = await client.execute({
|
|
3429
4160
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3430
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4161
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3431
4162
|
args: [parentTaskId, ...scScope.args]
|
|
3432
4163
|
});
|
|
3433
4164
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3461,7 +4192,7 @@ var init_tasks_chain = __esm({
|
|
|
3461
4192
|
|
|
3462
4193
|
// src/lib/project-name.ts
|
|
3463
4194
|
import { execSync as execSync6 } from "child_process";
|
|
3464
|
-
import
|
|
4195
|
+
import path16 from "path";
|
|
3465
4196
|
function getProjectName(cwd) {
|
|
3466
4197
|
const dir = cwd ?? process.cwd();
|
|
3467
4198
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3474,7 +4205,7 @@ function getProjectName(cwd) {
|
|
|
3474
4205
|
timeout: 2e3,
|
|
3475
4206
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3476
4207
|
}).trim();
|
|
3477
|
-
repoRoot =
|
|
4208
|
+
repoRoot = path16.dirname(gitCommonDir);
|
|
3478
4209
|
} catch {
|
|
3479
4210
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3480
4211
|
cwd: dir,
|
|
@@ -3483,11 +4214,11 @@ function getProjectName(cwd) {
|
|
|
3483
4214
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3484
4215
|
}).trim();
|
|
3485
4216
|
}
|
|
3486
|
-
_cached2 =
|
|
4217
|
+
_cached2 = path16.basename(repoRoot);
|
|
3487
4218
|
_cachedCwd = dir;
|
|
3488
4219
|
return _cached2;
|
|
3489
4220
|
} catch {
|
|
3490
|
-
_cached2 =
|
|
4221
|
+
_cached2 = path16.basename(dir);
|
|
3491
4222
|
_cachedCwd = dir;
|
|
3492
4223
|
return _cached2;
|
|
3493
4224
|
}
|
|
@@ -3637,10 +4368,10 @@ __export(behaviors_exports, {
|
|
|
3637
4368
|
listBehaviorsByDomain: () => listBehaviorsByDomain,
|
|
3638
4369
|
storeBehavior: () => storeBehavior
|
|
3639
4370
|
});
|
|
3640
|
-
import
|
|
4371
|
+
import crypto5 from "crypto";
|
|
3641
4372
|
async function storeBehavior(opts) {
|
|
3642
4373
|
const client = getClient();
|
|
3643
|
-
const id =
|
|
4374
|
+
const id = crypto5.randomUUID();
|
|
3644
4375
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3645
4376
|
await client.execute({
|
|
3646
4377
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -3724,7 +4455,7 @@ __export(skill_learning_exports, {
|
|
|
3724
4455
|
storeTrajectory: () => storeTrajectory,
|
|
3725
4456
|
sweepTrajectories: () => sweepTrajectories
|
|
3726
4457
|
});
|
|
3727
|
-
import
|
|
4458
|
+
import crypto6 from "crypto";
|
|
3728
4459
|
async function extractTrajectory(taskId, agentId) {
|
|
3729
4460
|
const client = getClient();
|
|
3730
4461
|
const result = await client.execute({
|
|
@@ -3753,11 +4484,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3753
4484
|
return signature;
|
|
3754
4485
|
}
|
|
3755
4486
|
function hashSignature(signature) {
|
|
3756
|
-
return
|
|
4487
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3757
4488
|
}
|
|
3758
4489
|
async function storeTrajectory(opts) {
|
|
3759
4490
|
const client = getClient();
|
|
3760
|
-
const id =
|
|
4491
|
+
const id = crypto6.randomUUID();
|
|
3761
4492
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3762
4493
|
const signatureHash = hashSignature(opts.signature);
|
|
3763
4494
|
await client.execute({
|
|
@@ -4022,8 +4753,8 @@ __export(tasks_exports, {
|
|
|
4022
4753
|
updateTaskStatus: () => updateTaskStatus,
|
|
4023
4754
|
writeCheckpoint: () => writeCheckpoint
|
|
4024
4755
|
});
|
|
4025
|
-
import
|
|
4026
|
-
import { writeFileSync as
|
|
4756
|
+
import path17 from "path";
|
|
4757
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4027
4758
|
async function createTask(input) {
|
|
4028
4759
|
const result = await createTaskCore(input);
|
|
4029
4760
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4042,12 +4773,12 @@ async function updateTask(input) {
|
|
|
4042
4773
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4043
4774
|
try {
|
|
4044
4775
|
const agent = String(row.assigned_to);
|
|
4045
|
-
const cacheDir =
|
|
4046
|
-
const cachePath =
|
|
4776
|
+
const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
|
|
4777
|
+
const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
|
|
4047
4778
|
if (input.status === "in_progress") {
|
|
4048
4779
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4049
|
-
|
|
4050
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4780
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4781
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
4051
4782
|
try {
|
|
4052
4783
|
unlinkSync5(cachePath);
|
|
4053
4784
|
} catch {
|
|
@@ -4055,10 +4786,10 @@ async function updateTask(input) {
|
|
|
4055
4786
|
}
|
|
4056
4787
|
} catch {
|
|
4057
4788
|
}
|
|
4058
|
-
if (input.status === "done") {
|
|
4789
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4059
4790
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
4060
4791
|
}
|
|
4061
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4792
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4062
4793
|
try {
|
|
4063
4794
|
const client = getClient();
|
|
4064
4795
|
const taskTitle = String(row.title);
|
|
@@ -4074,7 +4805,7 @@ async function updateTask(input) {
|
|
|
4074
4805
|
if (!isCoordinatorName(assignedAgent)) {
|
|
4075
4806
|
try {
|
|
4076
4807
|
const draftClient = getClient();
|
|
4077
|
-
if (input.status === "done") {
|
|
4808
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4078
4809
|
await draftClient.execute({
|
|
4079
4810
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4080
4811
|
args: [assignedAgent]
|
|
@@ -4091,7 +4822,7 @@ async function updateTask(input) {
|
|
|
4091
4822
|
try {
|
|
4092
4823
|
const client = getClient();
|
|
4093
4824
|
const cascaded = await client.execute({
|
|
4094
|
-
sql: `UPDATE tasks SET status = '
|
|
4825
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
4095
4826
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
4096
4827
|
args: [now, taskId]
|
|
4097
4828
|
});
|
|
@@ -4104,14 +4835,14 @@ async function updateTask(input) {
|
|
|
4104
4835
|
} catch {
|
|
4105
4836
|
}
|
|
4106
4837
|
}
|
|
4107
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4838
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4108
4839
|
if (isTerminal) {
|
|
4109
4840
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4110
4841
|
if (!isCoordinator) {
|
|
4111
4842
|
notifyTaskDone();
|
|
4112
4843
|
}
|
|
4113
4844
|
await markTaskNotificationsRead(taskFile);
|
|
4114
|
-
if (input.status === "done") {
|
|
4845
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4115
4846
|
try {
|
|
4116
4847
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4117
4848
|
} catch {
|
|
@@ -4131,7 +4862,7 @@ async function updateTask(input) {
|
|
|
4131
4862
|
}
|
|
4132
4863
|
}
|
|
4133
4864
|
}
|
|
4134
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4865
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4135
4866
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4136
4867
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4137
4868
|
taskId,
|
|
@@ -4503,6 +5234,7 @@ __export(tmux_routing_exports, {
|
|
|
4503
5234
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4504
5235
|
isExeSession: () => isExeSession,
|
|
4505
5236
|
isSessionBusy: () => isSessionBusy,
|
|
5237
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4506
5238
|
notifyParentExe: () => notifyParentExe,
|
|
4507
5239
|
parseParentExe: () => parseParentExe,
|
|
4508
5240
|
registerParentExe: () => registerParentExe,
|
|
@@ -4513,13 +5245,13 @@ __export(tmux_routing_exports, {
|
|
|
4513
5245
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4514
5246
|
});
|
|
4515
5247
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4516
|
-
import { readFileSync as
|
|
4517
|
-
import
|
|
4518
|
-
import
|
|
5248
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5249
|
+
import path18 from "path";
|
|
5250
|
+
import os11 from "os";
|
|
4519
5251
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4520
5252
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4521
5253
|
function spawnLockPath(sessionName) {
|
|
4522
|
-
return
|
|
5254
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4523
5255
|
}
|
|
4524
5256
|
function isProcessAlive(pid) {
|
|
4525
5257
|
try {
|
|
@@ -4530,13 +5262,13 @@ function isProcessAlive(pid) {
|
|
|
4530
5262
|
}
|
|
4531
5263
|
}
|
|
4532
5264
|
function acquireSpawnLock2(sessionName) {
|
|
4533
|
-
if (!
|
|
5265
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4534
5266
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4535
5267
|
}
|
|
4536
5268
|
const lockFile = spawnLockPath(sessionName);
|
|
4537
|
-
if (
|
|
5269
|
+
if (existsSync14(lockFile)) {
|
|
4538
5270
|
try {
|
|
4539
|
-
const lock = JSON.parse(
|
|
5271
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4540
5272
|
const age = Date.now() - lock.timestamp;
|
|
4541
5273
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4542
5274
|
return false;
|
|
@@ -4544,7 +5276,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4544
5276
|
} catch {
|
|
4545
5277
|
}
|
|
4546
5278
|
}
|
|
4547
|
-
|
|
5279
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4548
5280
|
return true;
|
|
4549
5281
|
}
|
|
4550
5282
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4556,13 +5288,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4556
5288
|
function resolveBehaviorsExporterScript() {
|
|
4557
5289
|
try {
|
|
4558
5290
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4559
|
-
const scriptPath =
|
|
4560
|
-
|
|
5291
|
+
const scriptPath = path18.join(
|
|
5292
|
+
path18.dirname(thisFile),
|
|
4561
5293
|
"..",
|
|
4562
5294
|
"bin",
|
|
4563
5295
|
"exe-export-behaviors.js"
|
|
4564
5296
|
);
|
|
4565
|
-
return
|
|
5297
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4566
5298
|
} catch {
|
|
4567
5299
|
return null;
|
|
4568
5300
|
}
|
|
@@ -4628,12 +5360,12 @@ function extractRootExe(name) {
|
|
|
4628
5360
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4629
5361
|
}
|
|
4630
5362
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4631
|
-
if (!
|
|
5363
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4632
5364
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4633
5365
|
}
|
|
4634
5366
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4635
|
-
const filePath =
|
|
4636
|
-
|
|
5367
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5368
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4637
5369
|
parentExe: rootExe,
|
|
4638
5370
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4639
5371
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4641,7 +5373,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4641
5373
|
}
|
|
4642
5374
|
function getParentExe(sessionKey) {
|
|
4643
5375
|
try {
|
|
4644
|
-
const data = JSON.parse(
|
|
5376
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4645
5377
|
return data.parentExe || null;
|
|
4646
5378
|
} catch {
|
|
4647
5379
|
return null;
|
|
@@ -4649,8 +5381,8 @@ function getParentExe(sessionKey) {
|
|
|
4649
5381
|
}
|
|
4650
5382
|
function getDispatchedBy(sessionKey) {
|
|
4651
5383
|
try {
|
|
4652
|
-
const data = JSON.parse(
|
|
4653
|
-
|
|
5384
|
+
const data = JSON.parse(readFileSync12(
|
|
5385
|
+
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4654
5386
|
"utf8"
|
|
4655
5387
|
));
|
|
4656
5388
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4720,8 +5452,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4720
5452
|
}
|
|
4721
5453
|
function readDebounceState() {
|
|
4722
5454
|
try {
|
|
4723
|
-
if (!
|
|
4724
|
-
const raw = JSON.parse(
|
|
5455
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5456
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
4725
5457
|
const state = {};
|
|
4726
5458
|
for (const [key, val] of Object.entries(raw)) {
|
|
4727
5459
|
if (typeof val === "number") {
|
|
@@ -4737,8 +5469,8 @@ function readDebounceState() {
|
|
|
4737
5469
|
}
|
|
4738
5470
|
function writeDebounceState(state) {
|
|
4739
5471
|
try {
|
|
4740
|
-
if (!
|
|
4741
|
-
|
|
5472
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5473
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4742
5474
|
} catch {
|
|
4743
5475
|
}
|
|
4744
5476
|
}
|
|
@@ -4836,8 +5568,8 @@ function sendIntercom(targetSession) {
|
|
|
4836
5568
|
try {
|
|
4837
5569
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4838
5570
|
const agent = baseAgentName(rawAgent);
|
|
4839
|
-
const markerPath =
|
|
4840
|
-
if (
|
|
5571
|
+
const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5572
|
+
if (existsSync14(markerPath)) {
|
|
4841
5573
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4842
5574
|
return "debounced";
|
|
4843
5575
|
}
|
|
@@ -4846,8 +5578,8 @@ function sendIntercom(targetSession) {
|
|
|
4846
5578
|
try {
|
|
4847
5579
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4848
5580
|
const agent = baseAgentName(rawAgent);
|
|
4849
|
-
const taskDir =
|
|
4850
|
-
if (
|
|
5581
|
+
const taskDir = path18.join(process.cwd(), "exe", agent);
|
|
5582
|
+
if (existsSync14(taskDir)) {
|
|
4851
5583
|
const files = readdirSync3(taskDir).filter(
|
|
4852
5584
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4853
5585
|
);
|
|
@@ -4907,6 +5639,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4907
5639
|
}
|
|
4908
5640
|
return true;
|
|
4909
5641
|
}
|
|
5642
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5643
|
+
const transport = getTransport();
|
|
5644
|
+
try {
|
|
5645
|
+
const sessions = transport.listSessions();
|
|
5646
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5647
|
+
execSync7(
|
|
5648
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5649
|
+
{ timeout: 3e3 }
|
|
5650
|
+
);
|
|
5651
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5652
|
+
return true;
|
|
5653
|
+
} catch {
|
|
5654
|
+
return false;
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
4910
5657
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4911
5658
|
if (isCoordinatorName(employeeName)) {
|
|
4912
5659
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4980,26 +5727,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4980
5727
|
const transport = getTransport();
|
|
4981
5728
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4982
5729
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4983
|
-
const logDir =
|
|
4984
|
-
const logFile =
|
|
4985
|
-
if (!
|
|
5730
|
+
const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
|
|
5731
|
+
const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5732
|
+
if (!existsSync14(logDir)) {
|
|
4986
5733
|
mkdirSync6(logDir, { recursive: true });
|
|
4987
5734
|
}
|
|
4988
5735
|
transport.kill(sessionName);
|
|
4989
5736
|
let cleanupSuffix = "";
|
|
4990
5737
|
try {
|
|
4991
5738
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4992
|
-
const cleanupScript =
|
|
4993
|
-
if (
|
|
5739
|
+
const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5740
|
+
if (existsSync14(cleanupScript)) {
|
|
4994
5741
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4995
5742
|
}
|
|
4996
5743
|
} catch {
|
|
4997
5744
|
}
|
|
4998
5745
|
try {
|
|
4999
|
-
const claudeJsonPath =
|
|
5746
|
+
const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
|
|
5000
5747
|
let claudeJson = {};
|
|
5001
5748
|
try {
|
|
5002
|
-
claudeJson = JSON.parse(
|
|
5749
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
5003
5750
|
} catch {
|
|
5004
5751
|
}
|
|
5005
5752
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5007,17 +5754,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5007
5754
|
const trustDir = opts?.cwd ?? projectDir;
|
|
5008
5755
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
5009
5756
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
5010
|
-
|
|
5757
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
5011
5758
|
} catch {
|
|
5012
5759
|
}
|
|
5013
5760
|
try {
|
|
5014
|
-
const settingsDir =
|
|
5761
|
+
const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
|
|
5015
5762
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5016
|
-
const projSettingsDir =
|
|
5017
|
-
const settingsPath =
|
|
5763
|
+
const projSettingsDir = path18.join(settingsDir, normalizedKey);
|
|
5764
|
+
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
5018
5765
|
let settings = {};
|
|
5019
5766
|
try {
|
|
5020
|
-
settings = JSON.parse(
|
|
5767
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
5021
5768
|
} catch {
|
|
5022
5769
|
}
|
|
5023
5770
|
const perms = settings.permissions ?? {};
|
|
@@ -5046,7 +5793,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5046
5793
|
perms.allow = allow;
|
|
5047
5794
|
settings.permissions = perms;
|
|
5048
5795
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5049
|
-
|
|
5796
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5050
5797
|
}
|
|
5051
5798
|
} catch {
|
|
5052
5799
|
}
|
|
@@ -5061,8 +5808,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5061
5808
|
let behaviorsFlag = "";
|
|
5062
5809
|
let legacyFallbackWarned = false;
|
|
5063
5810
|
if (!useExeAgent && !useBinSymlink) {
|
|
5064
|
-
const identityPath =
|
|
5065
|
-
|
|
5811
|
+
const identityPath = path18.join(
|
|
5812
|
+
os11.homedir(),
|
|
5066
5813
|
".exe-os",
|
|
5067
5814
|
"identity",
|
|
5068
5815
|
`${employeeName}.md`
|
|
@@ -5071,13 +5818,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5071
5818
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5072
5819
|
if (hasAgentFlag) {
|
|
5073
5820
|
identityFlag = ` --agent ${employeeName}`;
|
|
5074
|
-
} else if (
|
|
5821
|
+
} else if (existsSync14(identityPath)) {
|
|
5075
5822
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5076
5823
|
legacyFallbackWarned = true;
|
|
5077
5824
|
}
|
|
5078
5825
|
const behaviorsFile = exportBehaviorsSync(
|
|
5079
5826
|
employeeName,
|
|
5080
|
-
|
|
5827
|
+
path18.basename(spawnCwd),
|
|
5081
5828
|
sessionName
|
|
5082
5829
|
);
|
|
5083
5830
|
if (behaviorsFile) {
|
|
@@ -5092,16 +5839,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5092
5839
|
}
|
|
5093
5840
|
let sessionContextFlag = "";
|
|
5094
5841
|
try {
|
|
5095
|
-
const ctxDir =
|
|
5842
|
+
const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5096
5843
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5097
|
-
const ctxFile =
|
|
5844
|
+
const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5098
5845
|
const ctxContent = [
|
|
5099
5846
|
`## Session Context`,
|
|
5100
5847
|
`You are running in tmux session: ${sessionName}.`,
|
|
5101
5848
|
`Your parent coordinator session is ${exeSession}.`,
|
|
5102
5849
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5103
5850
|
].join("\n");
|
|
5104
|
-
|
|
5851
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5105
5852
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5106
5853
|
} catch {
|
|
5107
5854
|
}
|
|
@@ -5178,8 +5925,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5178
5925
|
transport.pipeLog(sessionName, logFile);
|
|
5179
5926
|
try {
|
|
5180
5927
|
const mySession = getMySession();
|
|
5181
|
-
const dispatchInfo =
|
|
5182
|
-
|
|
5928
|
+
const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5929
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5183
5930
|
dispatchedBy: mySession,
|
|
5184
5931
|
rootExe: exeSession,
|
|
5185
5932
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5253,15 +6000,15 @@ var init_tmux_routing = __esm({
|
|
|
5253
6000
|
init_intercom_queue();
|
|
5254
6001
|
init_plan_limits();
|
|
5255
6002
|
init_employees();
|
|
5256
|
-
SPAWN_LOCK_DIR =
|
|
5257
|
-
SESSION_CACHE =
|
|
6003
|
+
SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
6004
|
+
SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
|
|
5258
6005
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5259
6006
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5260
6007
|
VERIFY_PANE_LINES = 200;
|
|
5261
6008
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5262
6009
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5263
|
-
INTERCOM_LOG2 =
|
|
5264
|
-
DEBOUNCE_FILE =
|
|
6010
|
+
INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
6011
|
+
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5265
6012
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5266
6013
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5267
6014
|
}
|
|
@@ -5278,14 +6025,14 @@ var init_memory = __esm({
|
|
|
5278
6025
|
|
|
5279
6026
|
// src/lib/keychain.ts
|
|
5280
6027
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5281
|
-
import { existsSync as
|
|
5282
|
-
import
|
|
5283
|
-
import
|
|
6028
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6029
|
+
import path19 from "path";
|
|
6030
|
+
import os12 from "os";
|
|
5284
6031
|
function getKeyDir() {
|
|
5285
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6032
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
|
|
5286
6033
|
}
|
|
5287
6034
|
function getKeyPath() {
|
|
5288
|
-
return
|
|
6035
|
+
return path19.join(getKeyDir(), "master.key");
|
|
5289
6036
|
}
|
|
5290
6037
|
async function tryKeytar() {
|
|
5291
6038
|
try {
|
|
@@ -5306,9 +6053,9 @@ async function getMasterKey() {
|
|
|
5306
6053
|
}
|
|
5307
6054
|
}
|
|
5308
6055
|
const keyPath = getKeyPath();
|
|
5309
|
-
if (!
|
|
6056
|
+
if (!existsSync15(keyPath)) {
|
|
5310
6057
|
process.stderr.write(
|
|
5311
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
6058
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5312
6059
|
`
|
|
5313
6060
|
);
|
|
5314
6061
|
return null;
|
|
@@ -5338,6 +6085,7 @@ var shard_manager_exports = {};
|
|
|
5338
6085
|
__export(shard_manager_exports, {
|
|
5339
6086
|
disposeShards: () => disposeShards,
|
|
5340
6087
|
ensureShardSchema: () => ensureShardSchema,
|
|
6088
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5341
6089
|
getReadyShardClient: () => getReadyShardClient,
|
|
5342
6090
|
getShardClient: () => getShardClient,
|
|
5343
6091
|
getShardsDir: () => getShardsDir,
|
|
@@ -5346,15 +6094,18 @@ __export(shard_manager_exports, {
|
|
|
5346
6094
|
listShards: () => listShards,
|
|
5347
6095
|
shardExists: () => shardExists
|
|
5348
6096
|
});
|
|
5349
|
-
import
|
|
5350
|
-
import { existsSync as
|
|
6097
|
+
import path20 from "path";
|
|
6098
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5351
6099
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5352
6100
|
function initShardManager(encryptionKey) {
|
|
5353
6101
|
_encryptionKey = encryptionKey;
|
|
5354
|
-
if (!
|
|
6102
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5355
6103
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5356
6104
|
}
|
|
5357
6105
|
_shardingEnabled = true;
|
|
6106
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6107
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6108
|
+
_evictionTimer.unref();
|
|
5358
6109
|
}
|
|
5359
6110
|
function isShardingEnabled() {
|
|
5360
6111
|
return _shardingEnabled;
|
|
@@ -5371,21 +6122,28 @@ function getShardClient(projectName) {
|
|
|
5371
6122
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5372
6123
|
}
|
|
5373
6124
|
const cached = _shards.get(safeName);
|
|
5374
|
-
if (cached)
|
|
5375
|
-
|
|
6125
|
+
if (cached) {
|
|
6126
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6127
|
+
return cached;
|
|
6128
|
+
}
|
|
6129
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6130
|
+
evictLRU();
|
|
6131
|
+
}
|
|
6132
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
5376
6133
|
const client = createClient2({
|
|
5377
6134
|
url: `file:${dbPath}`,
|
|
5378
6135
|
encryptionKey: _encryptionKey
|
|
5379
6136
|
});
|
|
5380
6137
|
_shards.set(safeName, client);
|
|
6138
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5381
6139
|
return client;
|
|
5382
6140
|
}
|
|
5383
6141
|
function shardExists(projectName) {
|
|
5384
6142
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5385
|
-
return
|
|
6143
|
+
return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
5386
6144
|
}
|
|
5387
6145
|
function listShards() {
|
|
5388
|
-
if (!
|
|
6146
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5389
6147
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5390
6148
|
}
|
|
5391
6149
|
async function ensureShardSchema(client) {
|
|
@@ -5437,6 +6195,8 @@ async function ensureShardSchema(client) {
|
|
|
5437
6195
|
for (const col of [
|
|
5438
6196
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5439
6197
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6198
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6199
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5440
6200
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5441
6201
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5442
6202
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5459,7 +6219,23 @@ async function ensureShardSchema(client) {
|
|
|
5459
6219
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5460
6220
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5461
6221
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5462
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6222
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6223
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6224
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6225
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6226
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6227
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6228
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6229
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6230
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6231
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6232
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6233
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6234
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6235
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6236
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6237
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6238
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5463
6239
|
]) {
|
|
5464
6240
|
try {
|
|
5465
6241
|
await client.execute(col);
|
|
@@ -5558,21 +6334,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5558
6334
|
await ensureShardSchema(client);
|
|
5559
6335
|
return client;
|
|
5560
6336
|
}
|
|
6337
|
+
function evictLRU() {
|
|
6338
|
+
let oldest = null;
|
|
6339
|
+
let oldestTime = Infinity;
|
|
6340
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6341
|
+
if (time < oldestTime) {
|
|
6342
|
+
oldestTime = time;
|
|
6343
|
+
oldest = name;
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
if (oldest) {
|
|
6347
|
+
const client = _shards.get(oldest);
|
|
6348
|
+
if (client) {
|
|
6349
|
+
client.close();
|
|
6350
|
+
}
|
|
6351
|
+
_shards.delete(oldest);
|
|
6352
|
+
_shardLastAccess.delete(oldest);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
function evictIdleShards() {
|
|
6356
|
+
const now = Date.now();
|
|
6357
|
+
const toEvict = [];
|
|
6358
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6359
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6360
|
+
toEvict.push(name);
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
for (const name of toEvict) {
|
|
6364
|
+
const client = _shards.get(name);
|
|
6365
|
+
if (client) {
|
|
6366
|
+
client.close();
|
|
6367
|
+
}
|
|
6368
|
+
_shards.delete(name);
|
|
6369
|
+
_shardLastAccess.delete(name);
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
function getOpenShardCount() {
|
|
6373
|
+
return _shards.size;
|
|
6374
|
+
}
|
|
5561
6375
|
function disposeShards() {
|
|
6376
|
+
if (_evictionTimer) {
|
|
6377
|
+
clearInterval(_evictionTimer);
|
|
6378
|
+
_evictionTimer = null;
|
|
6379
|
+
}
|
|
5562
6380
|
for (const [, client] of _shards) {
|
|
5563
6381
|
client.close();
|
|
5564
6382
|
}
|
|
5565
6383
|
_shards.clear();
|
|
6384
|
+
_shardLastAccess.clear();
|
|
5566
6385
|
_shardingEnabled = false;
|
|
5567
6386
|
_encryptionKey = null;
|
|
5568
6387
|
}
|
|
5569
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6388
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5570
6389
|
var init_shard_manager = __esm({
|
|
5571
6390
|
"src/lib/shard-manager.ts"() {
|
|
5572
6391
|
"use strict";
|
|
5573
6392
|
init_config();
|
|
5574
|
-
SHARDS_DIR =
|
|
6393
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
6394
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6395
|
+
MAX_OPEN_SHARDS = 10;
|
|
6396
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5575
6397
|
_shards = /* @__PURE__ */ new Map();
|
|
6398
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6399
|
+
_evictionTimer = null;
|
|
5576
6400
|
_encryptionKey = null;
|
|
5577
6401
|
_shardingEnabled = false;
|
|
5578
6402
|
}
|
|
@@ -6368,8 +7192,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
6368
7192
|
try {
|
|
6369
7193
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
6370
7194
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
6371
|
-
const { readFileSync:
|
|
6372
|
-
const source =
|
|
7195
|
+
const { readFileSync: readFileSync13 } = __require("fs");
|
|
7196
|
+
const source = readFileSync13(filePath, "utf8");
|
|
6373
7197
|
const lines = source.split("\n");
|
|
6374
7198
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
6375
7199
|
let matchLine = -1;
|
|
@@ -6435,9 +7259,9 @@ function extractBash(input, response) {
|
|
|
6435
7259
|
}
|
|
6436
7260
|
function extractGrep(input, response) {
|
|
6437
7261
|
const pattern = String(input.pattern ?? "");
|
|
6438
|
-
const
|
|
7262
|
+
const path21 = input.path ? String(input.path) : "";
|
|
6439
7263
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
6440
|
-
return `Searched for "${pattern}"${
|
|
7264
|
+
return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
|
|
6441
7265
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
6442
7266
|
}
|
|
6443
7267
|
function extractGlob(input, response) {
|
|
@@ -6540,7 +7364,7 @@ __export(error_detector_exports, {
|
|
|
6540
7364
|
errorFingerprint: () => errorFingerprint,
|
|
6541
7365
|
isExeOsError: () => isExeOsError
|
|
6542
7366
|
});
|
|
6543
|
-
import
|
|
7367
|
+
import crypto7 from "crypto";
|
|
6544
7368
|
function isRealStderr(stderr) {
|
|
6545
7369
|
const lines = stderr.trim().split("\n");
|
|
6546
7370
|
const meaningful = lines.filter(
|
|
@@ -6611,7 +7435,7 @@ function classifyError(errorText) {
|
|
|
6611
7435
|
}
|
|
6612
7436
|
function errorFingerprint(toolName, errorText) {
|
|
6613
7437
|
const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
|
|
6614
|
-
return
|
|
7438
|
+
return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
|
|
6615
7439
|
}
|
|
6616
7440
|
var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
|
|
6617
7441
|
var init_error_detector = __esm({
|