@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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
3
5
|
var __esm = (fn, res) => function __init() {
|
|
4
6
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
7
|
};
|
|
@@ -7,6 +9,15 @@ var __export = (target, all) => {
|
|
|
7
9
|
for (var name in all)
|
|
8
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
11
|
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
10
21
|
|
|
11
22
|
// src/lib/db-retry.ts
|
|
12
23
|
function isBusyError(err) {
|
|
@@ -63,9 +74,34 @@ var init_db_retry = __esm({
|
|
|
63
74
|
}
|
|
64
75
|
});
|
|
65
76
|
|
|
77
|
+
// src/lib/secure-files.ts
|
|
78
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
79
|
+
import { chmod, mkdir } from "fs/promises";
|
|
80
|
+
async function ensurePrivateDir(dirPath) {
|
|
81
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
82
|
+
try {
|
|
83
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function enforcePrivateFile(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
94
|
+
var init_secure_files = __esm({
|
|
95
|
+
"src/lib/secure-files.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
PRIVATE_DIR_MODE = 448;
|
|
98
|
+
PRIVATE_FILE_MODE = 384;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
66
102
|
// src/lib/config.ts
|
|
67
|
-
import { readFile, writeFile
|
|
68
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
103
|
+
import { readFile, writeFile } from "fs/promises";
|
|
104
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
69
105
|
import path from "path";
|
|
70
106
|
import os from "os";
|
|
71
107
|
function resolveDataDir() {
|
|
@@ -73,7 +109,7 @@ function resolveDataDir() {
|
|
|
73
109
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
74
110
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
75
111
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
76
|
-
if (!
|
|
112
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
77
113
|
try {
|
|
78
114
|
renameSync(legacyDir, newDir);
|
|
79
115
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -136,9 +172,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
136
172
|
}
|
|
137
173
|
async function loadConfig() {
|
|
138
174
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
139
|
-
await
|
|
175
|
+
await ensurePrivateDir(dir);
|
|
140
176
|
const configPath = path.join(dir, "config.json");
|
|
141
|
-
if (!
|
|
177
|
+
if (!existsSync2(configPath)) {
|
|
142
178
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
143
179
|
}
|
|
144
180
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -151,6 +187,7 @@ async function loadConfig() {
|
|
|
151
187
|
`);
|
|
152
188
|
try {
|
|
153
189
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
190
|
+
await enforcePrivateFile(configPath);
|
|
154
191
|
} catch {
|
|
155
192
|
}
|
|
156
193
|
}
|
|
@@ -170,6 +207,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
170
207
|
var init_config = __esm({
|
|
171
208
|
"src/lib/config.ts"() {
|
|
172
209
|
"use strict";
|
|
210
|
+
init_secure_files();
|
|
173
211
|
EXE_AI_DIR = resolveDataDir();
|
|
174
212
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
175
213
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -248,7 +286,7 @@ var init_config = __esm({
|
|
|
248
286
|
|
|
249
287
|
// src/lib/employees.ts
|
|
250
288
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
251
|
-
import { existsSync as
|
|
289
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
252
290
|
import { execSync } from "child_process";
|
|
253
291
|
import path2 from "path";
|
|
254
292
|
import os2 from "os";
|
|
@@ -265,14 +303,14 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
265
303
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
266
304
|
}
|
|
267
305
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
268
|
-
if (!
|
|
306
|
+
if (!existsSync3(employeesPath)) return [];
|
|
269
307
|
try {
|
|
270
308
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
271
309
|
} catch {
|
|
272
310
|
return [];
|
|
273
311
|
}
|
|
274
312
|
}
|
|
275
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
313
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
276
314
|
var init_employees = __esm({
|
|
277
315
|
"src/lib/employees.ts"() {
|
|
278
316
|
"use strict";
|
|
@@ -280,12 +318,609 @@ var init_employees = __esm({
|
|
|
280
318
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
281
319
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
282
320
|
COORDINATOR_ROLE = "COO";
|
|
321
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// src/lib/database-adapter.ts
|
|
326
|
+
import os3 from "os";
|
|
327
|
+
import path3 from "path";
|
|
328
|
+
import { createRequire } from "module";
|
|
329
|
+
import { pathToFileURL } from "url";
|
|
330
|
+
function quotedIdentifier(identifier) {
|
|
331
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
332
|
+
}
|
|
333
|
+
function unqualifiedTableName(name) {
|
|
334
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
335
|
+
const parts = raw.split(".");
|
|
336
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
337
|
+
}
|
|
338
|
+
function stripTrailingSemicolon(sql) {
|
|
339
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
340
|
+
}
|
|
341
|
+
function appendClause(sql, clause) {
|
|
342
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
343
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
344
|
+
if (!returningMatch) {
|
|
345
|
+
return `${trimmed}${clause}`;
|
|
346
|
+
}
|
|
347
|
+
const idx = returningMatch.index;
|
|
348
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
349
|
+
}
|
|
350
|
+
function normalizeStatement(stmt) {
|
|
351
|
+
if (typeof stmt === "string") {
|
|
352
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
353
|
+
}
|
|
354
|
+
const sql = stmt.sql;
|
|
355
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
356
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
357
|
+
}
|
|
358
|
+
return { kind: "named", sql, args: stmt.args };
|
|
359
|
+
}
|
|
360
|
+
function rewriteBooleanLiterals(sql) {
|
|
361
|
+
let out = sql;
|
|
362
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
363
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
364
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
365
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
366
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
367
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
368
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
369
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
function rewriteInsertOrIgnore(sql) {
|
|
374
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
375
|
+
return sql;
|
|
376
|
+
}
|
|
377
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
378
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
379
|
+
}
|
|
380
|
+
function rewriteInsertOrReplace(sql) {
|
|
381
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
382
|
+
if (!match) {
|
|
383
|
+
return sql;
|
|
384
|
+
}
|
|
385
|
+
const rawTable = match[1];
|
|
386
|
+
const rawColumns = match[2];
|
|
387
|
+
const remainder = match[3];
|
|
388
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
389
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
390
|
+
if (!conflictKeys?.length) {
|
|
391
|
+
return sql;
|
|
392
|
+
}
|
|
393
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
394
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
395
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
396
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
397
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
398
|
+
}
|
|
399
|
+
function rewriteSql(sql) {
|
|
400
|
+
let out = sql;
|
|
401
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
402
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
403
|
+
out = rewriteBooleanLiterals(out);
|
|
404
|
+
out = rewriteInsertOrReplace(out);
|
|
405
|
+
out = rewriteInsertOrIgnore(out);
|
|
406
|
+
return stripTrailingSemicolon(out);
|
|
407
|
+
}
|
|
408
|
+
function toBoolean(value) {
|
|
409
|
+
if (value === null || value === void 0) return value;
|
|
410
|
+
if (typeof value === "boolean") return value;
|
|
411
|
+
if (typeof value === "number") return value !== 0;
|
|
412
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
413
|
+
if (typeof value === "string") {
|
|
414
|
+
const normalized = value.trim().toLowerCase();
|
|
415
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
416
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
417
|
+
}
|
|
418
|
+
return Boolean(value);
|
|
419
|
+
}
|
|
420
|
+
function countQuestionMarks(sql, end) {
|
|
421
|
+
let count = 0;
|
|
422
|
+
let inSingle = false;
|
|
423
|
+
let inDouble = false;
|
|
424
|
+
let inLineComment = false;
|
|
425
|
+
let inBlockComment = false;
|
|
426
|
+
for (let i = 0; i < end; i++) {
|
|
427
|
+
const ch = sql[i];
|
|
428
|
+
const next = sql[i + 1];
|
|
429
|
+
if (inLineComment) {
|
|
430
|
+
if (ch === "\n") inLineComment = false;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (inBlockComment) {
|
|
434
|
+
if (ch === "*" && next === "/") {
|
|
435
|
+
inBlockComment = false;
|
|
436
|
+
i += 1;
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
441
|
+
inLineComment = true;
|
|
442
|
+
i += 1;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
446
|
+
inBlockComment = true;
|
|
447
|
+
i += 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
451
|
+
inSingle = !inSingle;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
455
|
+
inDouble = !inDouble;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
459
|
+
count += 1;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return count;
|
|
463
|
+
}
|
|
464
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
465
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
466
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
467
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
468
|
+
for (const match of sql.matchAll(pattern)) {
|
|
469
|
+
const matchText = match[0];
|
|
470
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
471
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return indexes;
|
|
475
|
+
}
|
|
476
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
477
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
478
|
+
if (!match) return;
|
|
479
|
+
const rawTable = match[1];
|
|
480
|
+
const rawColumns = match[2];
|
|
481
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
482
|
+
if (!boolColumns?.size) return;
|
|
483
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
484
|
+
for (const [index, column] of columns.entries()) {
|
|
485
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
486
|
+
args[index] = toBoolean(args[index]);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
491
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
492
|
+
if (!match) return;
|
|
493
|
+
const rawTable = match[1];
|
|
494
|
+
const setClause = match[2];
|
|
495
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
496
|
+
if (!boolColumns?.size) return;
|
|
497
|
+
const assignments = setClause.split(",");
|
|
498
|
+
let placeholderIndex = 0;
|
|
499
|
+
for (const assignment of assignments) {
|
|
500
|
+
if (!assignment.includes("?")) continue;
|
|
501
|
+
placeholderIndex += 1;
|
|
502
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
503
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
504
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function coerceBooleanArgs(sql, args) {
|
|
509
|
+
const nextArgs = [...args];
|
|
510
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
511
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
512
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
513
|
+
for (const index of placeholderIndexes) {
|
|
514
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
515
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return nextArgs;
|
|
519
|
+
}
|
|
520
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
521
|
+
let out = "";
|
|
522
|
+
let placeholder = 0;
|
|
523
|
+
let inSingle = false;
|
|
524
|
+
let inDouble = false;
|
|
525
|
+
let inLineComment = false;
|
|
526
|
+
let inBlockComment = false;
|
|
527
|
+
for (let i = 0; i < sql.length; i++) {
|
|
528
|
+
const ch = sql[i];
|
|
529
|
+
const next = sql[i + 1];
|
|
530
|
+
if (inLineComment) {
|
|
531
|
+
out += ch;
|
|
532
|
+
if (ch === "\n") inLineComment = false;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (inBlockComment) {
|
|
536
|
+
out += ch;
|
|
537
|
+
if (ch === "*" && next === "/") {
|
|
538
|
+
out += next;
|
|
539
|
+
inBlockComment = false;
|
|
540
|
+
i += 1;
|
|
541
|
+
}
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
545
|
+
out += ch + next;
|
|
546
|
+
inLineComment = true;
|
|
547
|
+
i += 1;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
551
|
+
out += ch + next;
|
|
552
|
+
inBlockComment = true;
|
|
553
|
+
i += 1;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
557
|
+
inSingle = !inSingle;
|
|
558
|
+
out += ch;
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
562
|
+
inDouble = !inDouble;
|
|
563
|
+
out += ch;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
567
|
+
placeholder += 1;
|
|
568
|
+
out += `$${placeholder}`;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
out += ch;
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
function translateStatementForPostgres(stmt) {
|
|
576
|
+
const normalized = normalizeStatement(stmt);
|
|
577
|
+
if (normalized.kind === "named") {
|
|
578
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
579
|
+
}
|
|
580
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
581
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
582
|
+
return {
|
|
583
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
584
|
+
args: coercedArgs
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function shouldBypassPostgres(stmt) {
|
|
588
|
+
const normalized = normalizeStatement(stmt);
|
|
589
|
+
if (normalized.kind === "named") {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
593
|
+
}
|
|
594
|
+
function shouldFallbackOnError(error) {
|
|
595
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
596
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
597
|
+
}
|
|
598
|
+
function isReadQuery(sql) {
|
|
599
|
+
const trimmed = sql.trimStart();
|
|
600
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
601
|
+
}
|
|
602
|
+
function buildRow(row, columns) {
|
|
603
|
+
const values = columns.map((column) => row[column]);
|
|
604
|
+
return Object.assign(values, row);
|
|
605
|
+
}
|
|
606
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
607
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
608
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
609
|
+
return {
|
|
610
|
+
columns,
|
|
611
|
+
columnTypes: columns.map(() => ""),
|
|
612
|
+
rows: resultRows,
|
|
613
|
+
rowsAffected,
|
|
614
|
+
lastInsertRowid: void 0,
|
|
615
|
+
toJSON() {
|
|
616
|
+
return {
|
|
617
|
+
columns,
|
|
618
|
+
columnTypes: columns.map(() => ""),
|
|
619
|
+
rows,
|
|
620
|
+
rowsAffected,
|
|
621
|
+
lastInsertRowid: void 0
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
async function loadPrismaClient() {
|
|
627
|
+
if (!prismaClientPromise) {
|
|
628
|
+
prismaClientPromise = (async () => {
|
|
629
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
630
|
+
if (explicitPath) {
|
|
631
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
632
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
633
|
+
if (!PrismaClient2) {
|
|
634
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
635
|
+
}
|
|
636
|
+
return new PrismaClient2();
|
|
637
|
+
}
|
|
638
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
639
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
640
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
641
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
642
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
643
|
+
if (!PrismaClient) {
|
|
644
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
645
|
+
}
|
|
646
|
+
return new PrismaClient();
|
|
647
|
+
})();
|
|
648
|
+
}
|
|
649
|
+
return prismaClientPromise;
|
|
650
|
+
}
|
|
651
|
+
async function ensureCompatibilityViews(prisma) {
|
|
652
|
+
if (!compatibilityBootstrapPromise) {
|
|
653
|
+
compatibilityBootstrapPromise = (async () => {
|
|
654
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
655
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
656
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
657
|
+
"SELECT to_regclass($1) AS regclass",
|
|
658
|
+
relation
|
|
659
|
+
);
|
|
660
|
+
if (!rows[0]?.regclass) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
await prisma.$executeRawUnsafe(
|
|
664
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
})();
|
|
668
|
+
}
|
|
669
|
+
return compatibilityBootstrapPromise;
|
|
670
|
+
}
|
|
671
|
+
async function executeOnPrisma(executor, stmt) {
|
|
672
|
+
const translated = translateStatementForPostgres(stmt);
|
|
673
|
+
if (isReadQuery(translated.sql)) {
|
|
674
|
+
const rows = await executor.$queryRawUnsafe(
|
|
675
|
+
translated.sql,
|
|
676
|
+
...translated.args
|
|
677
|
+
);
|
|
678
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
679
|
+
}
|
|
680
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
681
|
+
return buildResultSet([], rowsAffected);
|
|
682
|
+
}
|
|
683
|
+
function splitSqlStatements(sql) {
|
|
684
|
+
const parts = [];
|
|
685
|
+
let current = "";
|
|
686
|
+
let inSingle = false;
|
|
687
|
+
let inDouble = false;
|
|
688
|
+
let inLineComment = false;
|
|
689
|
+
let inBlockComment = false;
|
|
690
|
+
for (let i = 0; i < sql.length; i++) {
|
|
691
|
+
const ch = sql[i];
|
|
692
|
+
const next = sql[i + 1];
|
|
693
|
+
if (inLineComment) {
|
|
694
|
+
current += ch;
|
|
695
|
+
if (ch === "\n") inLineComment = false;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (inBlockComment) {
|
|
699
|
+
current += ch;
|
|
700
|
+
if (ch === "*" && next === "/") {
|
|
701
|
+
current += next;
|
|
702
|
+
inBlockComment = false;
|
|
703
|
+
i += 1;
|
|
704
|
+
}
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
708
|
+
current += ch + next;
|
|
709
|
+
inLineComment = true;
|
|
710
|
+
i += 1;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
714
|
+
current += ch + next;
|
|
715
|
+
inBlockComment = true;
|
|
716
|
+
i += 1;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
720
|
+
inSingle = !inSingle;
|
|
721
|
+
current += ch;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
725
|
+
inDouble = !inDouble;
|
|
726
|
+
current += ch;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
730
|
+
if (current.trim()) {
|
|
731
|
+
parts.push(current.trim());
|
|
732
|
+
}
|
|
733
|
+
current = "";
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
current += ch;
|
|
737
|
+
}
|
|
738
|
+
if (current.trim()) {
|
|
739
|
+
parts.push(current.trim());
|
|
740
|
+
}
|
|
741
|
+
return parts;
|
|
742
|
+
}
|
|
743
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
744
|
+
const prisma = await loadPrismaClient();
|
|
745
|
+
await ensureCompatibilityViews(prisma);
|
|
746
|
+
let closed = false;
|
|
747
|
+
let adapter;
|
|
748
|
+
const fallbackExecute = async (stmt, error) => {
|
|
749
|
+
if (!fallbackClient) {
|
|
750
|
+
if (error) throw error;
|
|
751
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
752
|
+
}
|
|
753
|
+
if (error) {
|
|
754
|
+
process.stderr.write(
|
|
755
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
756
|
+
`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
return fallbackClient.execute(stmt);
|
|
760
|
+
};
|
|
761
|
+
adapter = {
|
|
762
|
+
async execute(stmt) {
|
|
763
|
+
if (shouldBypassPostgres(stmt)) {
|
|
764
|
+
return fallbackExecute(stmt);
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
return await executeOnPrisma(prisma, stmt);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
if (shouldFallbackOnError(error)) {
|
|
770
|
+
return fallbackExecute(stmt, error);
|
|
771
|
+
}
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
async batch(stmts, mode) {
|
|
776
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
777
|
+
if (!fallbackClient) {
|
|
778
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
779
|
+
}
|
|
780
|
+
return fallbackClient.batch(stmts, mode);
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
if (prisma.$transaction) {
|
|
784
|
+
return await prisma.$transaction(async (tx) => {
|
|
785
|
+
const results2 = [];
|
|
786
|
+
for (const stmt of stmts) {
|
|
787
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
788
|
+
}
|
|
789
|
+
return results2;
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const results = [];
|
|
793
|
+
for (const stmt of stmts) {
|
|
794
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
795
|
+
}
|
|
796
|
+
return results;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
799
|
+
process.stderr.write(
|
|
800
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
801
|
+
`
|
|
802
|
+
);
|
|
803
|
+
return fallbackClient.batch(stmts, mode);
|
|
804
|
+
}
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
async migrate(stmts) {
|
|
809
|
+
if (fallbackClient) {
|
|
810
|
+
return fallbackClient.migrate(stmts);
|
|
811
|
+
}
|
|
812
|
+
return adapter.batch(stmts, "deferred");
|
|
813
|
+
},
|
|
814
|
+
async transaction(mode) {
|
|
815
|
+
if (!fallbackClient) {
|
|
816
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
817
|
+
}
|
|
818
|
+
return fallbackClient.transaction(mode);
|
|
819
|
+
},
|
|
820
|
+
async executeMultiple(sql) {
|
|
821
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
822
|
+
return fallbackClient.executeMultiple(sql);
|
|
823
|
+
}
|
|
824
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
825
|
+
await adapter.execute(statement);
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
async sync() {
|
|
829
|
+
if (fallbackClient) {
|
|
830
|
+
return fallbackClient.sync();
|
|
831
|
+
}
|
|
832
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
833
|
+
},
|
|
834
|
+
close() {
|
|
835
|
+
closed = true;
|
|
836
|
+
prismaClientPromise = null;
|
|
837
|
+
compatibilityBootstrapPromise = null;
|
|
838
|
+
void prisma.$disconnect?.();
|
|
839
|
+
},
|
|
840
|
+
get closed() {
|
|
841
|
+
return closed;
|
|
842
|
+
},
|
|
843
|
+
get protocol() {
|
|
844
|
+
return "prisma-postgres";
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
return adapter;
|
|
848
|
+
}
|
|
849
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
850
|
+
var init_database_adapter = __esm({
|
|
851
|
+
"src/lib/database-adapter.ts"() {
|
|
852
|
+
"use strict";
|
|
853
|
+
VIEW_MAPPINGS = [
|
|
854
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
855
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
856
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
857
|
+
{ view: "entities", source: "memory.entities" },
|
|
858
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
859
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
860
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
861
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
862
|
+
{ view: "messages", source: "memory.messages" },
|
|
863
|
+
{ view: "users", source: "wiki.users" },
|
|
864
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
865
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
866
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
867
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
868
|
+
];
|
|
869
|
+
UPSERT_KEYS = {
|
|
870
|
+
memories: ["id"],
|
|
871
|
+
tasks: ["id"],
|
|
872
|
+
behaviors: ["id"],
|
|
873
|
+
entities: ["id"],
|
|
874
|
+
relationships: ["id"],
|
|
875
|
+
entity_aliases: ["alias"],
|
|
876
|
+
notifications: ["id"],
|
|
877
|
+
messages: ["id"],
|
|
878
|
+
users: ["id"],
|
|
879
|
+
workspaces: ["id"],
|
|
880
|
+
workspace_users: ["id"],
|
|
881
|
+
documents: ["id"],
|
|
882
|
+
chats: ["id"]
|
|
883
|
+
};
|
|
884
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
885
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
886
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
887
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
888
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
889
|
+
};
|
|
890
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
891
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
892
|
+
);
|
|
893
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
894
|
+
/\bPRAGMA\b/i,
|
|
895
|
+
/\bsqlite_master\b/i,
|
|
896
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
897
|
+
/\bMATCH\b/i,
|
|
898
|
+
/\bvector_distance_cos\s*\(/i,
|
|
899
|
+
/\bjson_extract\s*\(/i,
|
|
900
|
+
/\bjulianday\s*\(/i,
|
|
901
|
+
/\bstrftime\s*\(/i,
|
|
902
|
+
/\blast_insert_rowid\s*\(/i
|
|
903
|
+
];
|
|
904
|
+
prismaClientPromise = null;
|
|
905
|
+
compatibilityBootstrapPromise = null;
|
|
283
906
|
}
|
|
284
907
|
});
|
|
285
908
|
|
|
286
909
|
// src/lib/database.ts
|
|
287
910
|
import { createClient } from "@libsql/client";
|
|
288
911
|
async function initDatabase(config) {
|
|
912
|
+
if (_walCheckpointTimer) {
|
|
913
|
+
clearInterval(_walCheckpointTimer);
|
|
914
|
+
_walCheckpointTimer = null;
|
|
915
|
+
}
|
|
916
|
+
if (_daemonClient) {
|
|
917
|
+
_daemonClient.close();
|
|
918
|
+
_daemonClient = null;
|
|
919
|
+
}
|
|
920
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
921
|
+
_adapterClient.close();
|
|
922
|
+
}
|
|
923
|
+
_adapterClient = null;
|
|
289
924
|
if (_client) {
|
|
290
925
|
_client.close();
|
|
291
926
|
_client = null;
|
|
@@ -299,6 +934,7 @@ async function initDatabase(config) {
|
|
|
299
934
|
}
|
|
300
935
|
_client = createClient(opts);
|
|
301
936
|
_resilientClient = wrapWithRetry(_client);
|
|
937
|
+
_adapterClient = _resilientClient;
|
|
302
938
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
303
939
|
});
|
|
304
940
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -309,11 +945,17 @@ async function initDatabase(config) {
|
|
|
309
945
|
});
|
|
310
946
|
}, 3e4);
|
|
311
947
|
_walCheckpointTimer.unref();
|
|
948
|
+
if (process.env.DATABASE_URL) {
|
|
949
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
950
|
+
}
|
|
312
951
|
}
|
|
313
952
|
function getClient() {
|
|
314
|
-
if (!
|
|
953
|
+
if (!_adapterClient) {
|
|
315
954
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
316
955
|
}
|
|
956
|
+
if (process.env.DATABASE_URL) {
|
|
957
|
+
return _adapterClient;
|
|
958
|
+
}
|
|
317
959
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
318
960
|
return _resilientClient;
|
|
319
961
|
}
|
|
@@ -606,6 +1248,7 @@ async function ensureSchema() {
|
|
|
606
1248
|
project TEXT NOT NULL,
|
|
607
1249
|
summary TEXT NOT NULL,
|
|
608
1250
|
task_file TEXT,
|
|
1251
|
+
session_scope TEXT,
|
|
609
1252
|
read INTEGER NOT NULL DEFAULT 0,
|
|
610
1253
|
created_at TEXT NOT NULL
|
|
611
1254
|
);
|
|
@@ -614,7 +1257,7 @@ async function ensureSchema() {
|
|
|
614
1257
|
ON notifications(read);
|
|
615
1258
|
|
|
616
1259
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
617
|
-
ON notifications(agent_id);
|
|
1260
|
+
ON notifications(agent_id, session_scope);
|
|
618
1261
|
|
|
619
1262
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
620
1263
|
ON notifications(task_file);
|
|
@@ -652,6 +1295,7 @@ async function ensureSchema() {
|
|
|
652
1295
|
target_agent TEXT NOT NULL,
|
|
653
1296
|
target_project TEXT,
|
|
654
1297
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1298
|
+
session_scope TEXT,
|
|
655
1299
|
content TEXT NOT NULL,
|
|
656
1300
|
priority TEXT DEFAULT 'normal',
|
|
657
1301
|
status TEXT DEFAULT 'pending',
|
|
@@ -665,10 +1309,31 @@ async function ensureSchema() {
|
|
|
665
1309
|
);
|
|
666
1310
|
|
|
667
1311
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
668
|
-
ON messages(target_agent, status);
|
|
1312
|
+
ON messages(target_agent, session_scope, status);
|
|
669
1313
|
|
|
670
1314
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
671
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1315
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1316
|
+
`);
|
|
1317
|
+
try {
|
|
1318
|
+
await client.execute({
|
|
1319
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1320
|
+
args: []
|
|
1321
|
+
});
|
|
1322
|
+
} catch {
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
await client.execute({
|
|
1326
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1327
|
+
args: []
|
|
1328
|
+
});
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
await client.executeMultiple(`
|
|
1332
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1333
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1334
|
+
|
|
1335
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1336
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
672
1337
|
`);
|
|
673
1338
|
try {
|
|
674
1339
|
await client.execute({
|
|
@@ -1252,17 +1917,26 @@ async function ensureSchema() {
|
|
|
1252
1917
|
} catch {
|
|
1253
1918
|
}
|
|
1254
1919
|
}
|
|
1920
|
+
try {
|
|
1921
|
+
await client.execute({
|
|
1922
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1923
|
+
args: []
|
|
1924
|
+
});
|
|
1925
|
+
} catch {
|
|
1926
|
+
}
|
|
1255
1927
|
}
|
|
1256
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
1928
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1257
1929
|
var init_database = __esm({
|
|
1258
1930
|
"src/lib/database.ts"() {
|
|
1259
1931
|
"use strict";
|
|
1260
1932
|
init_db_retry();
|
|
1261
1933
|
init_employees();
|
|
1934
|
+
init_database_adapter();
|
|
1262
1935
|
_client = null;
|
|
1263
1936
|
_resilientClient = null;
|
|
1264
1937
|
_walCheckpointTimer = null;
|
|
1265
1938
|
_daemonClient = null;
|
|
1939
|
+
_adapterClient = null;
|
|
1266
1940
|
initTurso = initDatabase;
|
|
1267
1941
|
}
|
|
1268
1942
|
});
|
|
@@ -1327,6 +2001,7 @@ var shard_manager_exports = {};
|
|
|
1327
2001
|
__export(shard_manager_exports, {
|
|
1328
2002
|
disposeShards: () => disposeShards,
|
|
1329
2003
|
ensureShardSchema: () => ensureShardSchema,
|
|
2004
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1330
2005
|
getReadyShardClient: () => getReadyShardClient,
|
|
1331
2006
|
getShardClient: () => getShardClient,
|
|
1332
2007
|
getShardsDir: () => getShardsDir,
|
|
@@ -1335,15 +2010,18 @@ __export(shard_manager_exports, {
|
|
|
1335
2010
|
listShards: () => listShards,
|
|
1336
2011
|
shardExists: () => shardExists
|
|
1337
2012
|
});
|
|
1338
|
-
import
|
|
1339
|
-
import { existsSync as
|
|
2013
|
+
import path5 from "path";
|
|
2014
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1340
2015
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1341
2016
|
function initShardManager(encryptionKey) {
|
|
1342
2017
|
_encryptionKey = encryptionKey;
|
|
1343
|
-
if (!
|
|
1344
|
-
|
|
2018
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
2019
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1345
2020
|
}
|
|
1346
2021
|
_shardingEnabled = true;
|
|
2022
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2023
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2024
|
+
_evictionTimer.unref();
|
|
1347
2025
|
}
|
|
1348
2026
|
function isShardingEnabled() {
|
|
1349
2027
|
return _shardingEnabled;
|
|
@@ -1360,21 +2038,28 @@ function getShardClient(projectName) {
|
|
|
1360
2038
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1361
2039
|
}
|
|
1362
2040
|
const cached = _shards.get(safeName);
|
|
1363
|
-
if (cached)
|
|
1364
|
-
|
|
2041
|
+
if (cached) {
|
|
2042
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2043
|
+
return cached;
|
|
2044
|
+
}
|
|
2045
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2046
|
+
evictLRU();
|
|
2047
|
+
}
|
|
2048
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1365
2049
|
const client = createClient2({
|
|
1366
2050
|
url: `file:${dbPath}`,
|
|
1367
2051
|
encryptionKey: _encryptionKey
|
|
1368
2052
|
});
|
|
1369
2053
|
_shards.set(safeName, client);
|
|
2054
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1370
2055
|
return client;
|
|
1371
2056
|
}
|
|
1372
2057
|
function shardExists(projectName) {
|
|
1373
2058
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1374
|
-
return
|
|
2059
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1375
2060
|
}
|
|
1376
2061
|
function listShards() {
|
|
1377
|
-
if (!
|
|
2062
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1378
2063
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1379
2064
|
}
|
|
1380
2065
|
async function ensureShardSchema(client) {
|
|
@@ -1426,6 +2111,8 @@ async function ensureShardSchema(client) {
|
|
|
1426
2111
|
for (const col of [
|
|
1427
2112
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1428
2113
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2114
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2115
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1429
2116
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1430
2117
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1431
2118
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1448,7 +2135,23 @@ async function ensureShardSchema(client) {
|
|
|
1448
2135
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1449
2136
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1450
2137
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1451
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2138
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2139
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2140
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2141
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2142
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2143
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2144
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2145
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2146
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2147
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2148
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2149
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2150
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2151
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2152
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2153
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2154
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1452
2155
|
]) {
|
|
1453
2156
|
try {
|
|
1454
2157
|
await client.execute(col);
|
|
@@ -1547,21 +2250,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1547
2250
|
await ensureShardSchema(client);
|
|
1548
2251
|
return client;
|
|
1549
2252
|
}
|
|
2253
|
+
function evictLRU() {
|
|
2254
|
+
let oldest = null;
|
|
2255
|
+
let oldestTime = Infinity;
|
|
2256
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2257
|
+
if (time < oldestTime) {
|
|
2258
|
+
oldestTime = time;
|
|
2259
|
+
oldest = name;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
if (oldest) {
|
|
2263
|
+
const client = _shards.get(oldest);
|
|
2264
|
+
if (client) {
|
|
2265
|
+
client.close();
|
|
2266
|
+
}
|
|
2267
|
+
_shards.delete(oldest);
|
|
2268
|
+
_shardLastAccess.delete(oldest);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
function evictIdleShards() {
|
|
2272
|
+
const now = Date.now();
|
|
2273
|
+
const toEvict = [];
|
|
2274
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2275
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2276
|
+
toEvict.push(name);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
for (const name of toEvict) {
|
|
2280
|
+
const client = _shards.get(name);
|
|
2281
|
+
if (client) {
|
|
2282
|
+
client.close();
|
|
2283
|
+
}
|
|
2284
|
+
_shards.delete(name);
|
|
2285
|
+
_shardLastAccess.delete(name);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function getOpenShardCount() {
|
|
2289
|
+
return _shards.size;
|
|
2290
|
+
}
|
|
1550
2291
|
function disposeShards() {
|
|
2292
|
+
if (_evictionTimer) {
|
|
2293
|
+
clearInterval(_evictionTimer);
|
|
2294
|
+
_evictionTimer = null;
|
|
2295
|
+
}
|
|
1551
2296
|
for (const [, client] of _shards) {
|
|
1552
2297
|
client.close();
|
|
1553
2298
|
}
|
|
1554
2299
|
_shards.clear();
|
|
2300
|
+
_shardLastAccess.clear();
|
|
1555
2301
|
_shardingEnabled = false;
|
|
1556
2302
|
_encryptionKey = null;
|
|
1557
2303
|
}
|
|
1558
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2304
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1559
2305
|
var init_shard_manager = __esm({
|
|
1560
2306
|
"src/lib/shard-manager.ts"() {
|
|
1561
2307
|
"use strict";
|
|
1562
2308
|
init_config();
|
|
1563
|
-
SHARDS_DIR =
|
|
2309
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2310
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2311
|
+
MAX_OPEN_SHARDS = 10;
|
|
2312
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1564
2313
|
_shards = /* @__PURE__ */ new Map();
|
|
2314
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2315
|
+
_evictionTimer = null;
|
|
1565
2316
|
_encryptionKey = null;
|
|
1566
2317
|
_shardingEnabled = false;
|
|
1567
2318
|
}
|
|
@@ -1755,28 +2506,188 @@ ${p.content}`).join("\n\n");
|
|
|
1755
2506
|
});
|
|
1756
2507
|
|
|
1757
2508
|
// src/lib/session-registry.ts
|
|
1758
|
-
import
|
|
1759
|
-
import
|
|
2509
|
+
import path6 from "path";
|
|
2510
|
+
import os5 from "os";
|
|
1760
2511
|
var REGISTRY_PATH;
|
|
1761
2512
|
var init_session_registry = __esm({
|
|
1762
2513
|
"src/lib/session-registry.ts"() {
|
|
1763
2514
|
"use strict";
|
|
1764
|
-
REGISTRY_PATH =
|
|
2515
|
+
REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
1765
2516
|
}
|
|
1766
2517
|
});
|
|
1767
2518
|
|
|
1768
2519
|
// src/lib/session-key.ts
|
|
1769
2520
|
import { execSync as execSync2 } from "child_process";
|
|
2521
|
+
function normalizeCommand(command) {
|
|
2522
|
+
const trimmed = command.trim().toLowerCase();
|
|
2523
|
+
const parts = trimmed.split(/[\\/]/);
|
|
2524
|
+
return parts[parts.length - 1] ?? trimmed;
|
|
2525
|
+
}
|
|
2526
|
+
function detectRuntimeFromCommand(command) {
|
|
2527
|
+
const normalized = normalizeCommand(command);
|
|
2528
|
+
for (const [runtime, commands] of Object.entries(RUNTIME_COMMANDS)) {
|
|
2529
|
+
if (commands.includes(normalized)) {
|
|
2530
|
+
return runtime;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return null;
|
|
2534
|
+
}
|
|
2535
|
+
function resolveRuntimeProcess() {
|
|
2536
|
+
let pid = process.ppid;
|
|
2537
|
+
for (let i = 0; i < 10; i++) {
|
|
2538
|
+
try {
|
|
2539
|
+
const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
|
|
2540
|
+
encoding: "utf8",
|
|
2541
|
+
timeout: 2e3
|
|
2542
|
+
}).trim();
|
|
2543
|
+
const match = info.match(/^\s*(\d+)\s+(.+)$/);
|
|
2544
|
+
if (!match) break;
|
|
2545
|
+
const [, ppid, cmd] = match;
|
|
2546
|
+
const runtime = detectRuntimeFromCommand(cmd ?? "");
|
|
2547
|
+
if (runtime) {
|
|
2548
|
+
return { pid: String(pid), runtime };
|
|
2549
|
+
}
|
|
2550
|
+
pid = parseInt(ppid, 10);
|
|
2551
|
+
if (pid <= 1) break;
|
|
2552
|
+
} catch {
|
|
2553
|
+
break;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
function getSessionKey() {
|
|
2559
|
+
if (_cached) return _cached;
|
|
2560
|
+
if (process.env.EXE_SESSION_KEY) {
|
|
2561
|
+
_cached = process.env.EXE_SESSION_KEY;
|
|
2562
|
+
return _cached;
|
|
2563
|
+
}
|
|
2564
|
+
const resolved = resolveRuntimeProcess();
|
|
2565
|
+
if (resolved) {
|
|
2566
|
+
_cachedRuntime = resolved.runtime;
|
|
2567
|
+
_cached = resolved.pid;
|
|
2568
|
+
return _cached;
|
|
2569
|
+
}
|
|
2570
|
+
_cached = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
|
|
2571
|
+
return _cached;
|
|
2572
|
+
}
|
|
2573
|
+
var _cached, _cachedRuntime, RUNTIME_COMMANDS;
|
|
1770
2574
|
var init_session_key = __esm({
|
|
1771
2575
|
"src/lib/session-key.ts"() {
|
|
1772
2576
|
"use strict";
|
|
2577
|
+
_cached = null;
|
|
2578
|
+
_cachedRuntime = null;
|
|
2579
|
+
RUNTIME_COMMANDS = {
|
|
2580
|
+
claude: ["claude", "claude.exe", "claude-native"],
|
|
2581
|
+
codex: ["codex"],
|
|
2582
|
+
opencode: ["opencode"]
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
|
|
2587
|
+
// src/lib/tmux-transport.ts
|
|
2588
|
+
var tmux_transport_exports = {};
|
|
2589
|
+
__export(tmux_transport_exports, {
|
|
2590
|
+
TmuxTransport: () => TmuxTransport
|
|
2591
|
+
});
|
|
2592
|
+
import { execFileSync } from "child_process";
|
|
2593
|
+
var QUIET, TmuxTransport;
|
|
2594
|
+
var init_tmux_transport = __esm({
|
|
2595
|
+
"src/lib/tmux-transport.ts"() {
|
|
2596
|
+
"use strict";
|
|
2597
|
+
QUIET = {
|
|
2598
|
+
encoding: "utf8",
|
|
2599
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2600
|
+
};
|
|
2601
|
+
TmuxTransport = class {
|
|
2602
|
+
getMySession() {
|
|
2603
|
+
try {
|
|
2604
|
+
return execFileSync("tmux", ["display-message", "-p", "#{session_name}"], QUIET).trim() || null;
|
|
2605
|
+
} catch {
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
listSessions() {
|
|
2610
|
+
try {
|
|
2611
|
+
return execFileSync("tmux", ["list-sessions", "-F", "#{session_name}"], QUIET).trim().split("\n").filter(Boolean);
|
|
2612
|
+
} catch {
|
|
2613
|
+
return [];
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
isAlive(target) {
|
|
2617
|
+
try {
|
|
2618
|
+
const sessions = this.listSessions();
|
|
2619
|
+
if (!sessions.includes(target)) return false;
|
|
2620
|
+
const paneStatus = execFileSync(
|
|
2621
|
+
"tmux",
|
|
2622
|
+
["list-panes", "-t", target, "-F", "#{pane_dead}"],
|
|
2623
|
+
QUIET
|
|
2624
|
+
).trim();
|
|
2625
|
+
return paneStatus !== "1";
|
|
2626
|
+
} catch {
|
|
2627
|
+
return false;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
sendKeys(target, keys) {
|
|
2631
|
+
execFileSync("tmux", ["send-keys", "-t", target, keys, "Enter"], QUIET);
|
|
2632
|
+
}
|
|
2633
|
+
capturePane(target, lines) {
|
|
2634
|
+
const args = ["capture-pane", "-t", target, "-p"];
|
|
2635
|
+
if (lines) args.push("-S", `-${lines}`);
|
|
2636
|
+
return execFileSync("tmux", args, { ...QUIET, timeout: 3e3 });
|
|
2637
|
+
}
|
|
2638
|
+
isPaneInCopyMode(target) {
|
|
2639
|
+
try {
|
|
2640
|
+
const result = execFileSync(
|
|
2641
|
+
"tmux",
|
|
2642
|
+
["display-message", "-p", "-t", target, "#{pane_in_mode}"],
|
|
2643
|
+
{ ...QUIET, timeout: 3e3 }
|
|
2644
|
+
).trim();
|
|
2645
|
+
return result === "1";
|
|
2646
|
+
} catch {
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
spawn(name, config) {
|
|
2651
|
+
try {
|
|
2652
|
+
const args = ["new-session", "-d", "-s", name];
|
|
2653
|
+
if (config.cwd) args.push("-c", config.cwd);
|
|
2654
|
+
args.push(config.command);
|
|
2655
|
+
execFileSync("tmux", args);
|
|
2656
|
+
return { sessionName: name };
|
|
2657
|
+
} catch (e) {
|
|
2658
|
+
return { sessionName: name, error: `spawn failed: ${e}` };
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
kill(target) {
|
|
2662
|
+
try {
|
|
2663
|
+
execFileSync("tmux", ["kill-session", "-t", target], QUIET);
|
|
2664
|
+
} catch {
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
pipeLog(target, logFile) {
|
|
2668
|
+
try {
|
|
2669
|
+
const safePath = logFile.replace(/'/g, "'\\''");
|
|
2670
|
+
execFileSync("tmux", ["pipe-pane", "-t", target, `cat >> '${safePath}'`], QUIET);
|
|
2671
|
+
} catch {
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
1773
2675
|
}
|
|
1774
2676
|
});
|
|
1775
2677
|
|
|
1776
2678
|
// src/lib/transport.ts
|
|
2679
|
+
function getTransport() {
|
|
2680
|
+
if (!_transport) {
|
|
2681
|
+
const { TmuxTransport: TmuxTransport2 } = (init_tmux_transport(), __toCommonJS(tmux_transport_exports));
|
|
2682
|
+
_transport = new TmuxTransport2();
|
|
2683
|
+
}
|
|
2684
|
+
return _transport;
|
|
2685
|
+
}
|
|
2686
|
+
var _transport;
|
|
1777
2687
|
var init_transport = __esm({
|
|
1778
2688
|
"src/lib/transport.ts"() {
|
|
1779
2689
|
"use strict";
|
|
2690
|
+
_transport = null;
|
|
1780
2691
|
}
|
|
1781
2692
|
});
|
|
1782
2693
|
|
|
@@ -1836,15 +2747,16 @@ var init_runtime_table = __esm({
|
|
|
1836
2747
|
});
|
|
1837
2748
|
|
|
1838
2749
|
// src/lib/agent-config.ts
|
|
1839
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
1840
|
-
import
|
|
2750
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6 } from "fs";
|
|
2751
|
+
import path7 from "path";
|
|
1841
2752
|
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
1842
2753
|
var init_agent_config = __esm({
|
|
1843
2754
|
"src/lib/agent-config.ts"() {
|
|
1844
2755
|
"use strict";
|
|
1845
2756
|
init_config();
|
|
1846
2757
|
init_runtime_table();
|
|
1847
|
-
|
|
2758
|
+
init_secure_files();
|
|
2759
|
+
AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
|
|
1848
2760
|
DEFAULT_MODELS = {
|
|
1849
2761
|
claude: "claude-opus-4",
|
|
1850
2762
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -1854,38 +2766,41 @@ var init_agent_config = __esm({
|
|
|
1854
2766
|
});
|
|
1855
2767
|
|
|
1856
2768
|
// src/lib/intercom-queue.ts
|
|
1857
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as
|
|
1858
|
-
import
|
|
1859
|
-
import
|
|
2769
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2770
|
+
import path8 from "path";
|
|
2771
|
+
import os6 from "os";
|
|
1860
2772
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
1861
2773
|
var init_intercom_queue = __esm({
|
|
1862
2774
|
"src/lib/intercom-queue.ts"() {
|
|
1863
2775
|
"use strict";
|
|
1864
|
-
QUEUE_PATH =
|
|
2776
|
+
QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
1865
2777
|
TTL_MS = 60 * 60 * 1e3;
|
|
1866
|
-
INTERCOM_LOG =
|
|
2778
|
+
INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
1867
2779
|
}
|
|
1868
2780
|
});
|
|
1869
2781
|
|
|
1870
2782
|
// src/lib/license.ts
|
|
1871
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as
|
|
2783
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
1872
2784
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1873
|
-
import
|
|
2785
|
+
import { createRequire as createRequire2 } from "module";
|
|
2786
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2787
|
+
import os7 from "os";
|
|
2788
|
+
import path9 from "path";
|
|
1874
2789
|
import { jwtVerify, importSPKI } from "jose";
|
|
1875
2790
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
1876
2791
|
var init_license = __esm({
|
|
1877
2792
|
"src/lib/license.ts"() {
|
|
1878
2793
|
"use strict";
|
|
1879
2794
|
init_config();
|
|
1880
|
-
LICENSE_PATH =
|
|
1881
|
-
CACHE_PATH =
|
|
1882
|
-
DEVICE_ID_PATH =
|
|
2795
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
2796
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2797
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
1883
2798
|
}
|
|
1884
2799
|
});
|
|
1885
2800
|
|
|
1886
2801
|
// src/lib/plan-limits.ts
|
|
1887
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
1888
|
-
import
|
|
2802
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
2803
|
+
import path10 from "path";
|
|
1889
2804
|
var CACHE_PATH2;
|
|
1890
2805
|
var init_plan_limits = __esm({
|
|
1891
2806
|
"src/lib/plan-limits.ts"() {
|
|
@@ -1894,14 +2809,54 @@ var init_plan_limits = __esm({
|
|
|
1894
2809
|
init_employees();
|
|
1895
2810
|
init_license();
|
|
1896
2811
|
init_config();
|
|
1897
|
-
CACHE_PATH2 =
|
|
2812
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
1898
2813
|
}
|
|
1899
2814
|
});
|
|
1900
2815
|
|
|
1901
2816
|
// src/lib/tmux-routing.ts
|
|
1902
|
-
import
|
|
1903
|
-
import
|
|
2817
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
|
|
2818
|
+
import path11 from "path";
|
|
2819
|
+
import os8 from "os";
|
|
1904
2820
|
import { fileURLToPath } from "url";
|
|
2821
|
+
function getMySession() {
|
|
2822
|
+
return getTransport().getMySession();
|
|
2823
|
+
}
|
|
2824
|
+
function extractRootExe(name) {
|
|
2825
|
+
if (!name) return null;
|
|
2826
|
+
if (!name.includes("-")) return name;
|
|
2827
|
+
const parts = name.split("-").filter(Boolean);
|
|
2828
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2829
|
+
}
|
|
2830
|
+
function getParentExe(sessionKey) {
|
|
2831
|
+
try {
|
|
2832
|
+
const data = JSON.parse(readFileSync7(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2833
|
+
return data.parentExe || null;
|
|
2834
|
+
} catch {
|
|
2835
|
+
return null;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function resolveExeSession() {
|
|
2839
|
+
const mySession = getMySession();
|
|
2840
|
+
if (!mySession) return null;
|
|
2841
|
+
const fromSessionName = extractRootExe(mySession);
|
|
2842
|
+
try {
|
|
2843
|
+
const key = getSessionKey();
|
|
2844
|
+
const parentExe = getParentExe(key);
|
|
2845
|
+
if (parentExe) {
|
|
2846
|
+
const fromCache = extractRootExe(parentExe) ?? parentExe;
|
|
2847
|
+
if (fromSessionName && fromCache !== fromSessionName) {
|
|
2848
|
+
process.stderr.write(
|
|
2849
|
+
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
2850
|
+
`
|
|
2851
|
+
);
|
|
2852
|
+
return fromSessionName;
|
|
2853
|
+
}
|
|
2854
|
+
return fromCache;
|
|
2855
|
+
}
|
|
2856
|
+
} catch {
|
|
2857
|
+
}
|
|
2858
|
+
return fromSessionName ?? mySession;
|
|
2859
|
+
}
|
|
1905
2860
|
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1906
2861
|
var init_tmux_routing = __esm({
|
|
1907
2862
|
"src/lib/tmux-routing.ts"() {
|
|
@@ -1917,30 +2872,54 @@ var init_tmux_routing = __esm({
|
|
|
1917
2872
|
init_intercom_queue();
|
|
1918
2873
|
init_plan_limits();
|
|
1919
2874
|
init_employees();
|
|
1920
|
-
SPAWN_LOCK_DIR =
|
|
1921
|
-
SESSION_CACHE =
|
|
1922
|
-
INTERCOM_LOG2 =
|
|
1923
|
-
DEBOUNCE_FILE =
|
|
2875
|
+
SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
2876
|
+
SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
|
|
2877
|
+
INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
2878
|
+
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
1924
2879
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
1925
2880
|
}
|
|
1926
2881
|
});
|
|
1927
2882
|
|
|
2883
|
+
// src/lib/task-scope.ts
|
|
2884
|
+
function getCurrentSessionScope() {
|
|
2885
|
+
try {
|
|
2886
|
+
return resolveExeSession();
|
|
2887
|
+
} catch {
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2892
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2893
|
+
if (!scope) return { sql: "", args: [] };
|
|
2894
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2895
|
+
return {
|
|
2896
|
+
sql: ` AND ${col} = ?`,
|
|
2897
|
+
args: [scope]
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
var init_task_scope = __esm({
|
|
2901
|
+
"src/lib/task-scope.ts"() {
|
|
2902
|
+
"use strict";
|
|
2903
|
+
init_tmux_routing();
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
1928
2907
|
// src/lib/store.ts
|
|
1929
2908
|
import { createHash } from "crypto";
|
|
1930
2909
|
init_database();
|
|
1931
2910
|
|
|
1932
2911
|
// src/lib/keychain.ts
|
|
1933
2912
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1934
|
-
import { existsSync as
|
|
1935
|
-
import
|
|
1936
|
-
import
|
|
2913
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2914
|
+
import path4 from "path";
|
|
2915
|
+
import os4 from "os";
|
|
1937
2916
|
var SERVICE = "exe-mem";
|
|
1938
2917
|
var ACCOUNT = "master-key";
|
|
1939
2918
|
function getKeyDir() {
|
|
1940
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2919
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
1941
2920
|
}
|
|
1942
2921
|
function getKeyPath() {
|
|
1943
|
-
return
|
|
2922
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1944
2923
|
}
|
|
1945
2924
|
async function tryKeytar() {
|
|
1946
2925
|
try {
|
|
@@ -1961,9 +2940,9 @@ async function getMasterKey() {
|
|
|
1961
2940
|
}
|
|
1962
2941
|
}
|
|
1963
2942
|
const keyPath = getKeyPath();
|
|
1964
|
-
if (!
|
|
2943
|
+
if (!existsSync4(keyPath)) {
|
|
1965
2944
|
process.stderr.write(
|
|
1966
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2945
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1967
2946
|
`
|
|
1968
2947
|
);
|
|
1969
2948
|
return null;
|
|
@@ -2065,6 +3044,7 @@ async function initStore(options) {
|
|
|
2065
3044
|
// src/lib/messaging.ts
|
|
2066
3045
|
init_database();
|
|
2067
3046
|
init_tmux_routing();
|
|
3047
|
+
init_task_scope();
|
|
2068
3048
|
import crypto from "crypto";
|
|
2069
3049
|
function rowToMessage(row) {
|
|
2070
3050
|
return {
|
|
@@ -2074,6 +3054,7 @@ function rowToMessage(row) {
|
|
|
2074
3054
|
targetAgent: row.target_agent,
|
|
2075
3055
|
targetProject: row.target_project ?? null,
|
|
2076
3056
|
targetDevice: row.target_device,
|
|
3057
|
+
sessionScope: row.session_scope ?? null,
|
|
2077
3058
|
content: row.content,
|
|
2078
3059
|
priority: row.priority ?? "normal",
|
|
2079
3060
|
status: row.status ?? "pending",
|
|
@@ -2086,21 +3067,24 @@ function rowToMessage(row) {
|
|
|
2086
3067
|
failureReason: row.failure_reason ?? null
|
|
2087
3068
|
};
|
|
2088
3069
|
}
|
|
2089
|
-
async function getPendingMessages(targetAgent) {
|
|
3070
|
+
async function getPendingMessages(targetAgent, sessionScope) {
|
|
2090
3071
|
const client = getClient();
|
|
3072
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2091
3073
|
const result = await client.execute({
|
|
2092
3074
|
sql: `SELECT * FROM messages
|
|
2093
|
-
WHERE target_agent = ? AND status IN ('pending', 'delivered')
|
|
3075
|
+
WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
|
|
2094
3076
|
ORDER BY id`,
|
|
2095
|
-
args: [targetAgent]
|
|
3077
|
+
args: [targetAgent, ...scope.args]
|
|
2096
3078
|
});
|
|
2097
3079
|
return result.rows.map((row) => rowToMessage(row));
|
|
2098
3080
|
}
|
|
2099
|
-
async function markRead(messageId) {
|
|
3081
|
+
async function markRead(messageId, sessionScope) {
|
|
2100
3082
|
const client = getClient();
|
|
3083
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2101
3084
|
await client.execute({
|
|
2102
|
-
sql:
|
|
2103
|
-
|
|
3085
|
+
sql: `UPDATE messages SET status = 'read'
|
|
3086
|
+
WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
|
|
3087
|
+
args: [messageId, ...scope.args]
|
|
2104
3088
|
});
|
|
2105
3089
|
}
|
|
2106
3090
|
|