@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
|
@@ -70,9 +70,34 @@ var init_db_retry = __esm({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
// src/lib/secure-files.ts
|
|
74
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
75
|
+
import { chmod, mkdir } from "fs/promises";
|
|
76
|
+
async function ensurePrivateDir(dirPath) {
|
|
77
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
78
|
+
try {
|
|
79
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function enforcePrivateFile(filePath) {
|
|
84
|
+
try {
|
|
85
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
90
|
+
var init_secure_files = __esm({
|
|
91
|
+
"src/lib/secure-files.ts"() {
|
|
92
|
+
"use strict";
|
|
93
|
+
PRIVATE_DIR_MODE = 448;
|
|
94
|
+
PRIVATE_FILE_MODE = 384;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
73
98
|
// src/lib/config.ts
|
|
74
|
-
import { readFile, writeFile
|
|
75
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
99
|
+
import { readFile, writeFile } from "fs/promises";
|
|
100
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
76
101
|
import path from "path";
|
|
77
102
|
import os from "os";
|
|
78
103
|
function resolveDataDir() {
|
|
@@ -80,7 +105,7 @@ function resolveDataDir() {
|
|
|
80
105
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
81
106
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
82
107
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
83
|
-
if (!
|
|
108
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
84
109
|
try {
|
|
85
110
|
renameSync(legacyDir, newDir);
|
|
86
111
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -143,9 +168,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
143
168
|
}
|
|
144
169
|
async function loadConfig() {
|
|
145
170
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
146
|
-
await
|
|
171
|
+
await ensurePrivateDir(dir);
|
|
147
172
|
const configPath = path.join(dir, "config.json");
|
|
148
|
-
if (!
|
|
173
|
+
if (!existsSync2(configPath)) {
|
|
149
174
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
150
175
|
}
|
|
151
176
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -158,6 +183,7 @@ async function loadConfig() {
|
|
|
158
183
|
`);
|
|
159
184
|
try {
|
|
160
185
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
186
|
+
await enforcePrivateFile(configPath);
|
|
161
187
|
} catch {
|
|
162
188
|
}
|
|
163
189
|
}
|
|
@@ -177,6 +203,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
177
203
|
var init_config = __esm({
|
|
178
204
|
"src/lib/config.ts"() {
|
|
179
205
|
"use strict";
|
|
206
|
+
init_secure_files();
|
|
180
207
|
EXE_AI_DIR = resolveDataDir();
|
|
181
208
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
182
209
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -255,7 +282,7 @@ var init_config = __esm({
|
|
|
255
282
|
|
|
256
283
|
// src/lib/employees.ts
|
|
257
284
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
258
|
-
import { existsSync as
|
|
285
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
259
286
|
import { execSync } from "child_process";
|
|
260
287
|
import path2 from "path";
|
|
261
288
|
import os2 from "os";
|
|
@@ -272,7 +299,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
272
299
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
273
300
|
}
|
|
274
301
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
275
|
-
if (!
|
|
302
|
+
if (!existsSync3(employeesPath)) return [];
|
|
276
303
|
try {
|
|
277
304
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
278
305
|
} catch {
|
|
@@ -290,7 +317,7 @@ function baseAgentName(name, employees) {
|
|
|
290
317
|
if (getEmployee(roster, base)) return base;
|
|
291
318
|
return name;
|
|
292
319
|
}
|
|
293
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
|
|
320
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
294
321
|
var init_employees = __esm({
|
|
295
322
|
"src/lib/employees.ts"() {
|
|
296
323
|
"use strict";
|
|
@@ -298,12 +325,609 @@ var init_employees = __esm({
|
|
|
298
325
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
299
326
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
300
327
|
COORDINATOR_ROLE = "COO";
|
|
328
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// src/lib/database-adapter.ts
|
|
333
|
+
import os3 from "os";
|
|
334
|
+
import path3 from "path";
|
|
335
|
+
import { createRequire } from "module";
|
|
336
|
+
import { pathToFileURL } from "url";
|
|
337
|
+
function quotedIdentifier(identifier) {
|
|
338
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
339
|
+
}
|
|
340
|
+
function unqualifiedTableName(name) {
|
|
341
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
342
|
+
const parts = raw.split(".");
|
|
343
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
344
|
+
}
|
|
345
|
+
function stripTrailingSemicolon(sql) {
|
|
346
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
347
|
+
}
|
|
348
|
+
function appendClause(sql, clause) {
|
|
349
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
350
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
351
|
+
if (!returningMatch) {
|
|
352
|
+
return `${trimmed}${clause}`;
|
|
353
|
+
}
|
|
354
|
+
const idx = returningMatch.index;
|
|
355
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
356
|
+
}
|
|
357
|
+
function normalizeStatement(stmt) {
|
|
358
|
+
if (typeof stmt === "string") {
|
|
359
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
360
|
+
}
|
|
361
|
+
const sql = stmt.sql;
|
|
362
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
363
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
364
|
+
}
|
|
365
|
+
return { kind: "named", sql, args: stmt.args };
|
|
366
|
+
}
|
|
367
|
+
function rewriteBooleanLiterals(sql) {
|
|
368
|
+
let out = sql;
|
|
369
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
370
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
371
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
372
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
373
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
374
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
375
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
376
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
function rewriteInsertOrIgnore(sql) {
|
|
381
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
382
|
+
return sql;
|
|
383
|
+
}
|
|
384
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
385
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
386
|
+
}
|
|
387
|
+
function rewriteInsertOrReplace(sql) {
|
|
388
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
389
|
+
if (!match) {
|
|
390
|
+
return sql;
|
|
391
|
+
}
|
|
392
|
+
const rawTable = match[1];
|
|
393
|
+
const rawColumns = match[2];
|
|
394
|
+
const remainder = match[3];
|
|
395
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
396
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
397
|
+
if (!conflictKeys?.length) {
|
|
398
|
+
return sql;
|
|
399
|
+
}
|
|
400
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
401
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
402
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
403
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
404
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
405
|
+
}
|
|
406
|
+
function rewriteSql(sql) {
|
|
407
|
+
let out = sql;
|
|
408
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
409
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
410
|
+
out = rewriteBooleanLiterals(out);
|
|
411
|
+
out = rewriteInsertOrReplace(out);
|
|
412
|
+
out = rewriteInsertOrIgnore(out);
|
|
413
|
+
return stripTrailingSemicolon(out);
|
|
414
|
+
}
|
|
415
|
+
function toBoolean(value) {
|
|
416
|
+
if (value === null || value === void 0) return value;
|
|
417
|
+
if (typeof value === "boolean") return value;
|
|
418
|
+
if (typeof value === "number") return value !== 0;
|
|
419
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
420
|
+
if (typeof value === "string") {
|
|
421
|
+
const normalized = value.trim().toLowerCase();
|
|
422
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
423
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
424
|
+
}
|
|
425
|
+
return Boolean(value);
|
|
426
|
+
}
|
|
427
|
+
function countQuestionMarks(sql, end) {
|
|
428
|
+
let count = 0;
|
|
429
|
+
let inSingle = false;
|
|
430
|
+
let inDouble = false;
|
|
431
|
+
let inLineComment = false;
|
|
432
|
+
let inBlockComment = false;
|
|
433
|
+
for (let i = 0; i < end; i++) {
|
|
434
|
+
const ch = sql[i];
|
|
435
|
+
const next = sql[i + 1];
|
|
436
|
+
if (inLineComment) {
|
|
437
|
+
if (ch === "\n") inLineComment = false;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (inBlockComment) {
|
|
441
|
+
if (ch === "*" && next === "/") {
|
|
442
|
+
inBlockComment = false;
|
|
443
|
+
i += 1;
|
|
444
|
+
}
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
448
|
+
inLineComment = true;
|
|
449
|
+
i += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
453
|
+
inBlockComment = true;
|
|
454
|
+
i += 1;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
458
|
+
inSingle = !inSingle;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
462
|
+
inDouble = !inDouble;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
466
|
+
count += 1;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return count;
|
|
470
|
+
}
|
|
471
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
472
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
473
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
474
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
475
|
+
for (const match of sql.matchAll(pattern)) {
|
|
476
|
+
const matchText = match[0];
|
|
477
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
478
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return indexes;
|
|
482
|
+
}
|
|
483
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
484
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
485
|
+
if (!match) return;
|
|
486
|
+
const rawTable = match[1];
|
|
487
|
+
const rawColumns = match[2];
|
|
488
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
489
|
+
if (!boolColumns?.size) return;
|
|
490
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
491
|
+
for (const [index, column] of columns.entries()) {
|
|
492
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
493
|
+
args[index] = toBoolean(args[index]);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
498
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
499
|
+
if (!match) return;
|
|
500
|
+
const rawTable = match[1];
|
|
501
|
+
const setClause = match[2];
|
|
502
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
503
|
+
if (!boolColumns?.size) return;
|
|
504
|
+
const assignments = setClause.split(",");
|
|
505
|
+
let placeholderIndex = 0;
|
|
506
|
+
for (const assignment of assignments) {
|
|
507
|
+
if (!assignment.includes("?")) continue;
|
|
508
|
+
placeholderIndex += 1;
|
|
509
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
510
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
511
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function coerceBooleanArgs(sql, args) {
|
|
516
|
+
const nextArgs = [...args];
|
|
517
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
518
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
519
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
520
|
+
for (const index of placeholderIndexes) {
|
|
521
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
522
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return nextArgs;
|
|
526
|
+
}
|
|
527
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
528
|
+
let out = "";
|
|
529
|
+
let placeholder = 0;
|
|
530
|
+
let inSingle = false;
|
|
531
|
+
let inDouble = false;
|
|
532
|
+
let inLineComment = false;
|
|
533
|
+
let inBlockComment = false;
|
|
534
|
+
for (let i = 0; i < sql.length; i++) {
|
|
535
|
+
const ch = sql[i];
|
|
536
|
+
const next = sql[i + 1];
|
|
537
|
+
if (inLineComment) {
|
|
538
|
+
out += ch;
|
|
539
|
+
if (ch === "\n") inLineComment = false;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (inBlockComment) {
|
|
543
|
+
out += ch;
|
|
544
|
+
if (ch === "*" && next === "/") {
|
|
545
|
+
out += next;
|
|
546
|
+
inBlockComment = false;
|
|
547
|
+
i += 1;
|
|
548
|
+
}
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
552
|
+
out += ch + next;
|
|
553
|
+
inLineComment = true;
|
|
554
|
+
i += 1;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
558
|
+
out += ch + next;
|
|
559
|
+
inBlockComment = true;
|
|
560
|
+
i += 1;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
564
|
+
inSingle = !inSingle;
|
|
565
|
+
out += ch;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
569
|
+
inDouble = !inDouble;
|
|
570
|
+
out += ch;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
574
|
+
placeholder += 1;
|
|
575
|
+
out += `$${placeholder}`;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
out += ch;
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
function translateStatementForPostgres(stmt) {
|
|
583
|
+
const normalized = normalizeStatement(stmt);
|
|
584
|
+
if (normalized.kind === "named") {
|
|
585
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
586
|
+
}
|
|
587
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
588
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
589
|
+
return {
|
|
590
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
591
|
+
args: coercedArgs
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function shouldBypassPostgres(stmt) {
|
|
595
|
+
const normalized = normalizeStatement(stmt);
|
|
596
|
+
if (normalized.kind === "named") {
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
600
|
+
}
|
|
601
|
+
function shouldFallbackOnError(error) {
|
|
602
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
603
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
604
|
+
}
|
|
605
|
+
function isReadQuery(sql) {
|
|
606
|
+
const trimmed = sql.trimStart();
|
|
607
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
608
|
+
}
|
|
609
|
+
function buildRow(row, columns) {
|
|
610
|
+
const values = columns.map((column) => row[column]);
|
|
611
|
+
return Object.assign(values, row);
|
|
612
|
+
}
|
|
613
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
614
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
615
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
616
|
+
return {
|
|
617
|
+
columns,
|
|
618
|
+
columnTypes: columns.map(() => ""),
|
|
619
|
+
rows: resultRows,
|
|
620
|
+
rowsAffected,
|
|
621
|
+
lastInsertRowid: void 0,
|
|
622
|
+
toJSON() {
|
|
623
|
+
return {
|
|
624
|
+
columns,
|
|
625
|
+
columnTypes: columns.map(() => ""),
|
|
626
|
+
rows,
|
|
627
|
+
rowsAffected,
|
|
628
|
+
lastInsertRowid: void 0
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async function loadPrismaClient() {
|
|
634
|
+
if (!prismaClientPromise) {
|
|
635
|
+
prismaClientPromise = (async () => {
|
|
636
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
637
|
+
if (explicitPath) {
|
|
638
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
639
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
640
|
+
if (!PrismaClient2) {
|
|
641
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
642
|
+
}
|
|
643
|
+
return new PrismaClient2();
|
|
644
|
+
}
|
|
645
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
646
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
647
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
648
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
649
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
650
|
+
if (!PrismaClient) {
|
|
651
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
652
|
+
}
|
|
653
|
+
return new PrismaClient();
|
|
654
|
+
})();
|
|
655
|
+
}
|
|
656
|
+
return prismaClientPromise;
|
|
657
|
+
}
|
|
658
|
+
async function ensureCompatibilityViews(prisma) {
|
|
659
|
+
if (!compatibilityBootstrapPromise) {
|
|
660
|
+
compatibilityBootstrapPromise = (async () => {
|
|
661
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
662
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
663
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
664
|
+
"SELECT to_regclass($1) AS regclass",
|
|
665
|
+
relation
|
|
666
|
+
);
|
|
667
|
+
if (!rows[0]?.regclass) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
await prisma.$executeRawUnsafe(
|
|
671
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
})();
|
|
675
|
+
}
|
|
676
|
+
return compatibilityBootstrapPromise;
|
|
677
|
+
}
|
|
678
|
+
async function executeOnPrisma(executor, stmt) {
|
|
679
|
+
const translated = translateStatementForPostgres(stmt);
|
|
680
|
+
if (isReadQuery(translated.sql)) {
|
|
681
|
+
const rows = await executor.$queryRawUnsafe(
|
|
682
|
+
translated.sql,
|
|
683
|
+
...translated.args
|
|
684
|
+
);
|
|
685
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
686
|
+
}
|
|
687
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
688
|
+
return buildResultSet([], rowsAffected);
|
|
689
|
+
}
|
|
690
|
+
function splitSqlStatements(sql) {
|
|
691
|
+
const parts = [];
|
|
692
|
+
let current = "";
|
|
693
|
+
let inSingle = false;
|
|
694
|
+
let inDouble = false;
|
|
695
|
+
let inLineComment = false;
|
|
696
|
+
let inBlockComment = false;
|
|
697
|
+
for (let i = 0; i < sql.length; i++) {
|
|
698
|
+
const ch = sql[i];
|
|
699
|
+
const next = sql[i + 1];
|
|
700
|
+
if (inLineComment) {
|
|
701
|
+
current += ch;
|
|
702
|
+
if (ch === "\n") inLineComment = false;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (inBlockComment) {
|
|
706
|
+
current += ch;
|
|
707
|
+
if (ch === "*" && next === "/") {
|
|
708
|
+
current += next;
|
|
709
|
+
inBlockComment = false;
|
|
710
|
+
i += 1;
|
|
711
|
+
}
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
715
|
+
current += ch + next;
|
|
716
|
+
inLineComment = true;
|
|
717
|
+
i += 1;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
721
|
+
current += ch + next;
|
|
722
|
+
inBlockComment = true;
|
|
723
|
+
i += 1;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
727
|
+
inSingle = !inSingle;
|
|
728
|
+
current += ch;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
732
|
+
inDouble = !inDouble;
|
|
733
|
+
current += ch;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
737
|
+
if (current.trim()) {
|
|
738
|
+
parts.push(current.trim());
|
|
739
|
+
}
|
|
740
|
+
current = "";
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
current += ch;
|
|
744
|
+
}
|
|
745
|
+
if (current.trim()) {
|
|
746
|
+
parts.push(current.trim());
|
|
747
|
+
}
|
|
748
|
+
return parts;
|
|
749
|
+
}
|
|
750
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
751
|
+
const prisma = await loadPrismaClient();
|
|
752
|
+
await ensureCompatibilityViews(prisma);
|
|
753
|
+
let closed = false;
|
|
754
|
+
let adapter;
|
|
755
|
+
const fallbackExecute = async (stmt, error) => {
|
|
756
|
+
if (!fallbackClient) {
|
|
757
|
+
if (error) throw error;
|
|
758
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
759
|
+
}
|
|
760
|
+
if (error) {
|
|
761
|
+
process.stderr.write(
|
|
762
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
763
|
+
`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return fallbackClient.execute(stmt);
|
|
767
|
+
};
|
|
768
|
+
adapter = {
|
|
769
|
+
async execute(stmt) {
|
|
770
|
+
if (shouldBypassPostgres(stmt)) {
|
|
771
|
+
return fallbackExecute(stmt);
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
return await executeOnPrisma(prisma, stmt);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (shouldFallbackOnError(error)) {
|
|
777
|
+
return fallbackExecute(stmt, error);
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
async batch(stmts, mode) {
|
|
783
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
784
|
+
if (!fallbackClient) {
|
|
785
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
786
|
+
}
|
|
787
|
+
return fallbackClient.batch(stmts, mode);
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
if (prisma.$transaction) {
|
|
791
|
+
return await prisma.$transaction(async (tx) => {
|
|
792
|
+
const results2 = [];
|
|
793
|
+
for (const stmt of stmts) {
|
|
794
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
795
|
+
}
|
|
796
|
+
return results2;
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const results = [];
|
|
800
|
+
for (const stmt of stmts) {
|
|
801
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
802
|
+
}
|
|
803
|
+
return results;
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
806
|
+
process.stderr.write(
|
|
807
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
808
|
+
`
|
|
809
|
+
);
|
|
810
|
+
return fallbackClient.batch(stmts, mode);
|
|
811
|
+
}
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
async migrate(stmts) {
|
|
816
|
+
if (fallbackClient) {
|
|
817
|
+
return fallbackClient.migrate(stmts);
|
|
818
|
+
}
|
|
819
|
+
return adapter.batch(stmts, "deferred");
|
|
820
|
+
},
|
|
821
|
+
async transaction(mode) {
|
|
822
|
+
if (!fallbackClient) {
|
|
823
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
824
|
+
}
|
|
825
|
+
return fallbackClient.transaction(mode);
|
|
826
|
+
},
|
|
827
|
+
async executeMultiple(sql) {
|
|
828
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
829
|
+
return fallbackClient.executeMultiple(sql);
|
|
830
|
+
}
|
|
831
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
832
|
+
await adapter.execute(statement);
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
async sync() {
|
|
836
|
+
if (fallbackClient) {
|
|
837
|
+
return fallbackClient.sync();
|
|
838
|
+
}
|
|
839
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
840
|
+
},
|
|
841
|
+
close() {
|
|
842
|
+
closed = true;
|
|
843
|
+
prismaClientPromise = null;
|
|
844
|
+
compatibilityBootstrapPromise = null;
|
|
845
|
+
void prisma.$disconnect?.();
|
|
846
|
+
},
|
|
847
|
+
get closed() {
|
|
848
|
+
return closed;
|
|
849
|
+
},
|
|
850
|
+
get protocol() {
|
|
851
|
+
return "prisma-postgres";
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
return adapter;
|
|
855
|
+
}
|
|
856
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
857
|
+
var init_database_adapter = __esm({
|
|
858
|
+
"src/lib/database-adapter.ts"() {
|
|
859
|
+
"use strict";
|
|
860
|
+
VIEW_MAPPINGS = [
|
|
861
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
862
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
863
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
864
|
+
{ view: "entities", source: "memory.entities" },
|
|
865
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
866
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
867
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
868
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
869
|
+
{ view: "messages", source: "memory.messages" },
|
|
870
|
+
{ view: "users", source: "wiki.users" },
|
|
871
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
872
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
873
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
874
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
875
|
+
];
|
|
876
|
+
UPSERT_KEYS = {
|
|
877
|
+
memories: ["id"],
|
|
878
|
+
tasks: ["id"],
|
|
879
|
+
behaviors: ["id"],
|
|
880
|
+
entities: ["id"],
|
|
881
|
+
relationships: ["id"],
|
|
882
|
+
entity_aliases: ["alias"],
|
|
883
|
+
notifications: ["id"],
|
|
884
|
+
messages: ["id"],
|
|
885
|
+
users: ["id"],
|
|
886
|
+
workspaces: ["id"],
|
|
887
|
+
workspace_users: ["id"],
|
|
888
|
+
documents: ["id"],
|
|
889
|
+
chats: ["id"]
|
|
890
|
+
};
|
|
891
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
892
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
893
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
894
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
895
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
896
|
+
};
|
|
897
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
898
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
899
|
+
);
|
|
900
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
901
|
+
/\bPRAGMA\b/i,
|
|
902
|
+
/\bsqlite_master\b/i,
|
|
903
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
904
|
+
/\bMATCH\b/i,
|
|
905
|
+
/\bvector_distance_cos\s*\(/i,
|
|
906
|
+
/\bjson_extract\s*\(/i,
|
|
907
|
+
/\bjulianday\s*\(/i,
|
|
908
|
+
/\bstrftime\s*\(/i,
|
|
909
|
+
/\blast_insert_rowid\s*\(/i
|
|
910
|
+
];
|
|
911
|
+
prismaClientPromise = null;
|
|
912
|
+
compatibilityBootstrapPromise = null;
|
|
301
913
|
}
|
|
302
914
|
});
|
|
303
915
|
|
|
304
916
|
// src/lib/database.ts
|
|
305
917
|
import { createClient } from "@libsql/client";
|
|
306
918
|
async function initDatabase(config) {
|
|
919
|
+
if (_walCheckpointTimer) {
|
|
920
|
+
clearInterval(_walCheckpointTimer);
|
|
921
|
+
_walCheckpointTimer = null;
|
|
922
|
+
}
|
|
923
|
+
if (_daemonClient) {
|
|
924
|
+
_daemonClient.close();
|
|
925
|
+
_daemonClient = null;
|
|
926
|
+
}
|
|
927
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
928
|
+
_adapterClient.close();
|
|
929
|
+
}
|
|
930
|
+
_adapterClient = null;
|
|
307
931
|
if (_client) {
|
|
308
932
|
_client.close();
|
|
309
933
|
_client = null;
|
|
@@ -317,6 +941,7 @@ async function initDatabase(config) {
|
|
|
317
941
|
}
|
|
318
942
|
_client = createClient(opts);
|
|
319
943
|
_resilientClient = wrapWithRetry(_client);
|
|
944
|
+
_adapterClient = _resilientClient;
|
|
320
945
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
321
946
|
});
|
|
322
947
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -327,11 +952,17 @@ async function initDatabase(config) {
|
|
|
327
952
|
});
|
|
328
953
|
}, 3e4);
|
|
329
954
|
_walCheckpointTimer.unref();
|
|
955
|
+
if (process.env.DATABASE_URL) {
|
|
956
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
957
|
+
}
|
|
330
958
|
}
|
|
331
959
|
function getClient() {
|
|
332
|
-
if (!
|
|
960
|
+
if (!_adapterClient) {
|
|
333
961
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
334
962
|
}
|
|
963
|
+
if (process.env.DATABASE_URL) {
|
|
964
|
+
return _adapterClient;
|
|
965
|
+
}
|
|
335
966
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
336
967
|
return _resilientClient;
|
|
337
968
|
}
|
|
@@ -624,6 +1255,7 @@ async function ensureSchema() {
|
|
|
624
1255
|
project TEXT NOT NULL,
|
|
625
1256
|
summary TEXT NOT NULL,
|
|
626
1257
|
task_file TEXT,
|
|
1258
|
+
session_scope TEXT,
|
|
627
1259
|
read INTEGER NOT NULL DEFAULT 0,
|
|
628
1260
|
created_at TEXT NOT NULL
|
|
629
1261
|
);
|
|
@@ -632,7 +1264,7 @@ async function ensureSchema() {
|
|
|
632
1264
|
ON notifications(read);
|
|
633
1265
|
|
|
634
1266
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
635
|
-
ON notifications(agent_id);
|
|
1267
|
+
ON notifications(agent_id, session_scope);
|
|
636
1268
|
|
|
637
1269
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
638
1270
|
ON notifications(task_file);
|
|
@@ -670,6 +1302,7 @@ async function ensureSchema() {
|
|
|
670
1302
|
target_agent TEXT NOT NULL,
|
|
671
1303
|
target_project TEXT,
|
|
672
1304
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1305
|
+
session_scope TEXT,
|
|
673
1306
|
content TEXT NOT NULL,
|
|
674
1307
|
priority TEXT DEFAULT 'normal',
|
|
675
1308
|
status TEXT DEFAULT 'pending',
|
|
@@ -683,10 +1316,31 @@ async function ensureSchema() {
|
|
|
683
1316
|
);
|
|
684
1317
|
|
|
685
1318
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
686
|
-
ON messages(target_agent, status);
|
|
1319
|
+
ON messages(target_agent, session_scope, status);
|
|
687
1320
|
|
|
688
1321
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
689
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1322
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1323
|
+
`);
|
|
1324
|
+
try {
|
|
1325
|
+
await client.execute({
|
|
1326
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1327
|
+
args: []
|
|
1328
|
+
});
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
await client.execute({
|
|
1333
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1334
|
+
args: []
|
|
1335
|
+
});
|
|
1336
|
+
} catch {
|
|
1337
|
+
}
|
|
1338
|
+
await client.executeMultiple(`
|
|
1339
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1340
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1341
|
+
|
|
1342
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1343
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
690
1344
|
`);
|
|
691
1345
|
try {
|
|
692
1346
|
await client.execute({
|
|
@@ -1270,28 +1924,45 @@ async function ensureSchema() {
|
|
|
1270
1924
|
} catch {
|
|
1271
1925
|
}
|
|
1272
1926
|
}
|
|
1927
|
+
try {
|
|
1928
|
+
await client.execute({
|
|
1929
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
1930
|
+
args: []
|
|
1931
|
+
});
|
|
1932
|
+
} catch {
|
|
1933
|
+
}
|
|
1273
1934
|
}
|
|
1274
1935
|
async function disposeDatabase() {
|
|
1936
|
+
if (_walCheckpointTimer) {
|
|
1937
|
+
clearInterval(_walCheckpointTimer);
|
|
1938
|
+
_walCheckpointTimer = null;
|
|
1939
|
+
}
|
|
1275
1940
|
if (_daemonClient) {
|
|
1276
1941
|
_daemonClient.close();
|
|
1277
1942
|
_daemonClient = null;
|
|
1278
1943
|
}
|
|
1944
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1945
|
+
_adapterClient.close();
|
|
1946
|
+
}
|
|
1947
|
+
_adapterClient = null;
|
|
1279
1948
|
if (_client) {
|
|
1280
1949
|
_client.close();
|
|
1281
1950
|
_client = null;
|
|
1282
1951
|
_resilientClient = null;
|
|
1283
1952
|
}
|
|
1284
1953
|
}
|
|
1285
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
1954
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1286
1955
|
var init_database = __esm({
|
|
1287
1956
|
"src/lib/database.ts"() {
|
|
1288
1957
|
"use strict";
|
|
1289
1958
|
init_db_retry();
|
|
1290
1959
|
init_employees();
|
|
1960
|
+
init_database_adapter();
|
|
1291
1961
|
_client = null;
|
|
1292
1962
|
_resilientClient = null;
|
|
1293
1963
|
_walCheckpointTimer = null;
|
|
1294
1964
|
_daemonClient = null;
|
|
1965
|
+
_adapterClient = null;
|
|
1295
1966
|
initTurso = initDatabase;
|
|
1296
1967
|
disposeTurso = disposeDatabase;
|
|
1297
1968
|
}
|
|
@@ -1302,6 +1973,7 @@ var shard_manager_exports = {};
|
|
|
1302
1973
|
__export(shard_manager_exports, {
|
|
1303
1974
|
disposeShards: () => disposeShards,
|
|
1304
1975
|
ensureShardSchema: () => ensureShardSchema,
|
|
1976
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
1305
1977
|
getReadyShardClient: () => getReadyShardClient,
|
|
1306
1978
|
getShardClient: () => getShardClient,
|
|
1307
1979
|
getShardsDir: () => getShardsDir,
|
|
@@ -1310,15 +1982,18 @@ __export(shard_manager_exports, {
|
|
|
1310
1982
|
listShards: () => listShards,
|
|
1311
1983
|
shardExists: () => shardExists
|
|
1312
1984
|
});
|
|
1313
|
-
import
|
|
1314
|
-
import { existsSync as
|
|
1985
|
+
import path5 from "path";
|
|
1986
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
1315
1987
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1316
1988
|
function initShardManager(encryptionKey) {
|
|
1317
1989
|
_encryptionKey = encryptionKey;
|
|
1318
|
-
if (!
|
|
1319
|
-
|
|
1990
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1991
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1320
1992
|
}
|
|
1321
1993
|
_shardingEnabled = true;
|
|
1994
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
1995
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
1996
|
+
_evictionTimer.unref();
|
|
1322
1997
|
}
|
|
1323
1998
|
function isShardingEnabled() {
|
|
1324
1999
|
return _shardingEnabled;
|
|
@@ -1335,21 +2010,28 @@ function getShardClient(projectName) {
|
|
|
1335
2010
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
1336
2011
|
}
|
|
1337
2012
|
const cached = _shards.get(safeName);
|
|
1338
|
-
if (cached)
|
|
1339
|
-
|
|
2013
|
+
if (cached) {
|
|
2014
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2015
|
+
return cached;
|
|
2016
|
+
}
|
|
2017
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2018
|
+
evictLRU();
|
|
2019
|
+
}
|
|
2020
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1340
2021
|
const client = createClient2({
|
|
1341
2022
|
url: `file:${dbPath}`,
|
|
1342
2023
|
encryptionKey: _encryptionKey
|
|
1343
2024
|
});
|
|
1344
2025
|
_shards.set(safeName, client);
|
|
2026
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
1345
2027
|
return client;
|
|
1346
2028
|
}
|
|
1347
2029
|
function shardExists(projectName) {
|
|
1348
2030
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1349
|
-
return
|
|
2031
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1350
2032
|
}
|
|
1351
2033
|
function listShards() {
|
|
1352
|
-
if (!
|
|
2034
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1353
2035
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1354
2036
|
}
|
|
1355
2037
|
async function ensureShardSchema(client) {
|
|
@@ -1401,6 +2083,8 @@ async function ensureShardSchema(client) {
|
|
|
1401
2083
|
for (const col of [
|
|
1402
2084
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
1403
2085
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2086
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2087
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
1404
2088
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
1405
2089
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
1406
2090
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -1423,7 +2107,23 @@ async function ensureShardSchema(client) {
|
|
|
1423
2107
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1424
2108
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1425
2109
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1426
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
2110
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
2111
|
+
// Metadata enrichment columns (must match database.ts)
|
|
2112
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2113
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2114
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2115
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2116
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2117
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2118
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2119
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2120
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2121
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2122
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2123
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2124
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2125
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2126
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1427
2127
|
]) {
|
|
1428
2128
|
try {
|
|
1429
2129
|
await client.execute(col);
|
|
@@ -1522,21 +2222,69 @@ async function getReadyShardClient(projectName) {
|
|
|
1522
2222
|
await ensureShardSchema(client);
|
|
1523
2223
|
return client;
|
|
1524
2224
|
}
|
|
2225
|
+
function evictLRU() {
|
|
2226
|
+
let oldest = null;
|
|
2227
|
+
let oldestTime = Infinity;
|
|
2228
|
+
for (const [name, time] of _shardLastAccess) {
|
|
2229
|
+
if (time < oldestTime) {
|
|
2230
|
+
oldestTime = time;
|
|
2231
|
+
oldest = name;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
if (oldest) {
|
|
2235
|
+
const client = _shards.get(oldest);
|
|
2236
|
+
if (client) {
|
|
2237
|
+
client.close();
|
|
2238
|
+
}
|
|
2239
|
+
_shards.delete(oldest);
|
|
2240
|
+
_shardLastAccess.delete(oldest);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
function evictIdleShards() {
|
|
2244
|
+
const now = Date.now();
|
|
2245
|
+
const toEvict = [];
|
|
2246
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
2247
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
2248
|
+
toEvict.push(name);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
for (const name of toEvict) {
|
|
2252
|
+
const client = _shards.get(name);
|
|
2253
|
+
if (client) {
|
|
2254
|
+
client.close();
|
|
2255
|
+
}
|
|
2256
|
+
_shards.delete(name);
|
|
2257
|
+
_shardLastAccess.delete(name);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function getOpenShardCount() {
|
|
2261
|
+
return _shards.size;
|
|
2262
|
+
}
|
|
1525
2263
|
function disposeShards() {
|
|
2264
|
+
if (_evictionTimer) {
|
|
2265
|
+
clearInterval(_evictionTimer);
|
|
2266
|
+
_evictionTimer = null;
|
|
2267
|
+
}
|
|
1526
2268
|
for (const [, client] of _shards) {
|
|
1527
2269
|
client.close();
|
|
1528
2270
|
}
|
|
1529
2271
|
_shards.clear();
|
|
2272
|
+
_shardLastAccess.clear();
|
|
1530
2273
|
_shardingEnabled = false;
|
|
1531
2274
|
_encryptionKey = null;
|
|
1532
2275
|
}
|
|
1533
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
2276
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
1534
2277
|
var init_shard_manager = __esm({
|
|
1535
2278
|
"src/lib/shard-manager.ts"() {
|
|
1536
2279
|
"use strict";
|
|
1537
2280
|
init_config();
|
|
1538
|
-
SHARDS_DIR =
|
|
2281
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
2282
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
2283
|
+
MAX_OPEN_SHARDS = 10;
|
|
2284
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1539
2285
|
_shards = /* @__PURE__ */ new Map();
|
|
2286
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
2287
|
+
_evictionTimer = null;
|
|
1540
2288
|
_encryptionKey = null;
|
|
1541
2289
|
_shardingEnabled = false;
|
|
1542
2290
|
}
|
|
@@ -1729,6 +2477,32 @@ ${p.content}`).join("\n\n");
|
|
|
1729
2477
|
}
|
|
1730
2478
|
});
|
|
1731
2479
|
|
|
2480
|
+
// src/lib/runtime-table.ts
|
|
2481
|
+
var RUNTIME_TABLE;
|
|
2482
|
+
var init_runtime_table = __esm({
|
|
2483
|
+
"src/lib/runtime-table.ts"() {
|
|
2484
|
+
"use strict";
|
|
2485
|
+
RUNTIME_TABLE = {
|
|
2486
|
+
codex: {
|
|
2487
|
+
binary: "codex",
|
|
2488
|
+
launchMode: "interactive",
|
|
2489
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
2490
|
+
inlineFlag: "--no-alt-screen",
|
|
2491
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
2492
|
+
defaultModel: "gpt-5.4"
|
|
2493
|
+
},
|
|
2494
|
+
opencode: {
|
|
2495
|
+
binary: "opencode",
|
|
2496
|
+
launchMode: "exec",
|
|
2497
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2498
|
+
inlineFlag: "",
|
|
2499
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
2500
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
|
|
1732
2506
|
// src/lib/session-key.ts
|
|
1733
2507
|
import { execSync as execSync2 } from "child_process";
|
|
1734
2508
|
function normalizeCommand(command) {
|
|
@@ -1807,9 +2581,9 @@ __export(active_agent_exports, {
|
|
|
1807
2581
|
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
1808
2582
|
writeActiveAgent: () => writeActiveAgent
|
|
1809
2583
|
});
|
|
1810
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as
|
|
2584
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
1811
2585
|
import { execSync as execSync3 } from "child_process";
|
|
1812
|
-
import
|
|
2586
|
+
import path7 from "path";
|
|
1813
2587
|
function isNameWithOptionalInstance(candidate, baseName) {
|
|
1814
2588
|
if (candidate === baseName) return true;
|
|
1815
2589
|
if (!candidate.startsWith(baseName)) return false;
|
|
@@ -1853,11 +2627,11 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
|
1853
2627
|
return null;
|
|
1854
2628
|
}
|
|
1855
2629
|
function getMarkerPath() {
|
|
1856
|
-
return
|
|
2630
|
+
return path7.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
1857
2631
|
}
|
|
1858
2632
|
function writeActiveAgent(agentId, agentRole) {
|
|
1859
2633
|
try {
|
|
1860
|
-
|
|
2634
|
+
mkdirSync4(CACHE_DIR, { recursive: true });
|
|
1861
2635
|
writeFileSync3(
|
|
1862
2636
|
getMarkerPath(),
|
|
1863
2637
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
@@ -1922,14 +2696,14 @@ function getAllActiveAgents() {
|
|
|
1922
2696
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
1923
2697
|
if (key === "undefined") continue;
|
|
1924
2698
|
try {
|
|
1925
|
-
const raw = readFileSync3(
|
|
2699
|
+
const raw = readFileSync3(path7.join(CACHE_DIR, file), "utf8");
|
|
1926
2700
|
const data = JSON.parse(raw);
|
|
1927
2701
|
if (!data.agentId) continue;
|
|
1928
2702
|
if (data.startedAt) {
|
|
1929
2703
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
1930
2704
|
if (age > STALE_MS) {
|
|
1931
2705
|
try {
|
|
1932
|
-
unlinkSync3(
|
|
2706
|
+
unlinkSync3(path7.join(CACHE_DIR, file));
|
|
1933
2707
|
} catch {
|
|
1934
2708
|
}
|
|
1935
2709
|
continue;
|
|
@@ -1952,11 +2726,11 @@ function getAllActiveAgents() {
|
|
|
1952
2726
|
function cleanupSessionMarkers() {
|
|
1953
2727
|
const key = getSessionKey();
|
|
1954
2728
|
try {
|
|
1955
|
-
unlinkSync3(
|
|
2729
|
+
unlinkSync3(path7.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
1956
2730
|
} catch {
|
|
1957
2731
|
}
|
|
1958
2732
|
try {
|
|
1959
|
-
unlinkSync3(
|
|
2733
|
+
unlinkSync3(path7.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
1960
2734
|
} catch {
|
|
1961
2735
|
}
|
|
1962
2736
|
}
|
|
@@ -1967,18 +2741,18 @@ var init_active_agent = __esm({
|
|
|
1967
2741
|
init_config();
|
|
1968
2742
|
init_session_key();
|
|
1969
2743
|
init_employees();
|
|
1970
|
-
CACHE_DIR =
|
|
2744
|
+
CACHE_DIR = path7.join(EXE_AI_DIR, "session-cache");
|
|
1971
2745
|
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1972
2746
|
}
|
|
1973
2747
|
});
|
|
1974
2748
|
|
|
1975
2749
|
// src/lib/agent-symlinks.ts
|
|
1976
|
-
import
|
|
1977
|
-
import
|
|
2750
|
+
import os6 from "os";
|
|
2751
|
+
import path8 from "path";
|
|
1978
2752
|
import {
|
|
1979
|
-
existsSync as
|
|
2753
|
+
existsSync as existsSync7,
|
|
1980
2754
|
lstatSync,
|
|
1981
|
-
mkdirSync as
|
|
2755
|
+
mkdirSync as mkdirSync5,
|
|
1982
2756
|
readlinkSync as readlinkSync2,
|
|
1983
2757
|
symlinkSync as symlinkSync2
|
|
1984
2758
|
} from "fs";
|
|
@@ -2004,12 +2778,12 @@ var init_mcp_prefix = __esm({
|
|
|
2004
2778
|
});
|
|
2005
2779
|
|
|
2006
2780
|
// src/lib/preferences.ts
|
|
2007
|
-
import { existsSync as
|
|
2008
|
-
import
|
|
2009
|
-
import
|
|
2010
|
-
function loadPreferences(homeDir =
|
|
2011
|
-
const configPath =
|
|
2012
|
-
if (!
|
|
2781
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2782
|
+
import path9 from "path";
|
|
2783
|
+
import os7 from "os";
|
|
2784
|
+
function loadPreferences(homeDir = os7.homedir()) {
|
|
2785
|
+
const configPath = path9.join(homeDir, ".exe-os", "config.json");
|
|
2786
|
+
if (!existsSync8(configPath)) return {};
|
|
2013
2787
|
try {
|
|
2014
2788
|
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
2015
2789
|
return config.preferences ?? {};
|
|
@@ -2020,32 +2794,33 @@ function loadPreferences(homeDir = os6.homedir()) {
|
|
|
2020
2794
|
var init_preferences = __esm({
|
|
2021
2795
|
"src/lib/preferences.ts"() {
|
|
2022
2796
|
"use strict";
|
|
2797
|
+
init_secure_files();
|
|
2023
2798
|
}
|
|
2024
2799
|
});
|
|
2025
2800
|
|
|
2026
2801
|
// src/adapters/claude/installer.ts
|
|
2027
2802
|
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdir } from "fs/promises";
|
|
2028
|
-
import { existsSync as
|
|
2029
|
-
import
|
|
2030
|
-
import
|
|
2803
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync5, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
|
|
2804
|
+
import path10 from "path";
|
|
2805
|
+
import os8 from "os";
|
|
2031
2806
|
import { execSync as execSync4 } from "child_process";
|
|
2032
2807
|
import { fileURLToPath } from "url";
|
|
2033
2808
|
function resolvePackageRoot() {
|
|
2034
2809
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2035
|
-
let dir =
|
|
2036
|
-
const root =
|
|
2810
|
+
let dir = path10.dirname(thisFile);
|
|
2811
|
+
const root = path10.parse(dir).root;
|
|
2037
2812
|
while (dir !== root) {
|
|
2038
|
-
const pkgPath =
|
|
2039
|
-
if (
|
|
2813
|
+
const pkgPath = path10.join(dir, "package.json");
|
|
2814
|
+
if (existsSync9(pkgPath)) {
|
|
2040
2815
|
try {
|
|
2041
2816
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
2042
2817
|
if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
|
|
2043
2818
|
} catch {
|
|
2044
2819
|
}
|
|
2045
2820
|
}
|
|
2046
|
-
dir =
|
|
2821
|
+
dir = path10.dirname(dir);
|
|
2047
2822
|
}
|
|
2048
|
-
return
|
|
2823
|
+
return path10.resolve(path10.dirname(thisFile), "..", "..", "..");
|
|
2049
2824
|
}
|
|
2050
2825
|
var EXE_SECTION_START, EXE_SECTION_END, ORCHESTRATION_RULES;
|
|
2051
2826
|
var init_installer = __esm({
|
|
@@ -2079,19 +2854,19 @@ __export(installer_exports, {
|
|
|
2079
2854
|
verifyCodexHooks: () => verifyCodexHooks
|
|
2080
2855
|
});
|
|
2081
2856
|
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
2082
|
-
import { existsSync as
|
|
2083
|
-
import
|
|
2084
|
-
import
|
|
2085
|
-
async function mergeCodexHooks(packageRoot, homeDir =
|
|
2086
|
-
const codexDir =
|
|
2087
|
-
const hooksPath =
|
|
2088
|
-
const logsDir =
|
|
2089
|
-
const hookLogPath =
|
|
2857
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2858
|
+
import path11 from "path";
|
|
2859
|
+
import os9 from "os";
|
|
2860
|
+
async function mergeCodexHooks(packageRoot, homeDir = os9.homedir()) {
|
|
2861
|
+
const codexDir = path11.join(homeDir, ".codex");
|
|
2862
|
+
const hooksPath = path11.join(codexDir, "hooks.json");
|
|
2863
|
+
const logsDir = path11.join(homeDir, ".exe-os", "logs");
|
|
2864
|
+
const hookLogPath = path11.join(logsDir, "hooks.log");
|
|
2090
2865
|
const logSuffix = ` 2>> "${hookLogPath}"`;
|
|
2091
2866
|
await mkdir5(codexDir, { recursive: true });
|
|
2092
2867
|
await mkdir5(logsDir, { recursive: true });
|
|
2093
2868
|
let hooksJson = {};
|
|
2094
|
-
if (
|
|
2869
|
+
if (existsSync10(hooksPath)) {
|
|
2095
2870
|
try {
|
|
2096
2871
|
hooksJson = JSON.parse(await readFile5(hooksPath, "utf-8"));
|
|
2097
2872
|
} catch {
|
|
@@ -2108,7 +2883,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2108
2883
|
hooks: [
|
|
2109
2884
|
{
|
|
2110
2885
|
type: "command",
|
|
2111
|
-
command: `node "${
|
|
2886
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
|
|
2112
2887
|
timeout: 30,
|
|
2113
2888
|
statusMessage: "exe-os: loading memory brief"
|
|
2114
2889
|
}
|
|
@@ -2123,11 +2898,11 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2123
2898
|
hooks: [
|
|
2124
2899
|
{
|
|
2125
2900
|
type: "command",
|
|
2126
|
-
command: `node "${
|
|
2901
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "ingest.js")}"${logSuffix}`
|
|
2127
2902
|
},
|
|
2128
2903
|
{
|
|
2129
2904
|
type: "command",
|
|
2130
|
-
command: `node "${
|
|
2905
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "error-recall.js")}"${logSuffix}`
|
|
2131
2906
|
}
|
|
2132
2907
|
]
|
|
2133
2908
|
},
|
|
@@ -2139,11 +2914,11 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2139
2914
|
hooks: [
|
|
2140
2915
|
{
|
|
2141
2916
|
type: "command",
|
|
2142
|
-
command: `node "${
|
|
2917
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
|
|
2143
2918
|
},
|
|
2144
2919
|
{
|
|
2145
2920
|
type: "command",
|
|
2146
|
-
command: `node "${
|
|
2921
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "exe-heartbeat-hook.js")}"${logSuffix}`,
|
|
2147
2922
|
timeout: 5
|
|
2148
2923
|
}
|
|
2149
2924
|
]
|
|
@@ -2156,7 +2931,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2156
2931
|
hooks: [
|
|
2157
2932
|
{
|
|
2158
2933
|
type: "command",
|
|
2159
|
-
command: `node "${
|
|
2934
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
|
|
2160
2935
|
}
|
|
2161
2936
|
]
|
|
2162
2937
|
},
|
|
@@ -2169,7 +2944,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2169
2944
|
hooks: [
|
|
2170
2945
|
{
|
|
2171
2946
|
type: "command",
|
|
2172
|
-
command: `node "${
|
|
2947
|
+
command: `node "${path11.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
|
|
2173
2948
|
}
|
|
2174
2949
|
]
|
|
2175
2950
|
},
|
|
@@ -2200,15 +2975,15 @@ async function mergeCodexHooks(packageRoot, homeDir = os8.homedir()) {
|
|
|
2200
2975
|
await writeFile5(hooksPath, JSON.stringify(hooksJson, null, 2) + "\n");
|
|
2201
2976
|
return { added, skipped };
|
|
2202
2977
|
}
|
|
2203
|
-
function verifyCodexHooks(homeDir =
|
|
2204
|
-
const hooksPath =
|
|
2205
|
-
if (!
|
|
2978
|
+
function verifyCodexHooks(homeDir = os9.homedir()) {
|
|
2979
|
+
const hooksPath = path11.join(homeDir, ".codex", "hooks.json");
|
|
2980
|
+
if (!existsSync10(hooksPath)) return false;
|
|
2206
2981
|
try {
|
|
2207
2982
|
const hooksJson = JSON.parse(
|
|
2208
2983
|
__require("fs").readFileSync(hooksPath, "utf-8")
|
|
2209
2984
|
);
|
|
2210
2985
|
if (!hooksJson.hooks) return false;
|
|
2211
|
-
const required = ["SessionStart", "PostToolUse", "UserPromptSubmit", "Stop"];
|
|
2986
|
+
const required = ["SessionStart", "PostToolUse", "UserPromptSubmit", "Stop", "PreToolUse"];
|
|
2212
2987
|
for (const event of required) {
|
|
2213
2988
|
const groups = hooksJson.hooks[event];
|
|
2214
2989
|
if (!groups || !groups.some(
|
|
@@ -2222,14 +2997,14 @@ function verifyCodexHooks(homeDir = os8.homedir()) {
|
|
|
2222
2997
|
return false;
|
|
2223
2998
|
}
|
|
2224
2999
|
}
|
|
2225
|
-
async function installCodexStatusLine(homeDir =
|
|
3000
|
+
async function installCodexStatusLine(homeDir = os9.homedir()) {
|
|
2226
3001
|
const prefs = loadPreferences(homeDir);
|
|
2227
3002
|
if (prefs.codexStatusLine === false) return "opted-out";
|
|
2228
|
-
const codexDir =
|
|
2229
|
-
const configPath =
|
|
3003
|
+
const codexDir = path11.join(homeDir, ".codex");
|
|
3004
|
+
const configPath = path11.join(codexDir, "config.toml");
|
|
2230
3005
|
await mkdir5(codexDir, { recursive: true });
|
|
2231
3006
|
let content = "";
|
|
2232
|
-
if (
|
|
3007
|
+
if (existsSync10(configPath)) {
|
|
2233
3008
|
content = await readFile5(configPath, "utf-8");
|
|
2234
3009
|
if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
|
|
2235
3010
|
return "already-configured";
|
|
@@ -2277,10 +3052,10 @@ var init_installer2 = __esm({
|
|
|
2277
3052
|
});
|
|
2278
3053
|
|
|
2279
3054
|
// src/bin/exe-start-codex.ts
|
|
2280
|
-
import
|
|
2281
|
-
import
|
|
3055
|
+
import os10 from "os";
|
|
3056
|
+
import path12 from "path";
|
|
2282
3057
|
import {
|
|
2283
|
-
existsSync as
|
|
3058
|
+
existsSync as existsSync11,
|
|
2284
3059
|
readFileSync as readFileSync6,
|
|
2285
3060
|
writeFileSync as writeFileSync6,
|
|
2286
3061
|
mkdirSync as mkdirSync7,
|
|
@@ -2294,16 +3069,16 @@ init_database();
|
|
|
2294
3069
|
|
|
2295
3070
|
// src/lib/keychain.ts
|
|
2296
3071
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2297
|
-
import { existsSync as
|
|
2298
|
-
import
|
|
2299
|
-
import
|
|
3072
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3073
|
+
import path4 from "path";
|
|
3074
|
+
import os4 from "os";
|
|
2300
3075
|
var SERVICE = "exe-mem";
|
|
2301
3076
|
var ACCOUNT = "master-key";
|
|
2302
3077
|
function getKeyDir() {
|
|
2303
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
3078
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
|
|
2304
3079
|
}
|
|
2305
3080
|
function getKeyPath() {
|
|
2306
|
-
return
|
|
3081
|
+
return path4.join(getKeyDir(), "master.key");
|
|
2307
3082
|
}
|
|
2308
3083
|
async function tryKeytar() {
|
|
2309
3084
|
try {
|
|
@@ -2324,9 +3099,9 @@ async function getMasterKey() {
|
|
|
2324
3099
|
}
|
|
2325
3100
|
}
|
|
2326
3101
|
const keyPath = getKeyPath();
|
|
2327
|
-
if (!
|
|
3102
|
+
if (!existsSync4(keyPath)) {
|
|
2328
3103
|
process.stderr.write(
|
|
2329
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
3104
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2330
3105
|
`
|
|
2331
3106
|
);
|
|
2332
3107
|
return null;
|
|
@@ -2637,11 +3412,11 @@ function vectorToBlob(vector) {
|
|
|
2637
3412
|
}
|
|
2638
3413
|
|
|
2639
3414
|
// src/lib/behaviors-export.ts
|
|
2640
|
-
import
|
|
2641
|
-
import
|
|
3415
|
+
import os5 from "os";
|
|
3416
|
+
import path6 from "path";
|
|
2642
3417
|
import {
|
|
2643
|
-
existsSync as
|
|
2644
|
-
mkdirSync as
|
|
3418
|
+
existsSync as existsSync6,
|
|
3419
|
+
mkdirSync as mkdirSync3,
|
|
2645
3420
|
readdirSync as readdirSync2,
|
|
2646
3421
|
statSync,
|
|
2647
3422
|
unlinkSync as unlinkSync2,
|
|
@@ -2680,15 +3455,15 @@ async function listBehaviors(agentId, projectName, limit = 30) {
|
|
|
2680
3455
|
}
|
|
2681
3456
|
|
|
2682
3457
|
// src/lib/behaviors-export.ts
|
|
2683
|
-
var BEHAVIORS_EXPORT_DIR =
|
|
2684
|
-
|
|
3458
|
+
var BEHAVIORS_EXPORT_DIR = path6.join(
|
|
3459
|
+
os5.homedir(),
|
|
2685
3460
|
".exe-os",
|
|
2686
3461
|
"behaviors-export"
|
|
2687
3462
|
);
|
|
2688
3463
|
var STALE_EXPORT_AGE_MS = 60 * 60 * 1e3;
|
|
2689
3464
|
var EXPORT_BEHAVIOR_LIMIT = 30;
|
|
2690
3465
|
function sweepStaleBehaviorExports(now = Date.now()) {
|
|
2691
|
-
if (!
|
|
3466
|
+
if (!existsSync6(BEHAVIORS_EXPORT_DIR)) return;
|
|
2692
3467
|
let entries;
|
|
2693
3468
|
try {
|
|
2694
3469
|
entries = readdirSync2(BEHAVIORS_EXPORT_DIR);
|
|
@@ -2696,7 +3471,7 @@ function sweepStaleBehaviorExports(now = Date.now()) {
|
|
|
2696
3471
|
return;
|
|
2697
3472
|
}
|
|
2698
3473
|
for (const entry of entries) {
|
|
2699
|
-
const filePath =
|
|
3474
|
+
const filePath = path6.join(BEHAVIORS_EXPORT_DIR, entry);
|
|
2700
3475
|
try {
|
|
2701
3476
|
const stat = statSync(filePath);
|
|
2702
3477
|
if (now - stat.mtimeMs > STALE_EXPORT_AGE_MS) {
|
|
@@ -2729,16 +3504,16 @@ function renderBehaviorExport(behaviors) {
|
|
|
2729
3504
|
}
|
|
2730
3505
|
function exportFilePath(agentId, projectName, sessionKey) {
|
|
2731
3506
|
if (!sessionKey) {
|
|
2732
|
-
return
|
|
3507
|
+
return path6.join(BEHAVIORS_EXPORT_DIR, `${agentId}.md`);
|
|
2733
3508
|
}
|
|
2734
3509
|
const safeProject = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2735
|
-
return
|
|
3510
|
+
return path6.join(
|
|
2736
3511
|
BEHAVIORS_EXPORT_DIR,
|
|
2737
3512
|
`${agentId}-${safeProject}-${sessionKey}.md`
|
|
2738
3513
|
);
|
|
2739
3514
|
}
|
|
2740
3515
|
async function exportBehaviorsForAgent(agentId, projectName, sessionKey) {
|
|
2741
|
-
|
|
3516
|
+
mkdirSync3(BEHAVIORS_EXPORT_DIR, { recursive: true });
|
|
2742
3517
|
sweepStaleBehaviorExports();
|
|
2743
3518
|
const behaviors = await listBehaviors(agentId, projectName, EXPORT_BEHAVIOR_LIMIT);
|
|
2744
3519
|
if (behaviors.length === 0) return null;
|
|
@@ -2750,28 +3525,7 @@ async function exportBehaviorsForAgent(agentId, projectName, sessionKey) {
|
|
|
2750
3525
|
|
|
2751
3526
|
// src/bin/exe-start-codex.ts
|
|
2752
3527
|
init_employees();
|
|
2753
|
-
|
|
2754
|
-
// src/lib/runtime-table.ts
|
|
2755
|
-
var RUNTIME_TABLE = {
|
|
2756
|
-
codex: {
|
|
2757
|
-
binary: "codex",
|
|
2758
|
-
launchMode: "interactive",
|
|
2759
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
2760
|
-
inlineFlag: "--no-alt-screen",
|
|
2761
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
2762
|
-
defaultModel: "gpt-5.4"
|
|
2763
|
-
},
|
|
2764
|
-
opencode: {
|
|
2765
|
-
binary: "opencode",
|
|
2766
|
-
launchMode: "exec",
|
|
2767
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2768
|
-
inlineFlag: "",
|
|
2769
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
2770
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
2771
|
-
}
|
|
2772
|
-
};
|
|
2773
|
-
|
|
2774
|
-
// src/bin/exe-start-codex.ts
|
|
3528
|
+
init_runtime_table();
|
|
2775
3529
|
var CODEX = RUNTIME_TABLE.codex;
|
|
2776
3530
|
var BOOT_INSTRUCTIONS = `
|
|
2777
3531
|
---
|
|
@@ -2782,7 +3536,7 @@ When done with a task: call update_task with status "done".
|
|
|
2782
3536
|
Always call store_memory to persist important findings.
|
|
2783
3537
|
`;
|
|
2784
3538
|
function resolveAgent(argv) {
|
|
2785
|
-
const invokedAs =
|
|
3539
|
+
const invokedAs = path12.basename(argv[1] ?? "");
|
|
2786
3540
|
if (invokedAs && invokedAs !== "exe-start-codex" && !invokedAs.endsWith(".js")) {
|
|
2787
3541
|
const agent2 = invokedAs.replace(/-codex$/, "").toLowerCase();
|
|
2788
3542
|
return { agent: agent2, passthrough: argv.slice(2) };
|
|
@@ -2808,9 +3562,9 @@ function resolveAgent(argv) {
|
|
|
2808
3562
|
return { agent, passthrough, sessionName };
|
|
2809
3563
|
}
|
|
2810
3564
|
function loadIdentity(agent) {
|
|
2811
|
-
const dir =
|
|
2812
|
-
const exact =
|
|
2813
|
-
if (
|
|
3565
|
+
const dir = path12.join(os10.homedir(), ".exe-os", "identity");
|
|
3566
|
+
const exact = path12.join(dir, `${agent}.md`);
|
|
3567
|
+
if (existsSync11(exact)) {
|
|
2814
3568
|
const content = readFileSync6(exact, "utf-8").trim();
|
|
2815
3569
|
if (content) return content;
|
|
2816
3570
|
}
|
|
@@ -2818,13 +3572,13 @@ function loadIdentity(agent) {
|
|
|
2818
3572
|
const files = readdirSync4(dir);
|
|
2819
3573
|
const match = files.find((f) => f.toLowerCase() === `${agent.toLowerCase()}.md`);
|
|
2820
3574
|
if (match) {
|
|
2821
|
-
const content = readFileSync6(
|
|
3575
|
+
const content = readFileSync6(path12.join(dir, match), "utf-8").trim();
|
|
2822
3576
|
if (content) return content;
|
|
2823
3577
|
}
|
|
2824
3578
|
} catch {
|
|
2825
3579
|
}
|
|
2826
3580
|
try {
|
|
2827
|
-
const rosterPath =
|
|
3581
|
+
const rosterPath = path12.join(os10.homedir(), ".exe-os", "exe-employees.json");
|
|
2828
3582
|
const roster = JSON.parse(readFileSync6(rosterPath, "utf8"));
|
|
2829
3583
|
const emp = roster.find((e) => e.name.toLowerCase() === agent.toLowerCase());
|
|
2830
3584
|
if (emp?.systemPrompt && emp.systemPrompt.trim().length > 20) {
|
|
@@ -2835,17 +3589,17 @@ function loadIdentity(agent) {
|
|
|
2835
3589
|
return null;
|
|
2836
3590
|
}
|
|
2837
3591
|
function writePromptFile(agent, identity, behaviorsPath) {
|
|
2838
|
-
const promptDir =
|
|
3592
|
+
const promptDir = path12.join(os10.homedir(), ".exe-os", "codex-prompt");
|
|
2839
3593
|
mkdirSync7(promptDir, { recursive: true });
|
|
2840
3594
|
let prompt = identity;
|
|
2841
|
-
if (behaviorsPath &&
|
|
3595
|
+
if (behaviorsPath && existsSync11(behaviorsPath)) {
|
|
2842
3596
|
const behaviors = readFileSync6(behaviorsPath, "utf-8").trim();
|
|
2843
3597
|
if (behaviors) {
|
|
2844
3598
|
prompt += "\n\n" + behaviors;
|
|
2845
3599
|
}
|
|
2846
3600
|
}
|
|
2847
3601
|
prompt += "\n" + BOOT_INSTRUCTIONS;
|
|
2848
|
-
const outPath =
|
|
3602
|
+
const outPath = path12.join(promptDir, `${agent}.md`);
|
|
2849
3603
|
writeFileSync6(outPath, prompt, "utf-8");
|
|
2850
3604
|
return outPath;
|
|
2851
3605
|
}
|
|
@@ -2926,7 +3680,7 @@ async function main() {
|
|
|
2926
3680
|
const empRole = (() => {
|
|
2927
3681
|
try {
|
|
2928
3682
|
const emps = readFileSync6(
|
|
2929
|
-
|
|
3683
|
+
path12.join(os10.homedir(), ".exe-os", "exe-employees.json"),
|
|
2930
3684
|
"utf-8"
|
|
2931
3685
|
);
|
|
2932
3686
|
const found = JSON.parse(emps).find(
|
|
@@ -2963,14 +3717,14 @@ async function main() {
|
|
|
2963
3717
|
if (WORKTREE_ROLES.has(empRole)) {
|
|
2964
3718
|
try {
|
|
2965
3719
|
const { execSync: es } = await import("child_process");
|
|
2966
|
-
const worktreeDir =
|
|
3720
|
+
const worktreeDir = path12.join(process.cwd(), ".worktrees", worktreeName);
|
|
2967
3721
|
const branchName = `${worktreeName}/codex-${Date.now()}`;
|
|
2968
|
-
if (
|
|
3722
|
+
if (existsSync11(worktreeDir)) {
|
|
2969
3723
|
worktreePath = worktreeDir;
|
|
2970
3724
|
process.stderr.write(`[exe-start-codex] Reusing worktree at ${worktreeDir}
|
|
2971
3725
|
`);
|
|
2972
3726
|
} else {
|
|
2973
|
-
mkdirSync7(
|
|
3727
|
+
mkdirSync7(path12.dirname(worktreeDir), { recursive: true });
|
|
2974
3728
|
es(`git worktree add "${worktreeDir}" -b "${branchName}" HEAD`, {
|
|
2975
3729
|
encoding: "utf-8",
|
|
2976
3730
|
timeout: 3e4
|