@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/bin/exe-rename.js
CHANGED
|
@@ -9,9 +9,34 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/lib/secure-files.ts
|
|
13
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
14
|
+
import { chmod, mkdir } from "fs/promises";
|
|
15
|
+
function ensurePrivateDirSync(dirPath) {
|
|
16
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
17
|
+
try {
|
|
18
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function enforcePrivateFileSync(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
29
|
+
var init_secure_files = __esm({
|
|
30
|
+
"src/lib/secure-files.ts"() {
|
|
31
|
+
"use strict";
|
|
32
|
+
PRIVATE_DIR_MODE = 448;
|
|
33
|
+
PRIVATE_FILE_MODE = 384;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
12
37
|
// src/lib/config.ts
|
|
13
|
-
import { readFile, writeFile
|
|
14
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
38
|
+
import { readFile, writeFile } from "fs/promises";
|
|
39
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
15
40
|
import path from "path";
|
|
16
41
|
import os from "os";
|
|
17
42
|
function resolveDataDir() {
|
|
@@ -19,7 +44,7 @@ function resolveDataDir() {
|
|
|
19
44
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
20
45
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
21
46
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
22
|
-
if (!
|
|
47
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
23
48
|
try {
|
|
24
49
|
renameSync(legacyDir, newDir);
|
|
25
50
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -34,6 +59,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
34
59
|
var init_config = __esm({
|
|
35
60
|
"src/lib/config.ts"() {
|
|
36
61
|
"use strict";
|
|
62
|
+
init_secure_files();
|
|
37
63
|
EXE_AI_DIR = resolveDataDir();
|
|
38
64
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
39
65
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -102,7 +128,7 @@ var init_config = __esm({
|
|
|
102
128
|
|
|
103
129
|
// src/lib/employees.ts
|
|
104
130
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
105
|
-
import { existsSync as
|
|
131
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
106
132
|
import { execSync } from "child_process";
|
|
107
133
|
import path2 from "path";
|
|
108
134
|
import os2 from "os";
|
|
@@ -134,7 +160,7 @@ function validateEmployeeName(name) {
|
|
|
134
160
|
return { valid: true };
|
|
135
161
|
}
|
|
136
162
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
137
|
-
if (!
|
|
163
|
+
if (!existsSync3(employeesPath)) {
|
|
138
164
|
return [];
|
|
139
165
|
}
|
|
140
166
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -149,7 +175,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
149
175
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
150
176
|
}
|
|
151
177
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
152
|
-
if (!
|
|
178
|
+
if (!existsSync3(employeesPath)) return [];
|
|
153
179
|
try {
|
|
154
180
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
155
181
|
} catch {
|
|
@@ -183,7 +209,7 @@ function registerBinSymlinks(name) {
|
|
|
183
209
|
for (const suffix of ["", "-opencode"]) {
|
|
184
210
|
const linkName = `${name}${suffix}`;
|
|
185
211
|
const linkPath = path2.join(binDir, linkName);
|
|
186
|
-
if (
|
|
212
|
+
if (existsSync3(linkPath)) {
|
|
187
213
|
skipped.push(linkName);
|
|
188
214
|
continue;
|
|
189
215
|
}
|
|
@@ -196,7 +222,7 @@ function registerBinSymlinks(name) {
|
|
|
196
222
|
}
|
|
197
223
|
return { created, skipped, errors };
|
|
198
224
|
}
|
|
199
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
225
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
200
226
|
var init_employees = __esm({
|
|
201
227
|
"src/lib/employees.ts"() {
|
|
202
228
|
"use strict";
|
|
@@ -204,6 +230,7 @@ var init_employees = __esm({
|
|
|
204
230
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
205
231
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
206
232
|
COORDINATOR_ROLE = "COO";
|
|
233
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
207
234
|
}
|
|
208
235
|
});
|
|
209
236
|
|
|
@@ -262,13 +289,634 @@ var init_db_retry = __esm({
|
|
|
262
289
|
}
|
|
263
290
|
});
|
|
264
291
|
|
|
292
|
+
// src/lib/database-adapter.ts
|
|
293
|
+
import os3 from "os";
|
|
294
|
+
import path3 from "path";
|
|
295
|
+
import { createRequire } from "module";
|
|
296
|
+
import { pathToFileURL } from "url";
|
|
297
|
+
function quotedIdentifier(identifier) {
|
|
298
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
299
|
+
}
|
|
300
|
+
function unqualifiedTableName(name) {
|
|
301
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
302
|
+
const parts = raw.split(".");
|
|
303
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
304
|
+
}
|
|
305
|
+
function stripTrailingSemicolon(sql) {
|
|
306
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
307
|
+
}
|
|
308
|
+
function appendClause(sql, clause) {
|
|
309
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
310
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
311
|
+
if (!returningMatch) {
|
|
312
|
+
return `${trimmed}${clause}`;
|
|
313
|
+
}
|
|
314
|
+
const idx = returningMatch.index;
|
|
315
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
316
|
+
}
|
|
317
|
+
function normalizeStatement(stmt) {
|
|
318
|
+
if (typeof stmt === "string") {
|
|
319
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
320
|
+
}
|
|
321
|
+
const sql = stmt.sql;
|
|
322
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
323
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
324
|
+
}
|
|
325
|
+
return { kind: "named", sql, args: stmt.args };
|
|
326
|
+
}
|
|
327
|
+
function rewriteBooleanLiterals(sql) {
|
|
328
|
+
let out = sql;
|
|
329
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
330
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
331
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
332
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
333
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
334
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
335
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
336
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function rewriteInsertOrIgnore(sql) {
|
|
341
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
342
|
+
return sql;
|
|
343
|
+
}
|
|
344
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
345
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
346
|
+
}
|
|
347
|
+
function rewriteInsertOrReplace(sql) {
|
|
348
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
349
|
+
if (!match) {
|
|
350
|
+
return sql;
|
|
351
|
+
}
|
|
352
|
+
const rawTable = match[1];
|
|
353
|
+
const rawColumns = match[2];
|
|
354
|
+
const remainder = match[3];
|
|
355
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
356
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
357
|
+
if (!conflictKeys?.length) {
|
|
358
|
+
return sql;
|
|
359
|
+
}
|
|
360
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
361
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
362
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
363
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
364
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
365
|
+
}
|
|
366
|
+
function rewriteSql(sql) {
|
|
367
|
+
let out = sql;
|
|
368
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
369
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
370
|
+
out = rewriteBooleanLiterals(out);
|
|
371
|
+
out = rewriteInsertOrReplace(out);
|
|
372
|
+
out = rewriteInsertOrIgnore(out);
|
|
373
|
+
return stripTrailingSemicolon(out);
|
|
374
|
+
}
|
|
375
|
+
function toBoolean(value) {
|
|
376
|
+
if (value === null || value === void 0) return value;
|
|
377
|
+
if (typeof value === "boolean") return value;
|
|
378
|
+
if (typeof value === "number") return value !== 0;
|
|
379
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
380
|
+
if (typeof value === "string") {
|
|
381
|
+
const normalized = value.trim().toLowerCase();
|
|
382
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
383
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
384
|
+
}
|
|
385
|
+
return Boolean(value);
|
|
386
|
+
}
|
|
387
|
+
function countQuestionMarks(sql, end) {
|
|
388
|
+
let count = 0;
|
|
389
|
+
let inSingle = false;
|
|
390
|
+
let inDouble = false;
|
|
391
|
+
let inLineComment = false;
|
|
392
|
+
let inBlockComment = false;
|
|
393
|
+
for (let i = 0; i < end; i++) {
|
|
394
|
+
const ch = sql[i];
|
|
395
|
+
const next = sql[i + 1];
|
|
396
|
+
if (inLineComment) {
|
|
397
|
+
if (ch === "\n") inLineComment = false;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (inBlockComment) {
|
|
401
|
+
if (ch === "*" && next === "/") {
|
|
402
|
+
inBlockComment = false;
|
|
403
|
+
i += 1;
|
|
404
|
+
}
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
408
|
+
inLineComment = true;
|
|
409
|
+
i += 1;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
413
|
+
inBlockComment = true;
|
|
414
|
+
i += 1;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
418
|
+
inSingle = !inSingle;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
422
|
+
inDouble = !inDouble;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
426
|
+
count += 1;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return count;
|
|
430
|
+
}
|
|
431
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
432
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
433
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
434
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
435
|
+
for (const match of sql.matchAll(pattern)) {
|
|
436
|
+
const matchText = match[0];
|
|
437
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
438
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return indexes;
|
|
442
|
+
}
|
|
443
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
444
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
445
|
+
if (!match) return;
|
|
446
|
+
const rawTable = match[1];
|
|
447
|
+
const rawColumns = match[2];
|
|
448
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
449
|
+
if (!boolColumns?.size) return;
|
|
450
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
451
|
+
for (const [index, column] of columns.entries()) {
|
|
452
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
453
|
+
args[index] = toBoolean(args[index]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
458
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
459
|
+
if (!match) return;
|
|
460
|
+
const rawTable = match[1];
|
|
461
|
+
const setClause = match[2];
|
|
462
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
463
|
+
if (!boolColumns?.size) return;
|
|
464
|
+
const assignments = setClause.split(",");
|
|
465
|
+
let placeholderIndex = 0;
|
|
466
|
+
for (const assignment of assignments) {
|
|
467
|
+
if (!assignment.includes("?")) continue;
|
|
468
|
+
placeholderIndex += 1;
|
|
469
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
470
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
471
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function coerceBooleanArgs(sql, args) {
|
|
476
|
+
const nextArgs = [...args];
|
|
477
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
478
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
479
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
480
|
+
for (const index of placeholderIndexes) {
|
|
481
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
482
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return nextArgs;
|
|
486
|
+
}
|
|
487
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
488
|
+
let out = "";
|
|
489
|
+
let placeholder = 0;
|
|
490
|
+
let inSingle = false;
|
|
491
|
+
let inDouble = false;
|
|
492
|
+
let inLineComment = false;
|
|
493
|
+
let inBlockComment = false;
|
|
494
|
+
for (let i = 0; i < sql.length; i++) {
|
|
495
|
+
const ch = sql[i];
|
|
496
|
+
const next = sql[i + 1];
|
|
497
|
+
if (inLineComment) {
|
|
498
|
+
out += ch;
|
|
499
|
+
if (ch === "\n") inLineComment = false;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (inBlockComment) {
|
|
503
|
+
out += ch;
|
|
504
|
+
if (ch === "*" && next === "/") {
|
|
505
|
+
out += next;
|
|
506
|
+
inBlockComment = false;
|
|
507
|
+
i += 1;
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
512
|
+
out += ch + next;
|
|
513
|
+
inLineComment = true;
|
|
514
|
+
i += 1;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
518
|
+
out += ch + next;
|
|
519
|
+
inBlockComment = true;
|
|
520
|
+
i += 1;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
524
|
+
inSingle = !inSingle;
|
|
525
|
+
out += ch;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
529
|
+
inDouble = !inDouble;
|
|
530
|
+
out += ch;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
534
|
+
placeholder += 1;
|
|
535
|
+
out += `$${placeholder}`;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
out += ch;
|
|
539
|
+
}
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
function translateStatementForPostgres(stmt) {
|
|
543
|
+
const normalized = normalizeStatement(stmt);
|
|
544
|
+
if (normalized.kind === "named") {
|
|
545
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
546
|
+
}
|
|
547
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
548
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
549
|
+
return {
|
|
550
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
551
|
+
args: coercedArgs
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function shouldBypassPostgres(stmt) {
|
|
555
|
+
const normalized = normalizeStatement(stmt);
|
|
556
|
+
if (normalized.kind === "named") {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
560
|
+
}
|
|
561
|
+
function shouldFallbackOnError(error) {
|
|
562
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
563
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
564
|
+
}
|
|
565
|
+
function isReadQuery(sql) {
|
|
566
|
+
const trimmed = sql.trimStart();
|
|
567
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
568
|
+
}
|
|
569
|
+
function buildRow(row, columns) {
|
|
570
|
+
const values = columns.map((column) => row[column]);
|
|
571
|
+
return Object.assign(values, row);
|
|
572
|
+
}
|
|
573
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
574
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
575
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
576
|
+
return {
|
|
577
|
+
columns,
|
|
578
|
+
columnTypes: columns.map(() => ""),
|
|
579
|
+
rows: resultRows,
|
|
580
|
+
rowsAffected,
|
|
581
|
+
lastInsertRowid: void 0,
|
|
582
|
+
toJSON() {
|
|
583
|
+
return {
|
|
584
|
+
columns,
|
|
585
|
+
columnTypes: columns.map(() => ""),
|
|
586
|
+
rows,
|
|
587
|
+
rowsAffected,
|
|
588
|
+
lastInsertRowid: void 0
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
async function loadPrismaClient() {
|
|
594
|
+
if (!prismaClientPromise) {
|
|
595
|
+
prismaClientPromise = (async () => {
|
|
596
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
597
|
+
if (explicitPath) {
|
|
598
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
599
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
600
|
+
if (!PrismaClient2) {
|
|
601
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
602
|
+
}
|
|
603
|
+
return new PrismaClient2();
|
|
604
|
+
}
|
|
605
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
606
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
607
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
608
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
609
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
610
|
+
if (!PrismaClient) {
|
|
611
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
612
|
+
}
|
|
613
|
+
return new PrismaClient();
|
|
614
|
+
})();
|
|
615
|
+
}
|
|
616
|
+
return prismaClientPromise;
|
|
617
|
+
}
|
|
618
|
+
async function ensureCompatibilityViews(prisma) {
|
|
619
|
+
if (!compatibilityBootstrapPromise) {
|
|
620
|
+
compatibilityBootstrapPromise = (async () => {
|
|
621
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
622
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
623
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
624
|
+
"SELECT to_regclass($1) AS regclass",
|
|
625
|
+
relation
|
|
626
|
+
);
|
|
627
|
+
if (!rows[0]?.regclass) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
await prisma.$executeRawUnsafe(
|
|
631
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
})();
|
|
635
|
+
}
|
|
636
|
+
return compatibilityBootstrapPromise;
|
|
637
|
+
}
|
|
638
|
+
async function executeOnPrisma(executor, stmt) {
|
|
639
|
+
const translated = translateStatementForPostgres(stmt);
|
|
640
|
+
if (isReadQuery(translated.sql)) {
|
|
641
|
+
const rows = await executor.$queryRawUnsafe(
|
|
642
|
+
translated.sql,
|
|
643
|
+
...translated.args
|
|
644
|
+
);
|
|
645
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
646
|
+
}
|
|
647
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
648
|
+
return buildResultSet([], rowsAffected);
|
|
649
|
+
}
|
|
650
|
+
function splitSqlStatements(sql) {
|
|
651
|
+
const parts = [];
|
|
652
|
+
let current = "";
|
|
653
|
+
let inSingle = false;
|
|
654
|
+
let inDouble = false;
|
|
655
|
+
let inLineComment = false;
|
|
656
|
+
let inBlockComment = false;
|
|
657
|
+
for (let i = 0; i < sql.length; i++) {
|
|
658
|
+
const ch = sql[i];
|
|
659
|
+
const next = sql[i + 1];
|
|
660
|
+
if (inLineComment) {
|
|
661
|
+
current += ch;
|
|
662
|
+
if (ch === "\n") inLineComment = false;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (inBlockComment) {
|
|
666
|
+
current += ch;
|
|
667
|
+
if (ch === "*" && next === "/") {
|
|
668
|
+
current += next;
|
|
669
|
+
inBlockComment = false;
|
|
670
|
+
i += 1;
|
|
671
|
+
}
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
675
|
+
current += ch + next;
|
|
676
|
+
inLineComment = true;
|
|
677
|
+
i += 1;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
681
|
+
current += ch + next;
|
|
682
|
+
inBlockComment = true;
|
|
683
|
+
i += 1;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
687
|
+
inSingle = !inSingle;
|
|
688
|
+
current += ch;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
692
|
+
inDouble = !inDouble;
|
|
693
|
+
current += ch;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
697
|
+
if (current.trim()) {
|
|
698
|
+
parts.push(current.trim());
|
|
699
|
+
}
|
|
700
|
+
current = "";
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
current += ch;
|
|
704
|
+
}
|
|
705
|
+
if (current.trim()) {
|
|
706
|
+
parts.push(current.trim());
|
|
707
|
+
}
|
|
708
|
+
return parts;
|
|
709
|
+
}
|
|
710
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
711
|
+
const prisma = await loadPrismaClient();
|
|
712
|
+
await ensureCompatibilityViews(prisma);
|
|
713
|
+
let closed = false;
|
|
714
|
+
let adapter;
|
|
715
|
+
const fallbackExecute = async (stmt, error) => {
|
|
716
|
+
if (!fallbackClient) {
|
|
717
|
+
if (error) throw error;
|
|
718
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
719
|
+
}
|
|
720
|
+
if (error) {
|
|
721
|
+
process.stderr.write(
|
|
722
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
723
|
+
`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return fallbackClient.execute(stmt);
|
|
727
|
+
};
|
|
728
|
+
adapter = {
|
|
729
|
+
async execute(stmt) {
|
|
730
|
+
if (shouldBypassPostgres(stmt)) {
|
|
731
|
+
return fallbackExecute(stmt);
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
return await executeOnPrisma(prisma, stmt);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
if (shouldFallbackOnError(error)) {
|
|
737
|
+
return fallbackExecute(stmt, error);
|
|
738
|
+
}
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
async batch(stmts, mode) {
|
|
743
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
744
|
+
if (!fallbackClient) {
|
|
745
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
746
|
+
}
|
|
747
|
+
return fallbackClient.batch(stmts, mode);
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
if (prisma.$transaction) {
|
|
751
|
+
return await prisma.$transaction(async (tx) => {
|
|
752
|
+
const results2 = [];
|
|
753
|
+
for (const stmt of stmts) {
|
|
754
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
755
|
+
}
|
|
756
|
+
return results2;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
const results = [];
|
|
760
|
+
for (const stmt of stmts) {
|
|
761
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
762
|
+
}
|
|
763
|
+
return results;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
766
|
+
process.stderr.write(
|
|
767
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
768
|
+
`
|
|
769
|
+
);
|
|
770
|
+
return fallbackClient.batch(stmts, mode);
|
|
771
|
+
}
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
async migrate(stmts) {
|
|
776
|
+
if (fallbackClient) {
|
|
777
|
+
return fallbackClient.migrate(stmts);
|
|
778
|
+
}
|
|
779
|
+
return adapter.batch(stmts, "deferred");
|
|
780
|
+
},
|
|
781
|
+
async transaction(mode) {
|
|
782
|
+
if (!fallbackClient) {
|
|
783
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
784
|
+
}
|
|
785
|
+
return fallbackClient.transaction(mode);
|
|
786
|
+
},
|
|
787
|
+
async executeMultiple(sql) {
|
|
788
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
789
|
+
return fallbackClient.executeMultiple(sql);
|
|
790
|
+
}
|
|
791
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
792
|
+
await adapter.execute(statement);
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
async sync() {
|
|
796
|
+
if (fallbackClient) {
|
|
797
|
+
return fallbackClient.sync();
|
|
798
|
+
}
|
|
799
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
800
|
+
},
|
|
801
|
+
close() {
|
|
802
|
+
closed = true;
|
|
803
|
+
prismaClientPromise = null;
|
|
804
|
+
compatibilityBootstrapPromise = null;
|
|
805
|
+
void prisma.$disconnect?.();
|
|
806
|
+
},
|
|
807
|
+
get closed() {
|
|
808
|
+
return closed;
|
|
809
|
+
},
|
|
810
|
+
get protocol() {
|
|
811
|
+
return "prisma-postgres";
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
return adapter;
|
|
815
|
+
}
|
|
816
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
817
|
+
var init_database_adapter = __esm({
|
|
818
|
+
"src/lib/database-adapter.ts"() {
|
|
819
|
+
"use strict";
|
|
820
|
+
VIEW_MAPPINGS = [
|
|
821
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
822
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
823
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
824
|
+
{ view: "entities", source: "memory.entities" },
|
|
825
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
826
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
827
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
828
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
829
|
+
{ view: "messages", source: "memory.messages" },
|
|
830
|
+
{ view: "users", source: "wiki.users" },
|
|
831
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
832
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
833
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
834
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
835
|
+
];
|
|
836
|
+
UPSERT_KEYS = {
|
|
837
|
+
memories: ["id"],
|
|
838
|
+
tasks: ["id"],
|
|
839
|
+
behaviors: ["id"],
|
|
840
|
+
entities: ["id"],
|
|
841
|
+
relationships: ["id"],
|
|
842
|
+
entity_aliases: ["alias"],
|
|
843
|
+
notifications: ["id"],
|
|
844
|
+
messages: ["id"],
|
|
845
|
+
users: ["id"],
|
|
846
|
+
workspaces: ["id"],
|
|
847
|
+
workspace_users: ["id"],
|
|
848
|
+
documents: ["id"],
|
|
849
|
+
chats: ["id"]
|
|
850
|
+
};
|
|
851
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
852
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
853
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
854
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
855
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
856
|
+
};
|
|
857
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
858
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
859
|
+
);
|
|
860
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
861
|
+
/\bPRAGMA\b/i,
|
|
862
|
+
/\bsqlite_master\b/i,
|
|
863
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
864
|
+
/\bMATCH\b/i,
|
|
865
|
+
/\bvector_distance_cos\s*\(/i,
|
|
866
|
+
/\bjson_extract\s*\(/i,
|
|
867
|
+
/\bjulianday\s*\(/i,
|
|
868
|
+
/\bstrftime\s*\(/i,
|
|
869
|
+
/\blast_insert_rowid\s*\(/i
|
|
870
|
+
];
|
|
871
|
+
prismaClientPromise = null;
|
|
872
|
+
compatibilityBootstrapPromise = null;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// src/lib/daemon-auth.ts
|
|
877
|
+
import crypto from "crypto";
|
|
878
|
+
import path4 from "path";
|
|
879
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
880
|
+
function normalizeToken(token) {
|
|
881
|
+
if (!token) return null;
|
|
882
|
+
const trimmed = token.trim();
|
|
883
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
884
|
+
}
|
|
885
|
+
function readDaemonToken() {
|
|
886
|
+
try {
|
|
887
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
888
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
889
|
+
} catch {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function ensureDaemonToken(seed) {
|
|
894
|
+
const existing = readDaemonToken();
|
|
895
|
+
if (existing) return existing;
|
|
896
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
897
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
898
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
899
|
+
`, "utf8");
|
|
900
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
901
|
+
return token;
|
|
902
|
+
}
|
|
903
|
+
var DAEMON_TOKEN_PATH;
|
|
904
|
+
var init_daemon_auth = __esm({
|
|
905
|
+
"src/lib/daemon-auth.ts"() {
|
|
906
|
+
"use strict";
|
|
907
|
+
init_config();
|
|
908
|
+
init_secure_files();
|
|
909
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
265
913
|
// src/lib/exe-daemon-client.ts
|
|
266
914
|
import net from "net";
|
|
267
|
-
import
|
|
915
|
+
import os4 from "os";
|
|
268
916
|
import { spawn } from "child_process";
|
|
269
917
|
import { randomUUID } from "crypto";
|
|
270
|
-
import { existsSync as
|
|
271
|
-
import
|
|
918
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
919
|
+
import path5 from "path";
|
|
272
920
|
import { fileURLToPath } from "url";
|
|
273
921
|
function handleData(chunk) {
|
|
274
922
|
_buffer += chunk.toString();
|
|
@@ -296,9 +944,9 @@ function handleData(chunk) {
|
|
|
296
944
|
}
|
|
297
945
|
}
|
|
298
946
|
function cleanupStaleFiles() {
|
|
299
|
-
if (
|
|
947
|
+
if (existsSync5(PID_PATH)) {
|
|
300
948
|
try {
|
|
301
|
-
const pid = parseInt(
|
|
949
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
302
950
|
if (pid > 0) {
|
|
303
951
|
try {
|
|
304
952
|
process.kill(pid, 0);
|
|
@@ -319,17 +967,17 @@ function cleanupStaleFiles() {
|
|
|
319
967
|
}
|
|
320
968
|
}
|
|
321
969
|
function findPackageRoot() {
|
|
322
|
-
let dir =
|
|
323
|
-
const { root } =
|
|
970
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
971
|
+
const { root } = path5.parse(dir);
|
|
324
972
|
while (dir !== root) {
|
|
325
|
-
if (
|
|
326
|
-
dir =
|
|
973
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
974
|
+
dir = path5.dirname(dir);
|
|
327
975
|
}
|
|
328
976
|
return null;
|
|
329
977
|
}
|
|
330
978
|
function spawnDaemon() {
|
|
331
|
-
const freeGB =
|
|
332
|
-
const totalGB =
|
|
979
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
980
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
333
981
|
if (totalGB <= 8) {
|
|
334
982
|
process.stderr.write(
|
|
335
983
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -349,16 +997,17 @@ function spawnDaemon() {
|
|
|
349
997
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
350
998
|
return;
|
|
351
999
|
}
|
|
352
|
-
const daemonPath =
|
|
353
|
-
if (!
|
|
1000
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1001
|
+
if (!existsSync5(daemonPath)) {
|
|
354
1002
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
355
1003
|
`);
|
|
356
1004
|
return;
|
|
357
1005
|
}
|
|
358
1006
|
const resolvedPath = daemonPath;
|
|
1007
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
359
1008
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
360
1009
|
`);
|
|
361
|
-
const logPath =
|
|
1010
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
362
1011
|
let stderrFd = "ignore";
|
|
363
1012
|
try {
|
|
364
1013
|
stderrFd = openSync(logPath, "a");
|
|
@@ -376,7 +1025,8 @@ function spawnDaemon() {
|
|
|
376
1025
|
TMUX_PANE: void 0,
|
|
377
1026
|
// Prevents resolveExeSession() from scoping to one session
|
|
378
1027
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
379
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1028
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1029
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
380
1030
|
}
|
|
381
1031
|
});
|
|
382
1032
|
child.unref();
|
|
@@ -483,13 +1133,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
483
1133
|
return;
|
|
484
1134
|
}
|
|
485
1135
|
const id = randomUUID();
|
|
1136
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
486
1137
|
const timer = setTimeout(() => {
|
|
487
1138
|
_pending.delete(id);
|
|
488
1139
|
resolve({ error: "Request timeout" });
|
|
489
1140
|
}, timeoutMs);
|
|
490
1141
|
_pending.set(id, { resolve, timer });
|
|
491
1142
|
try {
|
|
492
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1143
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
493
1144
|
} catch {
|
|
494
1145
|
clearTimeout(timer);
|
|
495
1146
|
_pending.delete(id);
|
|
@@ -500,17 +1151,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
500
1151
|
function isClientConnected() {
|
|
501
1152
|
return _connected;
|
|
502
1153
|
}
|
|
503
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1154
|
+
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;
|
|
504
1155
|
var init_exe_daemon_client = __esm({
|
|
505
1156
|
"src/lib/exe-daemon-client.ts"() {
|
|
506
1157
|
"use strict";
|
|
507
1158
|
init_config();
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1159
|
+
init_daemon_auth();
|
|
1160
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1161
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1162
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
511
1163
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
512
1164
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
513
1165
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1166
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
514
1167
|
_socket = null;
|
|
515
1168
|
_connected = false;
|
|
516
1169
|
_buffer = "";
|
|
@@ -589,7 +1242,7 @@ __export(db_daemon_client_exports, {
|
|
|
589
1242
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
590
1243
|
initDaemonDbClient: () => initDaemonDbClient
|
|
591
1244
|
});
|
|
592
|
-
function
|
|
1245
|
+
function normalizeStatement2(stmt) {
|
|
593
1246
|
if (typeof stmt === "string") {
|
|
594
1247
|
return { sql: stmt, args: [] };
|
|
595
1248
|
}
|
|
@@ -613,7 +1266,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
613
1266
|
if (!_useDaemon || !isClientConnected()) {
|
|
614
1267
|
return fallbackClient.execute(stmt);
|
|
615
1268
|
}
|
|
616
|
-
const { sql, args } =
|
|
1269
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
617
1270
|
const response = await sendDaemonRequest({
|
|
618
1271
|
type: "db-execute",
|
|
619
1272
|
sql,
|
|
@@ -638,7 +1291,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
638
1291
|
if (!_useDaemon || !isClientConnected()) {
|
|
639
1292
|
return fallbackClient.batch(stmts, mode);
|
|
640
1293
|
}
|
|
641
|
-
const statements = stmts.map(
|
|
1294
|
+
const statements = stmts.map(normalizeStatement2);
|
|
642
1295
|
const response = await sendDaemonRequest({
|
|
643
1296
|
type: "db-batch",
|
|
644
1297
|
statements,
|
|
@@ -733,6 +1386,18 @@ __export(database_exports, {
|
|
|
733
1386
|
});
|
|
734
1387
|
import { createClient } from "@libsql/client";
|
|
735
1388
|
async function initDatabase(config) {
|
|
1389
|
+
if (_walCheckpointTimer) {
|
|
1390
|
+
clearInterval(_walCheckpointTimer);
|
|
1391
|
+
_walCheckpointTimer = null;
|
|
1392
|
+
}
|
|
1393
|
+
if (_daemonClient) {
|
|
1394
|
+
_daemonClient.close();
|
|
1395
|
+
_daemonClient = null;
|
|
1396
|
+
}
|
|
1397
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1398
|
+
_adapterClient.close();
|
|
1399
|
+
}
|
|
1400
|
+
_adapterClient = null;
|
|
736
1401
|
if (_client) {
|
|
737
1402
|
_client.close();
|
|
738
1403
|
_client = null;
|
|
@@ -746,6 +1411,7 @@ async function initDatabase(config) {
|
|
|
746
1411
|
}
|
|
747
1412
|
_client = createClient(opts);
|
|
748
1413
|
_resilientClient = wrapWithRetry(_client);
|
|
1414
|
+
_adapterClient = _resilientClient;
|
|
749
1415
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
750
1416
|
});
|
|
751
1417
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -756,14 +1422,20 @@ async function initDatabase(config) {
|
|
|
756
1422
|
});
|
|
757
1423
|
}, 3e4);
|
|
758
1424
|
_walCheckpointTimer.unref();
|
|
1425
|
+
if (process.env.DATABASE_URL) {
|
|
1426
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1427
|
+
}
|
|
759
1428
|
}
|
|
760
1429
|
function isInitialized() {
|
|
761
|
-
return _client !== null;
|
|
1430
|
+
return _adapterClient !== null || _client !== null;
|
|
762
1431
|
}
|
|
763
1432
|
function getClient() {
|
|
764
|
-
if (!
|
|
1433
|
+
if (!_adapterClient) {
|
|
765
1434
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
766
1435
|
}
|
|
1436
|
+
if (process.env.DATABASE_URL) {
|
|
1437
|
+
return _adapterClient;
|
|
1438
|
+
}
|
|
767
1439
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
768
1440
|
return _resilientClient;
|
|
769
1441
|
}
|
|
@@ -773,6 +1445,7 @@ function getClient() {
|
|
|
773
1445
|
return _resilientClient;
|
|
774
1446
|
}
|
|
775
1447
|
async function initDaemonClient() {
|
|
1448
|
+
if (process.env.DATABASE_URL) return;
|
|
776
1449
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
777
1450
|
if (!_resilientClient) return;
|
|
778
1451
|
try {
|
|
@@ -1069,6 +1742,7 @@ async function ensureSchema() {
|
|
|
1069
1742
|
project TEXT NOT NULL,
|
|
1070
1743
|
summary TEXT NOT NULL,
|
|
1071
1744
|
task_file TEXT,
|
|
1745
|
+
session_scope TEXT,
|
|
1072
1746
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1073
1747
|
created_at TEXT NOT NULL
|
|
1074
1748
|
);
|
|
@@ -1077,7 +1751,7 @@ async function ensureSchema() {
|
|
|
1077
1751
|
ON notifications(read);
|
|
1078
1752
|
|
|
1079
1753
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1080
|
-
ON notifications(agent_id);
|
|
1754
|
+
ON notifications(agent_id, session_scope);
|
|
1081
1755
|
|
|
1082
1756
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1083
1757
|
ON notifications(task_file);
|
|
@@ -1115,6 +1789,7 @@ async function ensureSchema() {
|
|
|
1115
1789
|
target_agent TEXT NOT NULL,
|
|
1116
1790
|
target_project TEXT,
|
|
1117
1791
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1792
|
+
session_scope TEXT,
|
|
1118
1793
|
content TEXT NOT NULL,
|
|
1119
1794
|
priority TEXT DEFAULT 'normal',
|
|
1120
1795
|
status TEXT DEFAULT 'pending',
|
|
@@ -1128,10 +1803,31 @@ async function ensureSchema() {
|
|
|
1128
1803
|
);
|
|
1129
1804
|
|
|
1130
1805
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1131
|
-
ON messages(target_agent, status);
|
|
1806
|
+
ON messages(target_agent, session_scope, status);
|
|
1132
1807
|
|
|
1133
1808
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1134
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1809
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1810
|
+
`);
|
|
1811
|
+
try {
|
|
1812
|
+
await client.execute({
|
|
1813
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1814
|
+
args: []
|
|
1815
|
+
});
|
|
1816
|
+
} catch {
|
|
1817
|
+
}
|
|
1818
|
+
try {
|
|
1819
|
+
await client.execute({
|
|
1820
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1821
|
+
args: []
|
|
1822
|
+
});
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
await client.executeMultiple(`
|
|
1826
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1827
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1828
|
+
|
|
1829
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1830
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1135
1831
|
`);
|
|
1136
1832
|
try {
|
|
1137
1833
|
await client.execute({
|
|
@@ -1715,28 +2411,45 @@ async function ensureSchema() {
|
|
|
1715
2411
|
} catch {
|
|
1716
2412
|
}
|
|
1717
2413
|
}
|
|
2414
|
+
try {
|
|
2415
|
+
await client.execute({
|
|
2416
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2417
|
+
args: []
|
|
2418
|
+
});
|
|
2419
|
+
} catch {
|
|
2420
|
+
}
|
|
1718
2421
|
}
|
|
1719
2422
|
async function disposeDatabase() {
|
|
2423
|
+
if (_walCheckpointTimer) {
|
|
2424
|
+
clearInterval(_walCheckpointTimer);
|
|
2425
|
+
_walCheckpointTimer = null;
|
|
2426
|
+
}
|
|
1720
2427
|
if (_daemonClient) {
|
|
1721
2428
|
_daemonClient.close();
|
|
1722
2429
|
_daemonClient = null;
|
|
1723
2430
|
}
|
|
2431
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2432
|
+
_adapterClient.close();
|
|
2433
|
+
}
|
|
2434
|
+
_adapterClient = null;
|
|
1724
2435
|
if (_client) {
|
|
1725
2436
|
_client.close();
|
|
1726
2437
|
_client = null;
|
|
1727
2438
|
_resilientClient = null;
|
|
1728
2439
|
}
|
|
1729
2440
|
}
|
|
1730
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2441
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1731
2442
|
var init_database = __esm({
|
|
1732
2443
|
"src/lib/database.ts"() {
|
|
1733
2444
|
"use strict";
|
|
1734
2445
|
init_db_retry();
|
|
1735
2446
|
init_employees();
|
|
2447
|
+
init_database_adapter();
|
|
1736
2448
|
_client = null;
|
|
1737
2449
|
_resilientClient = null;
|
|
1738
2450
|
_walCheckpointTimer = null;
|
|
1739
2451
|
_daemonClient = null;
|
|
2452
|
+
_adapterClient = null;
|
|
1740
2453
|
initTurso = initDatabase;
|
|
1741
2454
|
disposeTurso = disposeDatabase;
|
|
1742
2455
|
}
|
|
@@ -1744,9 +2457,9 @@ var init_database = __esm({
|
|
|
1744
2457
|
|
|
1745
2458
|
// src/bin/exe-rename.ts
|
|
1746
2459
|
init_employees();
|
|
1747
|
-
import { readFileSync as
|
|
2460
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3, existsSync as existsSync6 } from "fs";
|
|
1748
2461
|
import { execSync as execSync2 } from "child_process";
|
|
1749
|
-
import
|
|
2462
|
+
import path6 from "path";
|
|
1750
2463
|
import { homedir } from "os";
|
|
1751
2464
|
|
|
1752
2465
|
// src/lib/global-procedures.ts
|
|
@@ -1891,9 +2604,9 @@ function isMainModule(importMetaUrl) {
|
|
|
1891
2604
|
|
|
1892
2605
|
// src/bin/exe-rename.ts
|
|
1893
2606
|
async function renameEmployee(oldName, newName, opts = {}) {
|
|
1894
|
-
const rosterPath = opts.rosterPath ??
|
|
1895
|
-
const identityDir = opts.identityDir ??
|
|
1896
|
-
const agentsDir = opts.agentsDir ??
|
|
2607
|
+
const rosterPath = opts.rosterPath ?? path6.join(homedir(), ".exe-os", "exe-employees.json");
|
|
2608
|
+
const identityDir = opts.identityDir ?? path6.join(homedir(), ".exe-os", "identity");
|
|
2609
|
+
const agentsDir = opts.agentsDir ?? path6.join(homedir(), ".claude", "agents");
|
|
1897
2610
|
const validation = validateEmployeeName(newName);
|
|
1898
2611
|
if (!validation.valid) {
|
|
1899
2612
|
return { success: false, error: validation.error };
|
|
@@ -1922,40 +2635,40 @@ async function renameEmployee(oldName, newName, opts = {}) {
|
|
|
1922
2635
|
undo: () => {
|
|
1923
2636
|
employee.name = originalName;
|
|
1924
2637
|
employee.systemPrompt = originalPrompt;
|
|
1925
|
-
|
|
2638
|
+
writeFileSync3(rosterPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1926
2639
|
}
|
|
1927
2640
|
});
|
|
1928
|
-
const oldIdentityPath =
|
|
1929
|
-
const newIdentityPath =
|
|
1930
|
-
if (
|
|
1931
|
-
const content =
|
|
2641
|
+
const oldIdentityPath = path6.join(identityDir, `${rosterOldName}.md`);
|
|
2642
|
+
const newIdentityPath = path6.join(identityDir, `${newName}.md`);
|
|
2643
|
+
if (existsSync6(oldIdentityPath)) {
|
|
2644
|
+
const content = readFileSync5(oldIdentityPath, "utf-8");
|
|
1932
2645
|
const updatedContent = content.replace(
|
|
1933
2646
|
/^(agent_id:\s*)\S+/m,
|
|
1934
2647
|
`$1${newName}`
|
|
1935
2648
|
);
|
|
1936
2649
|
renameSync3(oldIdentityPath, newIdentityPath);
|
|
1937
|
-
|
|
2650
|
+
writeFileSync3(newIdentityPath, updatedContent, "utf-8");
|
|
1938
2651
|
rollbackStack.push({
|
|
1939
2652
|
description: "restore identity file",
|
|
1940
2653
|
undo: () => {
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
2654
|
+
if (existsSync6(newIdentityPath)) {
|
|
2655
|
+
writeFileSync3(newIdentityPath, content, "utf-8");
|
|
1943
2656
|
renameSync3(newIdentityPath, oldIdentityPath);
|
|
1944
2657
|
}
|
|
1945
2658
|
}
|
|
1946
2659
|
});
|
|
1947
2660
|
}
|
|
1948
|
-
const oldAgentPath =
|
|
1949
|
-
const newAgentPath =
|
|
1950
|
-
if (
|
|
1951
|
-
const agentContent =
|
|
2661
|
+
const oldAgentPath = path6.join(agentsDir, `${rosterOldName}.md`);
|
|
2662
|
+
const newAgentPath = path6.join(agentsDir, `${newName}.md`);
|
|
2663
|
+
if (existsSync6(oldAgentPath)) {
|
|
2664
|
+
const agentContent = readFileSync5(oldAgentPath, "utf-8");
|
|
1952
2665
|
renameSync3(oldAgentPath, newAgentPath);
|
|
1953
2666
|
rollbackStack.push({
|
|
1954
2667
|
description: "restore agent file",
|
|
1955
2668
|
undo: () => {
|
|
1956
|
-
if (
|
|
2669
|
+
if (existsSync6(newAgentPath)) {
|
|
1957
2670
|
renameSync3(newAgentPath, oldAgentPath);
|
|
1958
|
-
|
|
2671
|
+
writeFileSync3(oldAgentPath, agentContent, "utf-8");
|
|
1959
2672
|
}
|
|
1960
2673
|
}
|
|
1961
2674
|
});
|
|
@@ -2033,10 +2746,10 @@ function removeOldSymlinks(name) {
|
|
|
2033
2746
|
try {
|
|
2034
2747
|
const exeBinPath = findExeBin2();
|
|
2035
2748
|
if (!exeBinPath) return;
|
|
2036
|
-
const binDir =
|
|
2749
|
+
const binDir = path6.dirname(exeBinPath);
|
|
2037
2750
|
for (const suffix of ["", "-opencode"]) {
|
|
2038
|
-
const linkPath =
|
|
2039
|
-
if (
|
|
2751
|
+
const linkPath = path6.join(binDir, `${name}${suffix}`);
|
|
2752
|
+
if (existsSync6(linkPath)) {
|
|
2040
2753
|
try {
|
|
2041
2754
|
unlinkSync3(linkPath);
|
|
2042
2755
|
} catch {
|