@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
|
@@ -90,6 +90,44 @@ var init_db_retry = __esm({
|
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
// src/lib/secure-files.ts
|
|
94
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
95
|
+
import { chmod, mkdir } from "fs/promises";
|
|
96
|
+
async function ensurePrivateDir(dirPath) {
|
|
97
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
98
|
+
try {
|
|
99
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function ensurePrivateDirSync(dirPath) {
|
|
104
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
105
|
+
try {
|
|
106
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function enforcePrivateFile(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function enforcePrivateFileSync(filePath) {
|
|
117
|
+
try {
|
|
118
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
123
|
+
var init_secure_files = __esm({
|
|
124
|
+
"src/lib/secure-files.ts"() {
|
|
125
|
+
"use strict";
|
|
126
|
+
PRIVATE_DIR_MODE = 448;
|
|
127
|
+
PRIVATE_FILE_MODE = 384;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
93
131
|
// src/lib/config.ts
|
|
94
132
|
var config_exports = {};
|
|
95
133
|
__export(config_exports, {
|
|
@@ -106,8 +144,8 @@ __export(config_exports, {
|
|
|
106
144
|
migrateConfig: () => migrateConfig,
|
|
107
145
|
saveConfig: () => saveConfig
|
|
108
146
|
});
|
|
109
|
-
import { readFile, writeFile
|
|
110
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
147
|
+
import { readFile, writeFile } from "fs/promises";
|
|
148
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
111
149
|
import path from "path";
|
|
112
150
|
import os from "os";
|
|
113
151
|
function resolveDataDir() {
|
|
@@ -115,7 +153,7 @@ function resolveDataDir() {
|
|
|
115
153
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
116
154
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
117
155
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
118
|
-
if (!
|
|
156
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
119
157
|
try {
|
|
120
158
|
renameSync(legacyDir, newDir);
|
|
121
159
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -178,9 +216,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
178
216
|
}
|
|
179
217
|
async function loadConfig() {
|
|
180
218
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
181
|
-
await
|
|
219
|
+
await ensurePrivateDir(dir);
|
|
182
220
|
const configPath = path.join(dir, "config.json");
|
|
183
|
-
if (!
|
|
221
|
+
if (!existsSync2(configPath)) {
|
|
184
222
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
185
223
|
}
|
|
186
224
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -193,6 +231,7 @@ async function loadConfig() {
|
|
|
193
231
|
`);
|
|
194
232
|
try {
|
|
195
233
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
234
|
+
await enforcePrivateFile(configPath);
|
|
196
235
|
} catch {
|
|
197
236
|
}
|
|
198
237
|
}
|
|
@@ -211,7 +250,7 @@ async function loadConfig() {
|
|
|
211
250
|
function loadConfigSync() {
|
|
212
251
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
213
252
|
const configPath = path.join(dir, "config.json");
|
|
214
|
-
if (!
|
|
253
|
+
if (!existsSync2(configPath)) {
|
|
215
254
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
216
255
|
}
|
|
217
256
|
try {
|
|
@@ -229,12 +268,10 @@ function loadConfigSync() {
|
|
|
229
268
|
}
|
|
230
269
|
async function saveConfig(config) {
|
|
231
270
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
232
|
-
await
|
|
271
|
+
await ensurePrivateDir(dir);
|
|
233
272
|
const configPath = path.join(dir, "config.json");
|
|
234
273
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
235
|
-
|
|
236
|
-
await chmod(configPath, 384);
|
|
237
|
-
}
|
|
274
|
+
await enforcePrivateFile(configPath);
|
|
238
275
|
}
|
|
239
276
|
async function loadConfigFrom(configPath) {
|
|
240
277
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -254,6 +291,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
254
291
|
var init_config = __esm({
|
|
255
292
|
"src/lib/config.ts"() {
|
|
256
293
|
"use strict";
|
|
294
|
+
init_secure_files();
|
|
257
295
|
EXE_AI_DIR = resolveDataDir();
|
|
258
296
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
259
297
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -332,7 +370,7 @@ var init_config = __esm({
|
|
|
332
370
|
|
|
333
371
|
// src/lib/employees.ts
|
|
334
372
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
335
|
-
import { existsSync as
|
|
373
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
336
374
|
import { execSync } from "child_process";
|
|
337
375
|
import path2 from "path";
|
|
338
376
|
import os2 from "os";
|
|
@@ -353,7 +391,7 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
|
|
|
353
391
|
return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
354
392
|
}
|
|
355
393
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
356
|
-
if (!
|
|
394
|
+
if (!existsSync3(employeesPath)) {
|
|
357
395
|
return [];
|
|
358
396
|
}
|
|
359
397
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -364,7 +402,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
364
402
|
}
|
|
365
403
|
}
|
|
366
404
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
367
|
-
if (!
|
|
405
|
+
if (!existsSync3(employeesPath)) return [];
|
|
368
406
|
try {
|
|
369
407
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
370
408
|
} catch {
|
|
@@ -388,7 +426,7 @@ function isMultiInstance(agentName2, employees) {
|
|
|
388
426
|
if (!emp) return false;
|
|
389
427
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
390
428
|
}
|
|
391
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
429
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
392
430
|
var init_employees = __esm({
|
|
393
431
|
"src/lib/employees.ts"() {
|
|
394
432
|
"use strict";
|
|
@@ -397,16 +435,638 @@ var init_employees = __esm({
|
|
|
397
435
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
398
436
|
COORDINATOR_ROLE = "COO";
|
|
399
437
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
438
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// src/lib/database-adapter.ts
|
|
443
|
+
import os3 from "os";
|
|
444
|
+
import path3 from "path";
|
|
445
|
+
import { createRequire } from "module";
|
|
446
|
+
import { pathToFileURL } from "url";
|
|
447
|
+
function quotedIdentifier(identifier) {
|
|
448
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
449
|
+
}
|
|
450
|
+
function unqualifiedTableName(name) {
|
|
451
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
452
|
+
const parts = raw.split(".");
|
|
453
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
454
|
+
}
|
|
455
|
+
function stripTrailingSemicolon(sql) {
|
|
456
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
457
|
+
}
|
|
458
|
+
function appendClause(sql, clause) {
|
|
459
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
460
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
461
|
+
if (!returningMatch) {
|
|
462
|
+
return `${trimmed}${clause}`;
|
|
463
|
+
}
|
|
464
|
+
const idx = returningMatch.index;
|
|
465
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
466
|
+
}
|
|
467
|
+
function normalizeStatement(stmt) {
|
|
468
|
+
if (typeof stmt === "string") {
|
|
469
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
470
|
+
}
|
|
471
|
+
const sql = stmt.sql;
|
|
472
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
473
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
474
|
+
}
|
|
475
|
+
return { kind: "named", sql, args: stmt.args };
|
|
476
|
+
}
|
|
477
|
+
function rewriteBooleanLiterals(sql) {
|
|
478
|
+
let out = sql;
|
|
479
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
480
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
481
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
482
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
483
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
484
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
485
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
486
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function rewriteInsertOrIgnore(sql) {
|
|
491
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
492
|
+
return sql;
|
|
493
|
+
}
|
|
494
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
495
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
496
|
+
}
|
|
497
|
+
function rewriteInsertOrReplace(sql) {
|
|
498
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
499
|
+
if (!match) {
|
|
500
|
+
return sql;
|
|
501
|
+
}
|
|
502
|
+
const rawTable = match[1];
|
|
503
|
+
const rawColumns = match[2];
|
|
504
|
+
const remainder = match[3];
|
|
505
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
506
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
507
|
+
if (!conflictKeys?.length) {
|
|
508
|
+
return sql;
|
|
509
|
+
}
|
|
510
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
511
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
512
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
513
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
514
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
515
|
+
}
|
|
516
|
+
function rewriteSql(sql) {
|
|
517
|
+
let out = sql;
|
|
518
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
519
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
520
|
+
out = rewriteBooleanLiterals(out);
|
|
521
|
+
out = rewriteInsertOrReplace(out);
|
|
522
|
+
out = rewriteInsertOrIgnore(out);
|
|
523
|
+
return stripTrailingSemicolon(out);
|
|
524
|
+
}
|
|
525
|
+
function toBoolean(value) {
|
|
526
|
+
if (value === null || value === void 0) return value;
|
|
527
|
+
if (typeof value === "boolean") return value;
|
|
528
|
+
if (typeof value === "number") return value !== 0;
|
|
529
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
530
|
+
if (typeof value === "string") {
|
|
531
|
+
const normalized = value.trim().toLowerCase();
|
|
532
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
533
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
534
|
+
}
|
|
535
|
+
return Boolean(value);
|
|
536
|
+
}
|
|
537
|
+
function countQuestionMarks(sql, end) {
|
|
538
|
+
let count = 0;
|
|
539
|
+
let inSingle = false;
|
|
540
|
+
let inDouble = false;
|
|
541
|
+
let inLineComment = false;
|
|
542
|
+
let inBlockComment = false;
|
|
543
|
+
for (let i = 0; i < end; i++) {
|
|
544
|
+
const ch = sql[i];
|
|
545
|
+
const next = sql[i + 1];
|
|
546
|
+
if (inLineComment) {
|
|
547
|
+
if (ch === "\n") inLineComment = false;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (inBlockComment) {
|
|
551
|
+
if (ch === "*" && next === "/") {
|
|
552
|
+
inBlockComment = false;
|
|
553
|
+
i += 1;
|
|
554
|
+
}
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
558
|
+
inLineComment = true;
|
|
559
|
+
i += 1;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
563
|
+
inBlockComment = true;
|
|
564
|
+
i += 1;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
568
|
+
inSingle = !inSingle;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
572
|
+
inDouble = !inDouble;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
576
|
+
count += 1;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return count;
|
|
580
|
+
}
|
|
581
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
582
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
583
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
584
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
585
|
+
for (const match of sql.matchAll(pattern)) {
|
|
586
|
+
const matchText = match[0];
|
|
587
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
588
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return indexes;
|
|
592
|
+
}
|
|
593
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
594
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
595
|
+
if (!match) return;
|
|
596
|
+
const rawTable = match[1];
|
|
597
|
+
const rawColumns = match[2];
|
|
598
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
599
|
+
if (!boolColumns?.size) return;
|
|
600
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
601
|
+
for (const [index, column] of columns.entries()) {
|
|
602
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
603
|
+
args[index] = toBoolean(args[index]);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
608
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
609
|
+
if (!match) return;
|
|
610
|
+
const rawTable = match[1];
|
|
611
|
+
const setClause = match[2];
|
|
612
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
613
|
+
if (!boolColumns?.size) return;
|
|
614
|
+
const assignments = setClause.split(",");
|
|
615
|
+
let placeholderIndex = 0;
|
|
616
|
+
for (const assignment of assignments) {
|
|
617
|
+
if (!assignment.includes("?")) continue;
|
|
618
|
+
placeholderIndex += 1;
|
|
619
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
620
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
621
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function coerceBooleanArgs(sql, args) {
|
|
626
|
+
const nextArgs = [...args];
|
|
627
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
628
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
629
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
630
|
+
for (const index of placeholderIndexes) {
|
|
631
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
632
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return nextArgs;
|
|
636
|
+
}
|
|
637
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
638
|
+
let out = "";
|
|
639
|
+
let placeholder = 0;
|
|
640
|
+
let inSingle = false;
|
|
641
|
+
let inDouble = false;
|
|
642
|
+
let inLineComment = false;
|
|
643
|
+
let inBlockComment = false;
|
|
644
|
+
for (let i = 0; i < sql.length; i++) {
|
|
645
|
+
const ch = sql[i];
|
|
646
|
+
const next = sql[i + 1];
|
|
647
|
+
if (inLineComment) {
|
|
648
|
+
out += ch;
|
|
649
|
+
if (ch === "\n") inLineComment = false;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (inBlockComment) {
|
|
653
|
+
out += ch;
|
|
654
|
+
if (ch === "*" && next === "/") {
|
|
655
|
+
out += next;
|
|
656
|
+
inBlockComment = false;
|
|
657
|
+
i += 1;
|
|
658
|
+
}
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
662
|
+
out += ch + next;
|
|
663
|
+
inLineComment = true;
|
|
664
|
+
i += 1;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
668
|
+
out += ch + next;
|
|
669
|
+
inBlockComment = true;
|
|
670
|
+
i += 1;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
674
|
+
inSingle = !inSingle;
|
|
675
|
+
out += ch;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
679
|
+
inDouble = !inDouble;
|
|
680
|
+
out += ch;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
684
|
+
placeholder += 1;
|
|
685
|
+
out += `$${placeholder}`;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
out += ch;
|
|
689
|
+
}
|
|
690
|
+
return out;
|
|
691
|
+
}
|
|
692
|
+
function translateStatementForPostgres(stmt) {
|
|
693
|
+
const normalized = normalizeStatement(stmt);
|
|
694
|
+
if (normalized.kind === "named") {
|
|
695
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
696
|
+
}
|
|
697
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
698
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
699
|
+
return {
|
|
700
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
701
|
+
args: coercedArgs
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function shouldBypassPostgres(stmt) {
|
|
705
|
+
const normalized = normalizeStatement(stmt);
|
|
706
|
+
if (normalized.kind === "named") {
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
710
|
+
}
|
|
711
|
+
function shouldFallbackOnError(error) {
|
|
712
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
713
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
714
|
+
}
|
|
715
|
+
function isReadQuery(sql) {
|
|
716
|
+
const trimmed = sql.trimStart();
|
|
717
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
718
|
+
}
|
|
719
|
+
function buildRow(row, columns) {
|
|
720
|
+
const values = columns.map((column) => row[column]);
|
|
721
|
+
return Object.assign(values, row);
|
|
722
|
+
}
|
|
723
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
724
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
725
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
726
|
+
return {
|
|
727
|
+
columns,
|
|
728
|
+
columnTypes: columns.map(() => ""),
|
|
729
|
+
rows: resultRows,
|
|
730
|
+
rowsAffected,
|
|
731
|
+
lastInsertRowid: void 0,
|
|
732
|
+
toJSON() {
|
|
733
|
+
return {
|
|
734
|
+
columns,
|
|
735
|
+
columnTypes: columns.map(() => ""),
|
|
736
|
+
rows,
|
|
737
|
+
rowsAffected,
|
|
738
|
+
lastInsertRowid: void 0
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
async function loadPrismaClient() {
|
|
744
|
+
if (!prismaClientPromise) {
|
|
745
|
+
prismaClientPromise = (async () => {
|
|
746
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
747
|
+
if (explicitPath) {
|
|
748
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
749
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
750
|
+
if (!PrismaClient2) {
|
|
751
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
752
|
+
}
|
|
753
|
+
return new PrismaClient2();
|
|
754
|
+
}
|
|
755
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
756
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
757
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
758
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
759
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
760
|
+
if (!PrismaClient) {
|
|
761
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
762
|
+
}
|
|
763
|
+
return new PrismaClient();
|
|
764
|
+
})();
|
|
765
|
+
}
|
|
766
|
+
return prismaClientPromise;
|
|
767
|
+
}
|
|
768
|
+
async function ensureCompatibilityViews(prisma) {
|
|
769
|
+
if (!compatibilityBootstrapPromise) {
|
|
770
|
+
compatibilityBootstrapPromise = (async () => {
|
|
771
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
772
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
773
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
774
|
+
"SELECT to_regclass($1) AS regclass",
|
|
775
|
+
relation
|
|
776
|
+
);
|
|
777
|
+
if (!rows[0]?.regclass) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
await prisma.$executeRawUnsafe(
|
|
781
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
})();
|
|
785
|
+
}
|
|
786
|
+
return compatibilityBootstrapPromise;
|
|
787
|
+
}
|
|
788
|
+
async function executeOnPrisma(executor, stmt) {
|
|
789
|
+
const translated = translateStatementForPostgres(stmt);
|
|
790
|
+
if (isReadQuery(translated.sql)) {
|
|
791
|
+
const rows = await executor.$queryRawUnsafe(
|
|
792
|
+
translated.sql,
|
|
793
|
+
...translated.args
|
|
794
|
+
);
|
|
795
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
796
|
+
}
|
|
797
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
798
|
+
return buildResultSet([], rowsAffected);
|
|
799
|
+
}
|
|
800
|
+
function splitSqlStatements(sql) {
|
|
801
|
+
const parts = [];
|
|
802
|
+
let current = "";
|
|
803
|
+
let inSingle = false;
|
|
804
|
+
let inDouble = false;
|
|
805
|
+
let inLineComment = false;
|
|
806
|
+
let inBlockComment = false;
|
|
807
|
+
for (let i = 0; i < sql.length; i++) {
|
|
808
|
+
const ch = sql[i];
|
|
809
|
+
const next = sql[i + 1];
|
|
810
|
+
if (inLineComment) {
|
|
811
|
+
current += ch;
|
|
812
|
+
if (ch === "\n") inLineComment = false;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (inBlockComment) {
|
|
816
|
+
current += ch;
|
|
817
|
+
if (ch === "*" && next === "/") {
|
|
818
|
+
current += next;
|
|
819
|
+
inBlockComment = false;
|
|
820
|
+
i += 1;
|
|
821
|
+
}
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
825
|
+
current += ch + next;
|
|
826
|
+
inLineComment = true;
|
|
827
|
+
i += 1;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
831
|
+
current += ch + next;
|
|
832
|
+
inBlockComment = true;
|
|
833
|
+
i += 1;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
837
|
+
inSingle = !inSingle;
|
|
838
|
+
current += ch;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
842
|
+
inDouble = !inDouble;
|
|
843
|
+
current += ch;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
847
|
+
if (current.trim()) {
|
|
848
|
+
parts.push(current.trim());
|
|
849
|
+
}
|
|
850
|
+
current = "";
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
current += ch;
|
|
854
|
+
}
|
|
855
|
+
if (current.trim()) {
|
|
856
|
+
parts.push(current.trim());
|
|
857
|
+
}
|
|
858
|
+
return parts;
|
|
859
|
+
}
|
|
860
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
861
|
+
const prisma = await loadPrismaClient();
|
|
862
|
+
await ensureCompatibilityViews(prisma);
|
|
863
|
+
let closed = false;
|
|
864
|
+
let adapter;
|
|
865
|
+
const fallbackExecute = async (stmt, error) => {
|
|
866
|
+
if (!fallbackClient) {
|
|
867
|
+
if (error) throw error;
|
|
868
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
869
|
+
}
|
|
870
|
+
if (error) {
|
|
871
|
+
process.stderr.write(
|
|
872
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
873
|
+
`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
return fallbackClient.execute(stmt);
|
|
877
|
+
};
|
|
878
|
+
adapter = {
|
|
879
|
+
async execute(stmt) {
|
|
880
|
+
if (shouldBypassPostgres(stmt)) {
|
|
881
|
+
return fallbackExecute(stmt);
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
return await executeOnPrisma(prisma, stmt);
|
|
885
|
+
} catch (error) {
|
|
886
|
+
if (shouldFallbackOnError(error)) {
|
|
887
|
+
return fallbackExecute(stmt, error);
|
|
888
|
+
}
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
async batch(stmts, mode) {
|
|
893
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
894
|
+
if (!fallbackClient) {
|
|
895
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
896
|
+
}
|
|
897
|
+
return fallbackClient.batch(stmts, mode);
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
if (prisma.$transaction) {
|
|
901
|
+
return await prisma.$transaction(async (tx) => {
|
|
902
|
+
const results2 = [];
|
|
903
|
+
for (const stmt of stmts) {
|
|
904
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
905
|
+
}
|
|
906
|
+
return results2;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
const results = [];
|
|
910
|
+
for (const stmt of stmts) {
|
|
911
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
912
|
+
}
|
|
913
|
+
return results;
|
|
914
|
+
} catch (error) {
|
|
915
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
916
|
+
process.stderr.write(
|
|
917
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
918
|
+
`
|
|
919
|
+
);
|
|
920
|
+
return fallbackClient.batch(stmts, mode);
|
|
921
|
+
}
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
async migrate(stmts) {
|
|
926
|
+
if (fallbackClient) {
|
|
927
|
+
return fallbackClient.migrate(stmts);
|
|
928
|
+
}
|
|
929
|
+
return adapter.batch(stmts, "deferred");
|
|
930
|
+
},
|
|
931
|
+
async transaction(mode) {
|
|
932
|
+
if (!fallbackClient) {
|
|
933
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
934
|
+
}
|
|
935
|
+
return fallbackClient.transaction(mode);
|
|
936
|
+
},
|
|
937
|
+
async executeMultiple(sql) {
|
|
938
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
939
|
+
return fallbackClient.executeMultiple(sql);
|
|
940
|
+
}
|
|
941
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
942
|
+
await adapter.execute(statement);
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
async sync() {
|
|
946
|
+
if (fallbackClient) {
|
|
947
|
+
return fallbackClient.sync();
|
|
948
|
+
}
|
|
949
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
950
|
+
},
|
|
951
|
+
close() {
|
|
952
|
+
closed = true;
|
|
953
|
+
prismaClientPromise = null;
|
|
954
|
+
compatibilityBootstrapPromise = null;
|
|
955
|
+
void prisma.$disconnect?.();
|
|
956
|
+
},
|
|
957
|
+
get closed() {
|
|
958
|
+
return closed;
|
|
959
|
+
},
|
|
960
|
+
get protocol() {
|
|
961
|
+
return "prisma-postgres";
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
return adapter;
|
|
965
|
+
}
|
|
966
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
967
|
+
var init_database_adapter = __esm({
|
|
968
|
+
"src/lib/database-adapter.ts"() {
|
|
969
|
+
"use strict";
|
|
970
|
+
VIEW_MAPPINGS = [
|
|
971
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
972
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
973
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
974
|
+
{ view: "entities", source: "memory.entities" },
|
|
975
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
976
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
977
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
978
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
979
|
+
{ view: "messages", source: "memory.messages" },
|
|
980
|
+
{ view: "users", source: "wiki.users" },
|
|
981
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
982
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
983
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
984
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
985
|
+
];
|
|
986
|
+
UPSERT_KEYS = {
|
|
987
|
+
memories: ["id"],
|
|
988
|
+
tasks: ["id"],
|
|
989
|
+
behaviors: ["id"],
|
|
990
|
+
entities: ["id"],
|
|
991
|
+
relationships: ["id"],
|
|
992
|
+
entity_aliases: ["alias"],
|
|
993
|
+
notifications: ["id"],
|
|
994
|
+
messages: ["id"],
|
|
995
|
+
users: ["id"],
|
|
996
|
+
workspaces: ["id"],
|
|
997
|
+
workspace_users: ["id"],
|
|
998
|
+
documents: ["id"],
|
|
999
|
+
chats: ["id"]
|
|
1000
|
+
};
|
|
1001
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1002
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1003
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1004
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1005
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1006
|
+
};
|
|
1007
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1008
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1009
|
+
);
|
|
1010
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1011
|
+
/\bPRAGMA\b/i,
|
|
1012
|
+
/\bsqlite_master\b/i,
|
|
1013
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1014
|
+
/\bMATCH\b/i,
|
|
1015
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1016
|
+
/\bjson_extract\s*\(/i,
|
|
1017
|
+
/\bjulianday\s*\(/i,
|
|
1018
|
+
/\bstrftime\s*\(/i,
|
|
1019
|
+
/\blast_insert_rowid\s*\(/i
|
|
1020
|
+
];
|
|
1021
|
+
prismaClientPromise = null;
|
|
1022
|
+
compatibilityBootstrapPromise = null;
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// src/lib/daemon-auth.ts
|
|
1027
|
+
import crypto from "crypto";
|
|
1028
|
+
import path4 from "path";
|
|
1029
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1030
|
+
function normalizeToken(token) {
|
|
1031
|
+
if (!token) return null;
|
|
1032
|
+
const trimmed = token.trim();
|
|
1033
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1034
|
+
}
|
|
1035
|
+
function readDaemonToken() {
|
|
1036
|
+
try {
|
|
1037
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
1038
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1039
|
+
} catch {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function ensureDaemonToken(seed) {
|
|
1044
|
+
const existing = readDaemonToken();
|
|
1045
|
+
if (existing) return existing;
|
|
1046
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1047
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1048
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1049
|
+
`, "utf8");
|
|
1050
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1051
|
+
return token;
|
|
1052
|
+
}
|
|
1053
|
+
var DAEMON_TOKEN_PATH;
|
|
1054
|
+
var init_daemon_auth = __esm({
|
|
1055
|
+
"src/lib/daemon-auth.ts"() {
|
|
1056
|
+
"use strict";
|
|
1057
|
+
init_config();
|
|
1058
|
+
init_secure_files();
|
|
1059
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
400
1060
|
}
|
|
401
1061
|
});
|
|
402
1062
|
|
|
403
1063
|
// src/lib/exe-daemon-client.ts
|
|
404
1064
|
import net from "net";
|
|
405
|
-
import
|
|
1065
|
+
import os4 from "os";
|
|
406
1066
|
import { spawn } from "child_process";
|
|
407
1067
|
import { randomUUID } from "crypto";
|
|
408
|
-
import { existsSync as
|
|
409
|
-
import
|
|
1068
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1069
|
+
import path5 from "path";
|
|
410
1070
|
import { fileURLToPath } from "url";
|
|
411
1071
|
function handleData(chunk) {
|
|
412
1072
|
_buffer += chunk.toString();
|
|
@@ -434,9 +1094,9 @@ function handleData(chunk) {
|
|
|
434
1094
|
}
|
|
435
1095
|
}
|
|
436
1096
|
function cleanupStaleFiles() {
|
|
437
|
-
if (
|
|
1097
|
+
if (existsSync5(PID_PATH)) {
|
|
438
1098
|
try {
|
|
439
|
-
const pid = parseInt(
|
|
1099
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
440
1100
|
if (pid > 0) {
|
|
441
1101
|
try {
|
|
442
1102
|
process.kill(pid, 0);
|
|
@@ -457,17 +1117,17 @@ function cleanupStaleFiles() {
|
|
|
457
1117
|
}
|
|
458
1118
|
}
|
|
459
1119
|
function findPackageRoot() {
|
|
460
|
-
let dir =
|
|
461
|
-
const { root } =
|
|
1120
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1121
|
+
const { root } = path5.parse(dir);
|
|
462
1122
|
while (dir !== root) {
|
|
463
|
-
if (
|
|
464
|
-
dir =
|
|
1123
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1124
|
+
dir = path5.dirname(dir);
|
|
465
1125
|
}
|
|
466
1126
|
return null;
|
|
467
1127
|
}
|
|
468
1128
|
function spawnDaemon() {
|
|
469
|
-
const freeGB =
|
|
470
|
-
const totalGB =
|
|
1129
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
1130
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
471
1131
|
if (totalGB <= 8) {
|
|
472
1132
|
process.stderr.write(
|
|
473
1133
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -487,16 +1147,17 @@ function spawnDaemon() {
|
|
|
487
1147
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
488
1148
|
return;
|
|
489
1149
|
}
|
|
490
|
-
const daemonPath =
|
|
491
|
-
if (!
|
|
1150
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1151
|
+
if (!existsSync5(daemonPath)) {
|
|
492
1152
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
493
1153
|
`);
|
|
494
1154
|
return;
|
|
495
1155
|
}
|
|
496
1156
|
const resolvedPath = daemonPath;
|
|
1157
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
497
1158
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
498
1159
|
`);
|
|
499
|
-
const logPath =
|
|
1160
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
500
1161
|
let stderrFd = "ignore";
|
|
501
1162
|
try {
|
|
502
1163
|
stderrFd = openSync(logPath, "a");
|
|
@@ -514,7 +1175,8 @@ function spawnDaemon() {
|
|
|
514
1175
|
TMUX_PANE: void 0,
|
|
515
1176
|
// Prevents resolveExeSession() from scoping to one session
|
|
516
1177
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
517
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1178
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1179
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
518
1180
|
}
|
|
519
1181
|
});
|
|
520
1182
|
child.unref();
|
|
@@ -624,13 +1286,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
624
1286
|
return;
|
|
625
1287
|
}
|
|
626
1288
|
const id = randomUUID();
|
|
1289
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
627
1290
|
const timer = setTimeout(() => {
|
|
628
1291
|
_pending.delete(id);
|
|
629
1292
|
resolve({ error: "Request timeout" });
|
|
630
1293
|
}, timeoutMs);
|
|
631
1294
|
_pending.set(id, { resolve, timer });
|
|
632
1295
|
try {
|
|
633
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1296
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
634
1297
|
} catch {
|
|
635
1298
|
clearTimeout(timer);
|
|
636
1299
|
_pending.delete(id);
|
|
@@ -647,74 +1310,123 @@ async function pingDaemon() {
|
|
|
647
1310
|
return null;
|
|
648
1311
|
}
|
|
649
1312
|
function killAndRespawnDaemon() {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1313
|
+
if (!acquireSpawnLock()) {
|
|
1314
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
1315
|
+
if (_socket) {
|
|
1316
|
+
_socket.destroy();
|
|
1317
|
+
_socket = null;
|
|
1318
|
+
}
|
|
1319
|
+
_connected = false;
|
|
1320
|
+
_buffer = "";
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
try {
|
|
1324
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1325
|
+
if (existsSync5(PID_PATH)) {
|
|
1326
|
+
try {
|
|
1327
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
1328
|
+
if (pid > 0) {
|
|
1329
|
+
try {
|
|
1330
|
+
process.kill(pid, "SIGKILL");
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
658
1333
|
}
|
|
1334
|
+
} catch {
|
|
659
1335
|
}
|
|
1336
|
+
}
|
|
1337
|
+
if (_socket) {
|
|
1338
|
+
_socket.destroy();
|
|
1339
|
+
_socket = null;
|
|
1340
|
+
}
|
|
1341
|
+
_connected = false;
|
|
1342
|
+
_buffer = "";
|
|
1343
|
+
try {
|
|
1344
|
+
unlinkSync2(PID_PATH);
|
|
660
1345
|
} catch {
|
|
661
1346
|
}
|
|
1347
|
+
try {
|
|
1348
|
+
unlinkSync2(SOCKET_PATH);
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
spawnDaemon();
|
|
1352
|
+
} finally {
|
|
1353
|
+
releaseSpawnLock();
|
|
662
1354
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
_socket = null;
|
|
666
|
-
}
|
|
667
|
-
_connected = false;
|
|
668
|
-
_buffer = "";
|
|
1355
|
+
}
|
|
1356
|
+
function isDaemonTooYoung() {
|
|
669
1357
|
try {
|
|
670
|
-
|
|
1358
|
+
const stat = statSync(PID_PATH);
|
|
1359
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
671
1360
|
} catch {
|
|
1361
|
+
return false;
|
|
672
1362
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1363
|
+
}
|
|
1364
|
+
async function retryThenRestart(doRequest, label) {
|
|
1365
|
+
const result = await doRequest();
|
|
1366
|
+
if (!result.error) {
|
|
1367
|
+
_consecutiveFailures = 0;
|
|
1368
|
+
return result;
|
|
676
1369
|
}
|
|
677
|
-
|
|
1370
|
+
_consecutiveFailures++;
|
|
1371
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
1372
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
1373
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
1374
|
+
`);
|
|
1375
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
1376
|
+
if (!_connected) {
|
|
1377
|
+
if (!await connectToSocket()) continue;
|
|
1378
|
+
}
|
|
1379
|
+
const retry = await doRequest();
|
|
1380
|
+
if (!retry.error) {
|
|
1381
|
+
_consecutiveFailures = 0;
|
|
1382
|
+
return retry;
|
|
1383
|
+
}
|
|
1384
|
+
_consecutiveFailures++;
|
|
1385
|
+
}
|
|
1386
|
+
if (isDaemonTooYoung()) {
|
|
1387
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
1388
|
+
`);
|
|
1389
|
+
return { error: result.error };
|
|
1390
|
+
}
|
|
1391
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
1392
|
+
`);
|
|
1393
|
+
killAndRespawnDaemon();
|
|
1394
|
+
const start = Date.now();
|
|
1395
|
+
let delay2 = 200;
|
|
1396
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1397
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1398
|
+
if (await connectToSocket()) break;
|
|
1399
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1400
|
+
}
|
|
1401
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
1402
|
+
const final = await doRequest();
|
|
1403
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
1404
|
+
return final;
|
|
678
1405
|
}
|
|
679
1406
|
async function embedViaClient(text, priority = "high") {
|
|
680
1407
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
681
1408
|
_requestCount++;
|
|
682
1409
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
683
1410
|
const health = await pingDaemon();
|
|
684
|
-
if (!health) {
|
|
1411
|
+
if (!health && !isDaemonTooYoung()) {
|
|
685
1412
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
686
1413
|
`);
|
|
687
1414
|
killAndRespawnDaemon();
|
|
688
1415
|
const start = Date.now();
|
|
689
|
-
let
|
|
1416
|
+
let d = 200;
|
|
690
1417
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
691
|
-
await new Promise((r) => setTimeout(r,
|
|
1418
|
+
await new Promise((r) => setTimeout(r, d));
|
|
692
1419
|
if (await connectToSocket()) break;
|
|
693
|
-
|
|
1420
|
+
d = Math.min(d * 2, 3e3);
|
|
694
1421
|
}
|
|
695
1422
|
if (!_connected) return null;
|
|
696
1423
|
}
|
|
697
1424
|
}
|
|
698
|
-
const result = await
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
killAndRespawnDaemon();
|
|
704
|
-
const start = Date.now();
|
|
705
|
-
let delay2 = 200;
|
|
706
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
707
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
708
|
-
if (await connectToSocket()) break;
|
|
709
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
710
|
-
}
|
|
711
|
-
if (!_connected) return null;
|
|
712
|
-
const retry = await sendRequest([text], priority);
|
|
713
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
714
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
715
|
-
`);
|
|
716
|
-
}
|
|
717
|
-
return null;
|
|
1425
|
+
const result = await retryThenRestart(
|
|
1426
|
+
() => sendRequest([text], priority),
|
|
1427
|
+
"Embed"
|
|
1428
|
+
);
|
|
1429
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
718
1430
|
}
|
|
719
1431
|
function disconnectClient() {
|
|
720
1432
|
if (_socket) {
|
|
@@ -732,22 +1444,28 @@ function disconnectClient() {
|
|
|
732
1444
|
function isClientConnected() {
|
|
733
1445
|
return _connected;
|
|
734
1446
|
}
|
|
735
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
1447
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
736
1448
|
var init_exe_daemon_client = __esm({
|
|
737
1449
|
"src/lib/exe-daemon-client.ts"() {
|
|
738
1450
|
"use strict";
|
|
739
1451
|
init_config();
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1452
|
+
init_daemon_auth();
|
|
1453
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1454
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1455
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
743
1456
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
744
1457
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
745
1458
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1459
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
746
1460
|
_socket = null;
|
|
747
1461
|
_connected = false;
|
|
748
1462
|
_buffer = "";
|
|
749
1463
|
_requestCount = 0;
|
|
1464
|
+
_consecutiveFailures = 0;
|
|
750
1465
|
HEALTH_CHECK_INTERVAL = 100;
|
|
1466
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
1467
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
1468
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
751
1469
|
_pending = /* @__PURE__ */ new Map();
|
|
752
1470
|
MAX_BUFFER = 1e7;
|
|
753
1471
|
}
|
|
@@ -823,7 +1541,7 @@ __export(db_daemon_client_exports, {
|
|
|
823
1541
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
824
1542
|
initDaemonDbClient: () => initDaemonDbClient
|
|
825
1543
|
});
|
|
826
|
-
function
|
|
1544
|
+
function normalizeStatement2(stmt) {
|
|
827
1545
|
if (typeof stmt === "string") {
|
|
828
1546
|
return { sql: stmt, args: [] };
|
|
829
1547
|
}
|
|
@@ -847,7 +1565,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
847
1565
|
if (!_useDaemon || !isClientConnected()) {
|
|
848
1566
|
return fallbackClient.execute(stmt);
|
|
849
1567
|
}
|
|
850
|
-
const { sql, args } =
|
|
1568
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
851
1569
|
const response = await sendDaemonRequest({
|
|
852
1570
|
type: "db-execute",
|
|
853
1571
|
sql,
|
|
@@ -872,7 +1590,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
872
1590
|
if (!_useDaemon || !isClientConnected()) {
|
|
873
1591
|
return fallbackClient.batch(stmts, mode);
|
|
874
1592
|
}
|
|
875
|
-
const statements = stmts.map(
|
|
1593
|
+
const statements = stmts.map(normalizeStatement2);
|
|
876
1594
|
const response = await sendDaemonRequest({
|
|
877
1595
|
type: "db-batch",
|
|
878
1596
|
statements,
|
|
@@ -967,6 +1685,18 @@ __export(database_exports, {
|
|
|
967
1685
|
});
|
|
968
1686
|
import { createClient } from "@libsql/client";
|
|
969
1687
|
async function initDatabase(config) {
|
|
1688
|
+
if (_walCheckpointTimer) {
|
|
1689
|
+
clearInterval(_walCheckpointTimer);
|
|
1690
|
+
_walCheckpointTimer = null;
|
|
1691
|
+
}
|
|
1692
|
+
if (_daemonClient) {
|
|
1693
|
+
_daemonClient.close();
|
|
1694
|
+
_daemonClient = null;
|
|
1695
|
+
}
|
|
1696
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1697
|
+
_adapterClient.close();
|
|
1698
|
+
}
|
|
1699
|
+
_adapterClient = null;
|
|
970
1700
|
if (_client) {
|
|
971
1701
|
_client.close();
|
|
972
1702
|
_client = null;
|
|
@@ -980,6 +1710,7 @@ async function initDatabase(config) {
|
|
|
980
1710
|
}
|
|
981
1711
|
_client = createClient(opts);
|
|
982
1712
|
_resilientClient = wrapWithRetry(_client);
|
|
1713
|
+
_adapterClient = _resilientClient;
|
|
983
1714
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
984
1715
|
});
|
|
985
1716
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -990,14 +1721,20 @@ async function initDatabase(config) {
|
|
|
990
1721
|
});
|
|
991
1722
|
}, 3e4);
|
|
992
1723
|
_walCheckpointTimer.unref();
|
|
1724
|
+
if (process.env.DATABASE_URL) {
|
|
1725
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1726
|
+
}
|
|
993
1727
|
}
|
|
994
1728
|
function isInitialized() {
|
|
995
|
-
return _client !== null;
|
|
1729
|
+
return _adapterClient !== null || _client !== null;
|
|
996
1730
|
}
|
|
997
1731
|
function getClient() {
|
|
998
|
-
if (!
|
|
1732
|
+
if (!_adapterClient) {
|
|
999
1733
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1000
1734
|
}
|
|
1735
|
+
if (process.env.DATABASE_URL) {
|
|
1736
|
+
return _adapterClient;
|
|
1737
|
+
}
|
|
1001
1738
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1002
1739
|
return _resilientClient;
|
|
1003
1740
|
}
|
|
@@ -1007,6 +1744,7 @@ function getClient() {
|
|
|
1007
1744
|
return _resilientClient;
|
|
1008
1745
|
}
|
|
1009
1746
|
async function initDaemonClient() {
|
|
1747
|
+
if (process.env.DATABASE_URL) return;
|
|
1010
1748
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1011
1749
|
if (!_resilientClient) return;
|
|
1012
1750
|
try {
|
|
@@ -1303,6 +2041,7 @@ async function ensureSchema() {
|
|
|
1303
2041
|
project TEXT NOT NULL,
|
|
1304
2042
|
summary TEXT NOT NULL,
|
|
1305
2043
|
task_file TEXT,
|
|
2044
|
+
session_scope TEXT,
|
|
1306
2045
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1307
2046
|
created_at TEXT NOT NULL
|
|
1308
2047
|
);
|
|
@@ -1311,7 +2050,7 @@ async function ensureSchema() {
|
|
|
1311
2050
|
ON notifications(read);
|
|
1312
2051
|
|
|
1313
2052
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1314
|
-
ON notifications(agent_id);
|
|
2053
|
+
ON notifications(agent_id, session_scope);
|
|
1315
2054
|
|
|
1316
2055
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1317
2056
|
ON notifications(task_file);
|
|
@@ -1349,6 +2088,7 @@ async function ensureSchema() {
|
|
|
1349
2088
|
target_agent TEXT NOT NULL,
|
|
1350
2089
|
target_project TEXT,
|
|
1351
2090
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2091
|
+
session_scope TEXT,
|
|
1352
2092
|
content TEXT NOT NULL,
|
|
1353
2093
|
priority TEXT DEFAULT 'normal',
|
|
1354
2094
|
status TEXT DEFAULT 'pending',
|
|
@@ -1362,10 +2102,31 @@ async function ensureSchema() {
|
|
|
1362
2102
|
);
|
|
1363
2103
|
|
|
1364
2104
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1365
|
-
ON messages(target_agent, status);
|
|
2105
|
+
ON messages(target_agent, session_scope, status);
|
|
1366
2106
|
|
|
1367
2107
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1368
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2108
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2109
|
+
`);
|
|
2110
|
+
try {
|
|
2111
|
+
await client.execute({
|
|
2112
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2113
|
+
args: []
|
|
2114
|
+
});
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
try {
|
|
2118
|
+
await client.execute({
|
|
2119
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2120
|
+
args: []
|
|
2121
|
+
});
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
2124
|
+
await client.executeMultiple(`
|
|
2125
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2126
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2127
|
+
|
|
2128
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2129
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1369
2130
|
`);
|
|
1370
2131
|
try {
|
|
1371
2132
|
await client.execute({
|
|
@@ -1949,28 +2710,45 @@ async function ensureSchema() {
|
|
|
1949
2710
|
} catch {
|
|
1950
2711
|
}
|
|
1951
2712
|
}
|
|
2713
|
+
try {
|
|
2714
|
+
await client.execute({
|
|
2715
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2716
|
+
args: []
|
|
2717
|
+
});
|
|
2718
|
+
} catch {
|
|
2719
|
+
}
|
|
1952
2720
|
}
|
|
1953
2721
|
async function disposeDatabase() {
|
|
2722
|
+
if (_walCheckpointTimer) {
|
|
2723
|
+
clearInterval(_walCheckpointTimer);
|
|
2724
|
+
_walCheckpointTimer = null;
|
|
2725
|
+
}
|
|
1954
2726
|
if (_daemonClient) {
|
|
1955
2727
|
_daemonClient.close();
|
|
1956
2728
|
_daemonClient = null;
|
|
1957
2729
|
}
|
|
2730
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2731
|
+
_adapterClient.close();
|
|
2732
|
+
}
|
|
2733
|
+
_adapterClient = null;
|
|
1958
2734
|
if (_client) {
|
|
1959
2735
|
_client.close();
|
|
1960
2736
|
_client = null;
|
|
1961
2737
|
_resilientClient = null;
|
|
1962
2738
|
}
|
|
1963
2739
|
}
|
|
1964
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2740
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1965
2741
|
var init_database = __esm({
|
|
1966
2742
|
"src/lib/database.ts"() {
|
|
1967
2743
|
"use strict";
|
|
1968
2744
|
init_db_retry();
|
|
1969
2745
|
init_employees();
|
|
2746
|
+
init_database_adapter();
|
|
1970
2747
|
_client = null;
|
|
1971
2748
|
_resilientClient = null;
|
|
1972
2749
|
_walCheckpointTimer = null;
|
|
1973
2750
|
_daemonClient = null;
|
|
2751
|
+
_adapterClient = null;
|
|
1974
2752
|
initTurso = initDatabase;
|
|
1975
2753
|
disposeTurso = disposeDatabase;
|
|
1976
2754
|
}
|
|
@@ -1978,14 +2756,14 @@ var init_database = __esm({
|
|
|
1978
2756
|
|
|
1979
2757
|
// src/lib/keychain.ts
|
|
1980
2758
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1981
|
-
import { existsSync as
|
|
1982
|
-
import
|
|
1983
|
-
import
|
|
2759
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2760
|
+
import path6 from "path";
|
|
2761
|
+
import os5 from "os";
|
|
1984
2762
|
function getKeyDir() {
|
|
1985
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2763
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
|
|
1986
2764
|
}
|
|
1987
2765
|
function getKeyPath() {
|
|
1988
|
-
return
|
|
2766
|
+
return path6.join(getKeyDir(), "master.key");
|
|
1989
2767
|
}
|
|
1990
2768
|
async function tryKeytar() {
|
|
1991
2769
|
try {
|
|
@@ -2006,9 +2784,9 @@ async function getMasterKey() {
|
|
|
2006
2784
|
}
|
|
2007
2785
|
}
|
|
2008
2786
|
const keyPath = getKeyPath();
|
|
2009
|
-
if (!
|
|
2787
|
+
if (!existsSync6(keyPath)) {
|
|
2010
2788
|
process.stderr.write(
|
|
2011
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2789
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2012
2790
|
`
|
|
2013
2791
|
);
|
|
2014
2792
|
return null;
|
|
@@ -2093,6 +2871,7 @@ var shard_manager_exports = {};
|
|
|
2093
2871
|
__export(shard_manager_exports, {
|
|
2094
2872
|
disposeShards: () => disposeShards,
|
|
2095
2873
|
ensureShardSchema: () => ensureShardSchema,
|
|
2874
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2096
2875
|
getReadyShardClient: () => getReadyShardClient,
|
|
2097
2876
|
getShardClient: () => getShardClient,
|
|
2098
2877
|
getShardsDir: () => getShardsDir,
|
|
@@ -2101,15 +2880,18 @@ __export(shard_manager_exports, {
|
|
|
2101
2880
|
listShards: () => listShards,
|
|
2102
2881
|
shardExists: () => shardExists
|
|
2103
2882
|
});
|
|
2104
|
-
import
|
|
2105
|
-
import { existsSync as
|
|
2883
|
+
import path7 from "path";
|
|
2884
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2106
2885
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2107
2886
|
function initShardManager(encryptionKey) {
|
|
2108
2887
|
_encryptionKey = encryptionKey;
|
|
2109
|
-
if (!
|
|
2110
|
-
|
|
2888
|
+
if (!existsSync7(SHARDS_DIR)) {
|
|
2889
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2111
2890
|
}
|
|
2112
2891
|
_shardingEnabled = true;
|
|
2892
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
2893
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
2894
|
+
_evictionTimer.unref();
|
|
2113
2895
|
}
|
|
2114
2896
|
function isShardingEnabled() {
|
|
2115
2897
|
return _shardingEnabled;
|
|
@@ -2126,21 +2908,28 @@ function getShardClient(projectName) {
|
|
|
2126
2908
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2127
2909
|
}
|
|
2128
2910
|
const cached = _shards.get(safeName);
|
|
2129
|
-
if (cached)
|
|
2130
|
-
|
|
2911
|
+
if (cached) {
|
|
2912
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2913
|
+
return cached;
|
|
2914
|
+
}
|
|
2915
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
2916
|
+
evictLRU();
|
|
2917
|
+
}
|
|
2918
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
2131
2919
|
const client = createClient2({
|
|
2132
2920
|
url: `file:${dbPath}`,
|
|
2133
2921
|
encryptionKey: _encryptionKey
|
|
2134
2922
|
});
|
|
2135
2923
|
_shards.set(safeName, client);
|
|
2924
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2136
2925
|
return client;
|
|
2137
2926
|
}
|
|
2138
2927
|
function shardExists(projectName) {
|
|
2139
2928
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2140
|
-
return
|
|
2929
|
+
return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
2141
2930
|
}
|
|
2142
2931
|
function listShards() {
|
|
2143
|
-
if (!
|
|
2932
|
+
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2144
2933
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2145
2934
|
}
|
|
2146
2935
|
async function ensureShardSchema(client) {
|
|
@@ -2192,6 +2981,8 @@ async function ensureShardSchema(client) {
|
|
|
2192
2981
|
for (const col of [
|
|
2193
2982
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2194
2983
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
2984
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
2985
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2195
2986
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2196
2987
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2197
2988
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2214,7 +3005,23 @@ async function ensureShardSchema(client) {
|
|
|
2214
3005
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2215
3006
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2216
3007
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2217
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3008
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3009
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3010
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3011
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3012
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3013
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3014
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3015
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3016
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3017
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3018
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3019
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3020
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3021
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3022
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3023
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3024
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2218
3025
|
]) {
|
|
2219
3026
|
try {
|
|
2220
3027
|
await client.execute(col);
|
|
@@ -2313,21 +3120,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2313
3120
|
await ensureShardSchema(client);
|
|
2314
3121
|
return client;
|
|
2315
3122
|
}
|
|
3123
|
+
function evictLRU() {
|
|
3124
|
+
let oldest = null;
|
|
3125
|
+
let oldestTime = Infinity;
|
|
3126
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3127
|
+
if (time < oldestTime) {
|
|
3128
|
+
oldestTime = time;
|
|
3129
|
+
oldest = name;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
if (oldest) {
|
|
3133
|
+
const client = _shards.get(oldest);
|
|
3134
|
+
if (client) {
|
|
3135
|
+
client.close();
|
|
3136
|
+
}
|
|
3137
|
+
_shards.delete(oldest);
|
|
3138
|
+
_shardLastAccess.delete(oldest);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function evictIdleShards() {
|
|
3142
|
+
const now = Date.now();
|
|
3143
|
+
const toEvict = [];
|
|
3144
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3145
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3146
|
+
toEvict.push(name);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
for (const name of toEvict) {
|
|
3150
|
+
const client = _shards.get(name);
|
|
3151
|
+
if (client) {
|
|
3152
|
+
client.close();
|
|
3153
|
+
}
|
|
3154
|
+
_shards.delete(name);
|
|
3155
|
+
_shardLastAccess.delete(name);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function getOpenShardCount() {
|
|
3159
|
+
return _shards.size;
|
|
3160
|
+
}
|
|
2316
3161
|
function disposeShards() {
|
|
3162
|
+
if (_evictionTimer) {
|
|
3163
|
+
clearInterval(_evictionTimer);
|
|
3164
|
+
_evictionTimer = null;
|
|
3165
|
+
}
|
|
2317
3166
|
for (const [, client] of _shards) {
|
|
2318
3167
|
client.close();
|
|
2319
3168
|
}
|
|
2320
3169
|
_shards.clear();
|
|
3170
|
+
_shardLastAccess.clear();
|
|
2321
3171
|
_shardingEnabled = false;
|
|
2322
3172
|
_encryptionKey = null;
|
|
2323
3173
|
}
|
|
2324
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3174
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2325
3175
|
var init_shard_manager = __esm({
|
|
2326
3176
|
"src/lib/shard-manager.ts"() {
|
|
2327
3177
|
"use strict";
|
|
2328
3178
|
init_config();
|
|
2329
|
-
SHARDS_DIR =
|
|
3179
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
3180
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3181
|
+
MAX_OPEN_SHARDS = 10;
|
|
3182
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2330
3183
|
_shards = /* @__PURE__ */ new Map();
|
|
3184
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3185
|
+
_evictionTimer = null;
|
|
2331
3186
|
_encryptionKey = null;
|
|
2332
3187
|
_shardingEnabled = false;
|
|
2333
3188
|
}
|
|
@@ -3091,13 +3946,13 @@ var init_store = __esm({
|
|
|
3091
3946
|
});
|
|
3092
3947
|
|
|
3093
3948
|
// src/lib/session-registry.ts
|
|
3094
|
-
import { readFileSync as
|
|
3095
|
-
import
|
|
3096
|
-
import
|
|
3949
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
|
|
3950
|
+
import path8 from "path";
|
|
3951
|
+
import os6 from "os";
|
|
3097
3952
|
function registerSession(entry) {
|
|
3098
|
-
const dir =
|
|
3099
|
-
if (!
|
|
3100
|
-
|
|
3953
|
+
const dir = path8.dirname(REGISTRY_PATH);
|
|
3954
|
+
if (!existsSync8(dir)) {
|
|
3955
|
+
mkdirSync3(dir, { recursive: true });
|
|
3101
3956
|
}
|
|
3102
3957
|
const sessions = listSessions();
|
|
3103
3958
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -3106,11 +3961,11 @@ function registerSession(entry) {
|
|
|
3106
3961
|
} else {
|
|
3107
3962
|
sessions.push(entry);
|
|
3108
3963
|
}
|
|
3109
|
-
|
|
3964
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
3110
3965
|
}
|
|
3111
3966
|
function listSessions() {
|
|
3112
3967
|
try {
|
|
3113
|
-
const raw =
|
|
3968
|
+
const raw = readFileSync5(REGISTRY_PATH, "utf8");
|
|
3114
3969
|
return JSON.parse(raw);
|
|
3115
3970
|
} catch {
|
|
3116
3971
|
return [];
|
|
@@ -3120,7 +3975,7 @@ var REGISTRY_PATH;
|
|
|
3120
3975
|
var init_session_registry = __esm({
|
|
3121
3976
|
"src/lib/session-registry.ts"() {
|
|
3122
3977
|
"use strict";
|
|
3123
|
-
REGISTRY_PATH =
|
|
3978
|
+
REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
3124
3979
|
}
|
|
3125
3980
|
});
|
|
3126
3981
|
|
|
@@ -3400,12 +4255,12 @@ var init_runtime_table = __esm({
|
|
|
3400
4255
|
});
|
|
3401
4256
|
|
|
3402
4257
|
// src/lib/agent-config.ts
|
|
3403
|
-
import { readFileSync as
|
|
3404
|
-
import
|
|
4258
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
|
|
4259
|
+
import path9 from "path";
|
|
3405
4260
|
function loadAgentConfig() {
|
|
3406
|
-
if (!
|
|
4261
|
+
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
3407
4262
|
try {
|
|
3408
|
-
return JSON.parse(
|
|
4263
|
+
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
3409
4264
|
} catch {
|
|
3410
4265
|
return {};
|
|
3411
4266
|
}
|
|
@@ -3424,7 +4279,8 @@ var init_agent_config = __esm({
|
|
|
3424
4279
|
"use strict";
|
|
3425
4280
|
init_config();
|
|
3426
4281
|
init_runtime_table();
|
|
3427
|
-
|
|
4282
|
+
init_secure_files();
|
|
4283
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
3428
4284
|
DEFAULT_MODELS = {
|
|
3429
4285
|
claude: "claude-opus-4",
|
|
3430
4286
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -3442,17 +4298,17 @@ __export(intercom_queue_exports, {
|
|
|
3442
4298
|
queueIntercom: () => queueIntercom,
|
|
3443
4299
|
readQueue: () => readQueue
|
|
3444
4300
|
});
|
|
3445
|
-
import { readFileSync as
|
|
3446
|
-
import
|
|
3447
|
-
import
|
|
4301
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
4302
|
+
import path10 from "path";
|
|
4303
|
+
import os7 from "os";
|
|
3448
4304
|
function ensureDir() {
|
|
3449
|
-
const dir =
|
|
3450
|
-
if (!
|
|
4305
|
+
const dir = path10.dirname(QUEUE_PATH);
|
|
4306
|
+
if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
|
|
3451
4307
|
}
|
|
3452
4308
|
function readQueue() {
|
|
3453
4309
|
try {
|
|
3454
|
-
if (!
|
|
3455
|
-
return JSON.parse(
|
|
4310
|
+
if (!existsSync10(QUEUE_PATH)) return [];
|
|
4311
|
+
return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
|
|
3456
4312
|
} catch {
|
|
3457
4313
|
return [];
|
|
3458
4314
|
}
|
|
@@ -3460,7 +4316,7 @@ function readQueue() {
|
|
|
3460
4316
|
function writeQueue(queue) {
|
|
3461
4317
|
ensureDir();
|
|
3462
4318
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
3463
|
-
|
|
4319
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
3464
4320
|
renameSync3(tmp, QUEUE_PATH);
|
|
3465
4321
|
}
|
|
3466
4322
|
function queueIntercom(targetSession, reason) {
|
|
@@ -3552,26 +4408,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
3552
4408
|
var init_intercom_queue = __esm({
|
|
3553
4409
|
"src/lib/intercom-queue.ts"() {
|
|
3554
4410
|
"use strict";
|
|
3555
|
-
QUEUE_PATH =
|
|
4411
|
+
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
3556
4412
|
MAX_RETRIES2 = 5;
|
|
3557
4413
|
TTL_MS = 60 * 60 * 1e3;
|
|
3558
|
-
INTERCOM_LOG =
|
|
4414
|
+
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3559
4415
|
}
|
|
3560
4416
|
});
|
|
3561
4417
|
|
|
3562
4418
|
// src/lib/license.ts
|
|
3563
|
-
import { readFileSync as
|
|
4419
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
|
|
3564
4420
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3565
|
-
import
|
|
4421
|
+
import { createRequire as createRequire2 } from "module";
|
|
4422
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
4423
|
+
import os8 from "os";
|
|
4424
|
+
import path11 from "path";
|
|
3566
4425
|
import { jwtVerify, importSPKI } from "jose";
|
|
3567
4426
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
3568
4427
|
var init_license = __esm({
|
|
3569
4428
|
"src/lib/license.ts"() {
|
|
3570
4429
|
"use strict";
|
|
3571
4430
|
init_config();
|
|
3572
|
-
LICENSE_PATH =
|
|
3573
|
-
CACHE_PATH =
|
|
3574
|
-
DEVICE_ID_PATH =
|
|
4431
|
+
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
4432
|
+
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4433
|
+
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
3575
4434
|
PLAN_LIMITS = {
|
|
3576
4435
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3577
4436
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3583,12 +4442,12 @@ var init_license = __esm({
|
|
|
3583
4442
|
});
|
|
3584
4443
|
|
|
3585
4444
|
// src/lib/plan-limits.ts
|
|
3586
|
-
import { readFileSync as
|
|
3587
|
-
import
|
|
4445
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
|
|
4446
|
+
import path12 from "path";
|
|
3588
4447
|
function getLicenseSync() {
|
|
3589
4448
|
try {
|
|
3590
|
-
if (!
|
|
3591
|
-
const raw = JSON.parse(
|
|
4449
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
4450
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
3592
4451
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3593
4452
|
const parts = raw.token.split(".");
|
|
3594
4453
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3626,8 +4485,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3626
4485
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3627
4486
|
let count = 0;
|
|
3628
4487
|
try {
|
|
3629
|
-
if (
|
|
3630
|
-
const raw =
|
|
4488
|
+
if (existsSync12(filePath)) {
|
|
4489
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3631
4490
|
const employees = JSON.parse(raw);
|
|
3632
4491
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3633
4492
|
}
|
|
@@ -3656,29 +4515,30 @@ var init_plan_limits = __esm({
|
|
|
3656
4515
|
this.name = "PlanLimitError";
|
|
3657
4516
|
}
|
|
3658
4517
|
};
|
|
3659
|
-
CACHE_PATH2 =
|
|
4518
|
+
CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
3660
4519
|
}
|
|
3661
4520
|
});
|
|
3662
4521
|
|
|
3663
4522
|
// src/lib/notifications.ts
|
|
3664
|
-
import
|
|
3665
|
-
import
|
|
3666
|
-
import
|
|
4523
|
+
import crypto2 from "crypto";
|
|
4524
|
+
import path13 from "path";
|
|
4525
|
+
import os9 from "os";
|
|
3667
4526
|
import {
|
|
3668
|
-
readFileSync as
|
|
4527
|
+
readFileSync as readFileSync10,
|
|
3669
4528
|
readdirSync as readdirSync2,
|
|
3670
4529
|
unlinkSync as unlinkSync3,
|
|
3671
|
-
existsSync as
|
|
4530
|
+
existsSync as existsSync13,
|
|
3672
4531
|
rmdirSync
|
|
3673
4532
|
} from "fs";
|
|
3674
4533
|
async function writeNotification(notification) {
|
|
3675
4534
|
try {
|
|
3676
4535
|
const client = getClient();
|
|
3677
|
-
const id =
|
|
4536
|
+
const id = crypto2.randomUUID();
|
|
3678
4537
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4538
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3679
4539
|
await client.execute({
|
|
3680
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3681
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4540
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4541
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3682
4542
|
args: [
|
|
3683
4543
|
id,
|
|
3684
4544
|
notification.agentId,
|
|
@@ -3687,6 +4547,7 @@ async function writeNotification(notification) {
|
|
|
3687
4547
|
notification.project,
|
|
3688
4548
|
notification.summary,
|
|
3689
4549
|
notification.taskFile ?? null,
|
|
4550
|
+
sessionScope,
|
|
3690
4551
|
now
|
|
3691
4552
|
]
|
|
3692
4553
|
});
|
|
@@ -3695,12 +4556,14 @@ async function writeNotification(notification) {
|
|
|
3695
4556
|
`);
|
|
3696
4557
|
}
|
|
3697
4558
|
}
|
|
3698
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
4559
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3699
4560
|
try {
|
|
3700
4561
|
const client = getClient();
|
|
4562
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3701
4563
|
await client.execute({
|
|
3702
|
-
sql:
|
|
3703
|
-
|
|
4564
|
+
sql: `UPDATE notifications SET read = 1
|
|
4565
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
4566
|
+
args: [taskFile, ...scope.args]
|
|
3704
4567
|
});
|
|
3705
4568
|
} catch {
|
|
3706
4569
|
}
|
|
@@ -3709,11 +4572,12 @@ var init_notifications = __esm({
|
|
|
3709
4572
|
"src/lib/notifications.ts"() {
|
|
3710
4573
|
"use strict";
|
|
3711
4574
|
init_database();
|
|
4575
|
+
init_task_scope();
|
|
3712
4576
|
}
|
|
3713
4577
|
});
|
|
3714
4578
|
|
|
3715
4579
|
// src/lib/session-kill-telemetry.ts
|
|
3716
|
-
import
|
|
4580
|
+
import crypto3 from "crypto";
|
|
3717
4581
|
async function recordSessionKill(input) {
|
|
3718
4582
|
try {
|
|
3719
4583
|
const client = getClient();
|
|
@@ -3723,7 +4587,7 @@ async function recordSessionKill(input) {
|
|
|
3723
4587
|
ticks_idle, estimated_tokens_saved)
|
|
3724
4588
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3725
4589
|
args: [
|
|
3726
|
-
|
|
4590
|
+
crypto3.randomUUID(),
|
|
3727
4591
|
input.sessionName,
|
|
3728
4592
|
input.agentId,
|
|
3729
4593
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3763,12 +4627,12 @@ __export(tasks_crud_exports, {
|
|
|
3763
4627
|
updateTaskStatus: () => updateTaskStatus,
|
|
3764
4628
|
writeCheckpoint: () => writeCheckpoint
|
|
3765
4629
|
});
|
|
3766
|
-
import
|
|
3767
|
-
import
|
|
3768
|
-
import
|
|
4630
|
+
import crypto4 from "crypto";
|
|
4631
|
+
import path14 from "path";
|
|
4632
|
+
import os10 from "os";
|
|
3769
4633
|
import { execSync as execSync4 } from "child_process";
|
|
3770
4634
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3771
|
-
import { existsSync as
|
|
4635
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3772
4636
|
async function writeCheckpoint(input) {
|
|
3773
4637
|
const client = getClient();
|
|
3774
4638
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3884,7 +4748,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3884
4748
|
}
|
|
3885
4749
|
async function createTaskCore(input) {
|
|
3886
4750
|
const client = getClient();
|
|
3887
|
-
const id =
|
|
4751
|
+
const id = crypto4.randomUUID();
|
|
3888
4752
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3889
4753
|
const slug = slugify(input.title);
|
|
3890
4754
|
let earlySessionScope = null;
|
|
@@ -3943,8 +4807,8 @@ ${laneWarning}` : laneWarning;
|
|
|
3943
4807
|
}
|
|
3944
4808
|
if (input.baseDir) {
|
|
3945
4809
|
try {
|
|
3946
|
-
await mkdir4(
|
|
3947
|
-
await mkdir4(
|
|
4810
|
+
await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4811
|
+
await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3948
4812
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3949
4813
|
await ensureGitignoreExe(input.baseDir);
|
|
3950
4814
|
} catch {
|
|
@@ -3980,13 +4844,19 @@ ${laneWarning}` : laneWarning;
|
|
|
3980
4844
|
});
|
|
3981
4845
|
if (input.baseDir) {
|
|
3982
4846
|
try {
|
|
3983
|
-
const EXE_OS_DIR =
|
|
3984
|
-
const mdPath =
|
|
3985
|
-
const mdDir =
|
|
3986
|
-
if (!
|
|
4847
|
+
const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
|
|
4848
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
4849
|
+
const mdDir = path14.dirname(mdPath);
|
|
4850
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
3987
4851
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3988
4852
|
const mdContent = `# ${input.title}
|
|
3989
4853
|
|
|
4854
|
+
## MANDATORY: When done
|
|
4855
|
+
|
|
4856
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4857
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4858
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4859
|
+
|
|
3990
4860
|
**ID:** ${id}
|
|
3991
4861
|
**Status:** ${initialStatus}
|
|
3992
4862
|
**Priority:** ${input.priority}
|
|
@@ -4000,12 +4870,6 @@ ${laneWarning}` : laneWarning;
|
|
|
4000
4870
|
## Context
|
|
4001
4871
|
|
|
4002
4872
|
${input.context}
|
|
4003
|
-
|
|
4004
|
-
## MANDATORY: When done
|
|
4005
|
-
|
|
4006
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
4007
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4008
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4009
4873
|
`;
|
|
4010
4874
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4011
4875
|
} catch (err) {
|
|
@@ -4254,7 +5118,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4254
5118
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4255
5119
|
} catch {
|
|
4256
5120
|
}
|
|
4257
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5121
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4258
5122
|
try {
|
|
4259
5123
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4260
5124
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4283,9 +5147,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4283
5147
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4284
5148
|
}
|
|
4285
5149
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4286
|
-
const archPath =
|
|
5150
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4287
5151
|
try {
|
|
4288
|
-
if (
|
|
5152
|
+
if (existsSync14(archPath)) return;
|
|
4289
5153
|
const template = [
|
|
4290
5154
|
`# ${projectName} \u2014 System Architecture`,
|
|
4291
5155
|
"",
|
|
@@ -4318,10 +5182,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4318
5182
|
}
|
|
4319
5183
|
}
|
|
4320
5184
|
async function ensureGitignoreExe(baseDir) {
|
|
4321
|
-
const gitignorePath =
|
|
5185
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
4322
5186
|
try {
|
|
4323
|
-
if (
|
|
4324
|
-
const content =
|
|
5187
|
+
if (existsSync14(gitignorePath)) {
|
|
5188
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
4325
5189
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4326
5190
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4327
5191
|
} else {
|
|
@@ -4364,8 +5228,8 @@ __export(tasks_review_exports, {
|
|
|
4364
5228
|
isStale: () => isStale,
|
|
4365
5229
|
listPendingReviews: () => listPendingReviews
|
|
4366
5230
|
});
|
|
4367
|
-
import
|
|
4368
|
-
import { existsSync as
|
|
5231
|
+
import path15 from "path";
|
|
5232
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
4369
5233
|
function formatAge(isoTimestamp) {
|
|
4370
5234
|
if (!isoTimestamp) return "";
|
|
4371
5235
|
const ms = Date.now() - new Date(isoTimestamp).getTime();
|
|
@@ -4383,54 +5247,38 @@ function isStale(isoTimestamp) {
|
|
|
4383
5247
|
}
|
|
4384
5248
|
async function countPendingReviews(sessionScope) {
|
|
4385
5249
|
const client = getClient();
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
args: [sessionScope]
|
|
4390
|
-
});
|
|
4391
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4392
|
-
}
|
|
5250
|
+
const scope = strictSessionScopeFilter(
|
|
5251
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5252
|
+
);
|
|
4393
5253
|
const result = await client.execute({
|
|
4394
|
-
sql:
|
|
4395
|
-
|
|
5254
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5255
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
5256
|
+
args: [...scope.args]
|
|
4396
5257
|
});
|
|
4397
5258
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4398
5259
|
}
|
|
4399
5260
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4400
5261
|
const client = getClient();
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
4405
|
-
AND session_scope = ?`,
|
|
4406
|
-
args: [sinceIso, sessionScope]
|
|
4407
|
-
});
|
|
4408
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4409
|
-
}
|
|
5262
|
+
const scope = strictSessionScopeFilter(
|
|
5263
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5264
|
+
);
|
|
4410
5265
|
const result = await client.execute({
|
|
4411
5266
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4412
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4413
|
-
args: [sinceIso]
|
|
5267
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
5268
|
+
args: [sinceIso, ...scope.args]
|
|
4414
5269
|
});
|
|
4415
5270
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4416
5271
|
}
|
|
4417
5272
|
async function listPendingReviews(limit, sessionScope) {
|
|
4418
5273
|
const client = getClient();
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
WHERE status = 'needs_review'
|
|
4423
|
-
AND session_scope = ?
|
|
4424
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
4425
|
-
args: [sessionScope, limit]
|
|
4426
|
-
});
|
|
4427
|
-
return result2.rows;
|
|
4428
|
-
}
|
|
5274
|
+
const scope = strictSessionScopeFilter(
|
|
5275
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5276
|
+
);
|
|
4429
5277
|
const result = await client.execute({
|
|
4430
5278
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4431
|
-
WHERE status = 'needs_review'
|
|
5279
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4432
5280
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4433
|
-
args: [limit]
|
|
5281
|
+
args: [...scope.args, limit]
|
|
4434
5282
|
});
|
|
4435
5283
|
return result.rows;
|
|
4436
5284
|
}
|
|
@@ -4442,7 +5290,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4442
5290
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4443
5291
|
AND assigned_by = 'system'
|
|
4444
5292
|
AND title LIKE 'Review:%'
|
|
4445
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
5293
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4446
5294
|
args: [now]
|
|
4447
5295
|
});
|
|
4448
5296
|
const r1b = await client.execute({
|
|
@@ -4650,11 +5498,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4650
5498
|
);
|
|
4651
5499
|
}
|
|
4652
5500
|
try {
|
|
4653
|
-
const cacheDir =
|
|
4654
|
-
if (
|
|
5501
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
5502
|
+
if (existsSync15(cacheDir)) {
|
|
4655
5503
|
for (const f of readdirSync3(cacheDir)) {
|
|
4656
5504
|
if (f.startsWith("review-notified-")) {
|
|
4657
|
-
unlinkSync4(
|
|
5505
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
4658
5506
|
}
|
|
4659
5507
|
}
|
|
4660
5508
|
}
|
|
@@ -4671,11 +5519,12 @@ var init_tasks_review = __esm({
|
|
|
4671
5519
|
init_tmux_routing();
|
|
4672
5520
|
init_session_key();
|
|
4673
5521
|
init_state_bus();
|
|
5522
|
+
init_task_scope();
|
|
4674
5523
|
}
|
|
4675
5524
|
});
|
|
4676
5525
|
|
|
4677
5526
|
// src/lib/tasks-chain.ts
|
|
4678
|
-
import
|
|
5527
|
+
import path16 from "path";
|
|
4679
5528
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
4680
5529
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4681
5530
|
const client = getClient();
|
|
@@ -4692,7 +5541,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4692
5541
|
});
|
|
4693
5542
|
for (const ur of unblockedRows.rows) {
|
|
4694
5543
|
try {
|
|
4695
|
-
const ubFile =
|
|
5544
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
4696
5545
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
4697
5546
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4698
5547
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4727,7 +5576,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4727
5576
|
const scScope = sessionScopeFilter();
|
|
4728
5577
|
const remaining = await client.execute({
|
|
4729
5578
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4730
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
5579
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4731
5580
|
args: [parentTaskId, ...scScope.args]
|
|
4732
5581
|
});
|
|
4733
5582
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4761,7 +5610,7 @@ var init_tasks_chain = __esm({
|
|
|
4761
5610
|
|
|
4762
5611
|
// src/lib/project-name.ts
|
|
4763
5612
|
import { execSync as execSync5 } from "child_process";
|
|
4764
|
-
import
|
|
5613
|
+
import path17 from "path";
|
|
4765
5614
|
function getProjectName(cwd) {
|
|
4766
5615
|
const dir = cwd ?? process.cwd();
|
|
4767
5616
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4774,7 +5623,7 @@ function getProjectName(cwd) {
|
|
|
4774
5623
|
timeout: 2e3,
|
|
4775
5624
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4776
5625
|
}).trim();
|
|
4777
|
-
repoRoot =
|
|
5626
|
+
repoRoot = path17.dirname(gitCommonDir);
|
|
4778
5627
|
} catch {
|
|
4779
5628
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4780
5629
|
cwd: dir,
|
|
@@ -4783,11 +5632,11 @@ function getProjectName(cwd) {
|
|
|
4783
5632
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4784
5633
|
}).trim();
|
|
4785
5634
|
}
|
|
4786
|
-
_cached2 =
|
|
5635
|
+
_cached2 = path17.basename(repoRoot);
|
|
4787
5636
|
_cachedCwd = dir;
|
|
4788
5637
|
return _cached2;
|
|
4789
5638
|
} catch {
|
|
4790
|
-
_cached2 =
|
|
5639
|
+
_cached2 = path17.basename(dir);
|
|
4791
5640
|
_cachedCwd = dir;
|
|
4792
5641
|
return _cached2;
|
|
4793
5642
|
}
|
|
@@ -4930,10 +5779,10 @@ var init_tasks_notify = __esm({
|
|
|
4930
5779
|
});
|
|
4931
5780
|
|
|
4932
5781
|
// src/lib/behaviors.ts
|
|
4933
|
-
import
|
|
5782
|
+
import crypto5 from "crypto";
|
|
4934
5783
|
async function storeBehavior(opts) {
|
|
4935
5784
|
const client = getClient();
|
|
4936
|
-
const id =
|
|
5785
|
+
const id = crypto5.randomUUID();
|
|
4937
5786
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4938
5787
|
await client.execute({
|
|
4939
5788
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4962,7 +5811,7 @@ __export(skill_learning_exports, {
|
|
|
4962
5811
|
storeTrajectory: () => storeTrajectory,
|
|
4963
5812
|
sweepTrajectories: () => sweepTrajectories
|
|
4964
5813
|
});
|
|
4965
|
-
import
|
|
5814
|
+
import crypto6 from "crypto";
|
|
4966
5815
|
async function extractTrajectory(taskId, agentId) {
|
|
4967
5816
|
const client = getClient();
|
|
4968
5817
|
const result = await client.execute({
|
|
@@ -4991,11 +5840,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4991
5840
|
return signature;
|
|
4992
5841
|
}
|
|
4993
5842
|
function hashSignature(signature) {
|
|
4994
|
-
return
|
|
5843
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4995
5844
|
}
|
|
4996
5845
|
async function storeTrajectory(opts) {
|
|
4997
5846
|
const client = getClient();
|
|
4998
|
-
const id =
|
|
5847
|
+
const id = crypto6.randomUUID();
|
|
4999
5848
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5000
5849
|
const signatureHash = hashSignature(opts.signature);
|
|
5001
5850
|
await client.execute({
|
|
@@ -5260,8 +6109,8 @@ __export(tasks_exports, {
|
|
|
5260
6109
|
updateTaskStatus: () => updateTaskStatus,
|
|
5261
6110
|
writeCheckpoint: () => writeCheckpoint
|
|
5262
6111
|
});
|
|
5263
|
-
import
|
|
5264
|
-
import { writeFileSync as
|
|
6112
|
+
import path18 from "path";
|
|
6113
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
5265
6114
|
async function createTask(input) {
|
|
5266
6115
|
const result = await createTaskCore(input);
|
|
5267
6116
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5280,12 +6129,12 @@ async function updateTask(input) {
|
|
|
5280
6129
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5281
6130
|
try {
|
|
5282
6131
|
const agent = String(row.assigned_to);
|
|
5283
|
-
const cacheDir =
|
|
5284
|
-
const cachePath =
|
|
6132
|
+
const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
|
|
6133
|
+
const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
|
|
5285
6134
|
if (input.status === "in_progress") {
|
|
5286
6135
|
mkdirSync6(cacheDir, { recursive: true });
|
|
5287
|
-
|
|
5288
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
6136
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
6137
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5289
6138
|
try {
|
|
5290
6139
|
unlinkSync5(cachePath);
|
|
5291
6140
|
} catch {
|
|
@@ -5293,10 +6142,10 @@ async function updateTask(input) {
|
|
|
5293
6142
|
}
|
|
5294
6143
|
} catch {
|
|
5295
6144
|
}
|
|
5296
|
-
if (input.status === "done") {
|
|
6145
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5297
6146
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5298
6147
|
}
|
|
5299
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
6148
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5300
6149
|
try {
|
|
5301
6150
|
const client = getClient();
|
|
5302
6151
|
const taskTitle = String(row.title);
|
|
@@ -5312,7 +6161,7 @@ async function updateTask(input) {
|
|
|
5312
6161
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5313
6162
|
try {
|
|
5314
6163
|
const draftClient = getClient();
|
|
5315
|
-
if (input.status === "done") {
|
|
6164
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5316
6165
|
await draftClient.execute({
|
|
5317
6166
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5318
6167
|
args: [assignedAgent]
|
|
@@ -5329,7 +6178,7 @@ async function updateTask(input) {
|
|
|
5329
6178
|
try {
|
|
5330
6179
|
const client = getClient();
|
|
5331
6180
|
const cascaded = await client.execute({
|
|
5332
|
-
sql: `UPDATE tasks SET status = '
|
|
6181
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5333
6182
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5334
6183
|
args: [now, taskId]
|
|
5335
6184
|
});
|
|
@@ -5342,14 +6191,14 @@ async function updateTask(input) {
|
|
|
5342
6191
|
} catch {
|
|
5343
6192
|
}
|
|
5344
6193
|
}
|
|
5345
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6194
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5346
6195
|
if (isTerminal) {
|
|
5347
6196
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5348
6197
|
if (!isCoordinator) {
|
|
5349
6198
|
notifyTaskDone();
|
|
5350
6199
|
}
|
|
5351
6200
|
await markTaskNotificationsRead(taskFile);
|
|
5352
|
-
if (input.status === "done") {
|
|
6201
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5353
6202
|
try {
|
|
5354
6203
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5355
6204
|
} catch {
|
|
@@ -5369,7 +6218,7 @@ async function updateTask(input) {
|
|
|
5369
6218
|
}
|
|
5370
6219
|
}
|
|
5371
6220
|
}
|
|
5372
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6221
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5373
6222
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5374
6223
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5375
6224
|
taskId,
|
|
@@ -5741,6 +6590,7 @@ __export(tmux_routing_exports, {
|
|
|
5741
6590
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5742
6591
|
isExeSession: () => isExeSession,
|
|
5743
6592
|
isSessionBusy: () => isSessionBusy,
|
|
6593
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5744
6594
|
notifyParentExe: () => notifyParentExe,
|
|
5745
6595
|
parseParentExe: () => parseParentExe,
|
|
5746
6596
|
registerParentExe: () => registerParentExe,
|
|
@@ -5751,13 +6601,13 @@ __export(tmux_routing_exports, {
|
|
|
5751
6601
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5752
6602
|
});
|
|
5753
6603
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
5754
|
-
import { readFileSync as
|
|
5755
|
-
import
|
|
5756
|
-
import
|
|
6604
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
6605
|
+
import path19 from "path";
|
|
6606
|
+
import os11 from "os";
|
|
5757
6607
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5758
6608
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5759
6609
|
function spawnLockPath(sessionName) {
|
|
5760
|
-
return
|
|
6610
|
+
return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5761
6611
|
}
|
|
5762
6612
|
function isProcessAlive(pid) {
|
|
5763
6613
|
try {
|
|
@@ -5768,13 +6618,13 @@ function isProcessAlive(pid) {
|
|
|
5768
6618
|
}
|
|
5769
6619
|
}
|
|
5770
6620
|
function acquireSpawnLock2(sessionName) {
|
|
5771
|
-
if (!
|
|
6621
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
5772
6622
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
5773
6623
|
}
|
|
5774
6624
|
const lockFile = spawnLockPath(sessionName);
|
|
5775
|
-
if (
|
|
6625
|
+
if (existsSync16(lockFile)) {
|
|
5776
6626
|
try {
|
|
5777
|
-
const lock = JSON.parse(
|
|
6627
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5778
6628
|
const age = Date.now() - lock.timestamp;
|
|
5779
6629
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5780
6630
|
return false;
|
|
@@ -5782,7 +6632,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5782
6632
|
} catch {
|
|
5783
6633
|
}
|
|
5784
6634
|
}
|
|
5785
|
-
|
|
6635
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5786
6636
|
return true;
|
|
5787
6637
|
}
|
|
5788
6638
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5794,13 +6644,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5794
6644
|
function resolveBehaviorsExporterScript() {
|
|
5795
6645
|
try {
|
|
5796
6646
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5797
|
-
const scriptPath =
|
|
5798
|
-
|
|
6647
|
+
const scriptPath = path19.join(
|
|
6648
|
+
path19.dirname(thisFile),
|
|
5799
6649
|
"..",
|
|
5800
6650
|
"bin",
|
|
5801
6651
|
"exe-export-behaviors.js"
|
|
5802
6652
|
);
|
|
5803
|
-
return
|
|
6653
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
5804
6654
|
} catch {
|
|
5805
6655
|
return null;
|
|
5806
6656
|
}
|
|
@@ -5866,12 +6716,12 @@ function extractRootExe(name) {
|
|
|
5866
6716
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5867
6717
|
}
|
|
5868
6718
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5869
|
-
if (!
|
|
6719
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
5870
6720
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5871
6721
|
}
|
|
5872
6722
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5873
|
-
const filePath =
|
|
5874
|
-
|
|
6723
|
+
const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6724
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5875
6725
|
parentExe: rootExe,
|
|
5876
6726
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5877
6727
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5879,7 +6729,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5879
6729
|
}
|
|
5880
6730
|
function getParentExe(sessionKey) {
|
|
5881
6731
|
try {
|
|
5882
|
-
const data = JSON.parse(
|
|
6732
|
+
const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5883
6733
|
return data.parentExe || null;
|
|
5884
6734
|
} catch {
|
|
5885
6735
|
return null;
|
|
@@ -5887,8 +6737,8 @@ function getParentExe(sessionKey) {
|
|
|
5887
6737
|
}
|
|
5888
6738
|
function getDispatchedBy(sessionKey) {
|
|
5889
6739
|
try {
|
|
5890
|
-
const data = JSON.parse(
|
|
5891
|
-
|
|
6740
|
+
const data = JSON.parse(readFileSync12(
|
|
6741
|
+
path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5892
6742
|
"utf8"
|
|
5893
6743
|
));
|
|
5894
6744
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5958,8 +6808,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5958
6808
|
}
|
|
5959
6809
|
function readDebounceState() {
|
|
5960
6810
|
try {
|
|
5961
|
-
if (!
|
|
5962
|
-
const raw = JSON.parse(
|
|
6811
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
6812
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5963
6813
|
const state = {};
|
|
5964
6814
|
for (const [key, val] of Object.entries(raw)) {
|
|
5965
6815
|
if (typeof val === "number") {
|
|
@@ -5975,8 +6825,8 @@ function readDebounceState() {
|
|
|
5975
6825
|
}
|
|
5976
6826
|
function writeDebounceState(state) {
|
|
5977
6827
|
try {
|
|
5978
|
-
if (!
|
|
5979
|
-
|
|
6828
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
6829
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5980
6830
|
} catch {
|
|
5981
6831
|
}
|
|
5982
6832
|
}
|
|
@@ -6074,8 +6924,8 @@ function sendIntercom(targetSession) {
|
|
|
6074
6924
|
try {
|
|
6075
6925
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6076
6926
|
const agent = baseAgentName(rawAgent);
|
|
6077
|
-
const markerPath =
|
|
6078
|
-
if (
|
|
6927
|
+
const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6928
|
+
if (existsSync16(markerPath)) {
|
|
6079
6929
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
6080
6930
|
return "debounced";
|
|
6081
6931
|
}
|
|
@@ -6084,8 +6934,8 @@ function sendIntercom(targetSession) {
|
|
|
6084
6934
|
try {
|
|
6085
6935
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6086
6936
|
const agent = baseAgentName(rawAgent);
|
|
6087
|
-
const taskDir =
|
|
6088
|
-
if (
|
|
6937
|
+
const taskDir = path19.join(process.cwd(), "exe", agent);
|
|
6938
|
+
if (existsSync16(taskDir)) {
|
|
6089
6939
|
const files = readdirSync4(taskDir).filter(
|
|
6090
6940
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
6091
6941
|
);
|
|
@@ -6145,6 +6995,21 @@ function notifyParentExe(sessionKey) {
|
|
|
6145
6995
|
}
|
|
6146
6996
|
return true;
|
|
6147
6997
|
}
|
|
6998
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName2, taskTitle) {
|
|
6999
|
+
const transport = getTransport();
|
|
7000
|
+
try {
|
|
7001
|
+
const sessions = transport.listSessions();
|
|
7002
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
7003
|
+
execSync6(
|
|
7004
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
7005
|
+
{ timeout: 3e3 }
|
|
7006
|
+
);
|
|
7007
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName2} completed "${taskTitle.slice(0, 50)}")`);
|
|
7008
|
+
return true;
|
|
7009
|
+
} catch {
|
|
7010
|
+
return false;
|
|
7011
|
+
}
|
|
7012
|
+
}
|
|
6148
7013
|
function ensureEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
6149
7014
|
if (isCoordinatorName(employeeName)) {
|
|
6150
7015
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6218,26 +7083,26 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6218
7083
|
const transport = getTransport();
|
|
6219
7084
|
const sessionName = employeeSessionName(employeeName, exeSession2, opts?.instance);
|
|
6220
7085
|
const instanceLabel2 = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6221
|
-
const logDir =
|
|
6222
|
-
const logFile =
|
|
6223
|
-
if (!
|
|
7086
|
+
const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
|
|
7087
|
+
const logFile = path19.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
|
|
7088
|
+
if (!existsSync16(logDir)) {
|
|
6224
7089
|
mkdirSync7(logDir, { recursive: true });
|
|
6225
7090
|
}
|
|
6226
7091
|
transport.kill(sessionName);
|
|
6227
7092
|
let cleanupSuffix = "";
|
|
6228
7093
|
try {
|
|
6229
7094
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6230
|
-
const cleanupScript =
|
|
6231
|
-
if (
|
|
7095
|
+
const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
7096
|
+
if (existsSync16(cleanupScript)) {
|
|
6232
7097
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession2}"`;
|
|
6233
7098
|
}
|
|
6234
7099
|
} catch {
|
|
6235
7100
|
}
|
|
6236
7101
|
try {
|
|
6237
|
-
const claudeJsonPath =
|
|
7102
|
+
const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
|
|
6238
7103
|
let claudeJson = {};
|
|
6239
7104
|
try {
|
|
6240
|
-
claudeJson = JSON.parse(
|
|
7105
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6241
7106
|
} catch {
|
|
6242
7107
|
}
|
|
6243
7108
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6245,17 +7110,17 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6245
7110
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6246
7111
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6247
7112
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6248
|
-
|
|
7113
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6249
7114
|
} catch {
|
|
6250
7115
|
}
|
|
6251
7116
|
try {
|
|
6252
|
-
const settingsDir =
|
|
7117
|
+
const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
|
|
6253
7118
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6254
|
-
const projSettingsDir =
|
|
6255
|
-
const settingsPath =
|
|
7119
|
+
const projSettingsDir = path19.join(settingsDir, normalizedKey);
|
|
7120
|
+
const settingsPath = path19.join(projSettingsDir, "settings.json");
|
|
6256
7121
|
let settings = {};
|
|
6257
7122
|
try {
|
|
6258
|
-
settings = JSON.parse(
|
|
7123
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6259
7124
|
} catch {
|
|
6260
7125
|
}
|
|
6261
7126
|
const perms = settings.permissions ?? {};
|
|
@@ -6284,7 +7149,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6284
7149
|
perms.allow = allow;
|
|
6285
7150
|
settings.permissions = perms;
|
|
6286
7151
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
6287
|
-
|
|
7152
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6288
7153
|
}
|
|
6289
7154
|
} catch {
|
|
6290
7155
|
}
|
|
@@ -6299,8 +7164,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6299
7164
|
let behaviorsFlag = "";
|
|
6300
7165
|
let legacyFallbackWarned = false;
|
|
6301
7166
|
if (!useExeAgent && !useBinSymlink) {
|
|
6302
|
-
const identityPath =
|
|
6303
|
-
|
|
7167
|
+
const identityPath = path19.join(
|
|
7168
|
+
os11.homedir(),
|
|
6304
7169
|
".exe-os",
|
|
6305
7170
|
"identity",
|
|
6306
7171
|
`${employeeName}.md`
|
|
@@ -6309,13 +7174,13 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6309
7174
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6310
7175
|
if (hasAgentFlag) {
|
|
6311
7176
|
identityFlag = ` --agent ${employeeName}`;
|
|
6312
|
-
} else if (
|
|
7177
|
+
} else if (existsSync16(identityPath)) {
|
|
6313
7178
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6314
7179
|
legacyFallbackWarned = true;
|
|
6315
7180
|
}
|
|
6316
7181
|
const behaviorsFile = exportBehaviorsSync(
|
|
6317
7182
|
employeeName,
|
|
6318
|
-
|
|
7183
|
+
path19.basename(spawnCwd),
|
|
6319
7184
|
sessionName
|
|
6320
7185
|
);
|
|
6321
7186
|
if (behaviorsFile) {
|
|
@@ -6330,16 +7195,16 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6330
7195
|
}
|
|
6331
7196
|
let sessionContextFlag = "";
|
|
6332
7197
|
try {
|
|
6333
|
-
const ctxDir =
|
|
7198
|
+
const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6334
7199
|
mkdirSync7(ctxDir, { recursive: true });
|
|
6335
|
-
const ctxFile =
|
|
7200
|
+
const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6336
7201
|
const ctxContent = [
|
|
6337
7202
|
`## Session Context`,
|
|
6338
7203
|
`You are running in tmux session: ${sessionName}.`,
|
|
6339
7204
|
`Your parent coordinator session is ${exeSession2}.`,
|
|
6340
7205
|
`Your employees (if any) use the -${exeSession2} suffix.`
|
|
6341
7206
|
].join("\n");
|
|
6342
|
-
|
|
7207
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6343
7208
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6344
7209
|
} catch {
|
|
6345
7210
|
}
|
|
@@ -6416,8 +7281,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
|
|
|
6416
7281
|
transport.pipeLog(sessionName, logFile);
|
|
6417
7282
|
try {
|
|
6418
7283
|
const mySession = getMySession();
|
|
6419
|
-
const dispatchInfo =
|
|
6420
|
-
|
|
7284
|
+
const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
7285
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
6421
7286
|
dispatchedBy: mySession,
|
|
6422
7287
|
rootExe: exeSession2,
|
|
6423
7288
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -6491,15 +7356,15 @@ var init_tmux_routing = __esm({
|
|
|
6491
7356
|
init_intercom_queue();
|
|
6492
7357
|
init_plan_limits();
|
|
6493
7358
|
init_employees();
|
|
6494
|
-
SPAWN_LOCK_DIR =
|
|
6495
|
-
SESSION_CACHE =
|
|
7359
|
+
SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
7360
|
+
SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6496
7361
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6497
7362
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
6498
7363
|
VERIFY_PANE_LINES = 200;
|
|
6499
7364
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
6500
7365
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
6501
|
-
INTERCOM_LOG2 =
|
|
6502
|
-
DEBOUNCE_FILE =
|
|
7366
|
+
INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
7367
|
+
DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6503
7368
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6504
7369
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
6505
7370
|
}
|
|
@@ -6522,6 +7387,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
6522
7387
|
args: [scope]
|
|
6523
7388
|
};
|
|
6524
7389
|
}
|
|
7390
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
7391
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7392
|
+
if (!scope) return { sql: "", args: [] };
|
|
7393
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7394
|
+
return {
|
|
7395
|
+
sql: ` AND ${col} = ?`,
|
|
7396
|
+
args: [scope]
|
|
7397
|
+
};
|
|
7398
|
+
}
|
|
6525
7399
|
var init_task_scope = __esm({
|
|
6526
7400
|
"src/lib/task-scope.ts"() {
|
|
6527
7401
|
"use strict";
|
|
@@ -6566,10 +7440,10 @@ async function disposeEmbedder() {
|
|
|
6566
7440
|
async function embedDirect(text) {
|
|
6567
7441
|
const llamaCpp = await import("node-llama-cpp");
|
|
6568
7442
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6569
|
-
const { existsSync:
|
|
6570
|
-
const
|
|
6571
|
-
const modelPath =
|
|
6572
|
-
if (!
|
|
7443
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
7444
|
+
const path21 = await import("path");
|
|
7445
|
+
const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
7446
|
+
if (!existsSync18(modelPath)) {
|
|
6573
7447
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
6574
7448
|
}
|
|
6575
7449
|
const llama = await llamaCpp.getLlama();
|
|
@@ -6827,8 +7701,8 @@ __export(worktree_exports, {
|
|
|
6827
7701
|
worktreePath: () => worktreePath
|
|
6828
7702
|
});
|
|
6829
7703
|
import { execSync as execSync8 } from "child_process";
|
|
6830
|
-
import { existsSync as
|
|
6831
|
-
import
|
|
7704
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
|
|
7705
|
+
import path20 from "path";
|
|
6832
7706
|
function getGitRoot(dir) {
|
|
6833
7707
|
try {
|
|
6834
7708
|
const root = execSync8("git rev-parse --show-toplevel", {
|
|
@@ -6848,14 +7722,14 @@ function getMainRepoRoot(dir) {
|
|
|
6848
7722
|
"git rev-parse --path-format=absolute --git-common-dir",
|
|
6849
7723
|
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
6850
7724
|
).trim();
|
|
6851
|
-
return realpath(
|
|
7725
|
+
return realpath(path20.dirname(commonDir));
|
|
6852
7726
|
} catch {
|
|
6853
7727
|
return null;
|
|
6854
7728
|
}
|
|
6855
7729
|
}
|
|
6856
7730
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
6857
7731
|
const label = instanceLabel(employeeName, instance);
|
|
6858
|
-
return
|
|
7732
|
+
return path20.join(repoRoot, ".worktrees", label);
|
|
6859
7733
|
}
|
|
6860
7734
|
function worktreeBranch(employeeName, instance) {
|
|
6861
7735
|
return `${instanceLabel(employeeName, instance)}-work`;
|
|
@@ -6868,10 +7742,10 @@ function ensureWorktree(projectDir, employeeName, instance) {
|
|
|
6868
7742
|
if (!repoRoot) return null;
|
|
6869
7743
|
const wtPath = worktreePath(repoRoot, employeeName, instance);
|
|
6870
7744
|
const branch = worktreeBranch(employeeName, instance);
|
|
6871
|
-
if (
|
|
7745
|
+
if (existsSync17(path20.join(wtPath, ".git"))) {
|
|
6872
7746
|
return wtPath;
|
|
6873
7747
|
}
|
|
6874
|
-
const worktreesDir =
|
|
7748
|
+
const worktreesDir = path20.join(repoRoot, ".worktrees");
|
|
6875
7749
|
mkdirSync8(worktreesDir, { recursive: true });
|
|
6876
7750
|
ensureGitignoreEntry(repoRoot, "/.worktrees/");
|
|
6877
7751
|
try {
|
|
@@ -6927,7 +7801,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
|
|
|
6927
7801
|
if (!repoRoot) return { cleaned: false, reason: "not a git repo" };
|
|
6928
7802
|
const wtPath = worktreePath(repoRoot, employeeName, instance);
|
|
6929
7803
|
const branch = worktreeBranch(employeeName, instance);
|
|
6930
|
-
if (!
|
|
7804
|
+
if (!existsSync17(wtPath)) {
|
|
6931
7805
|
return { cleaned: false, reason: "worktree does not exist" };
|
|
6932
7806
|
}
|
|
6933
7807
|
if (isWorktreeDirty(wtPath)) {
|
|
@@ -7005,9 +7879,9 @@ function realpath(p) {
|
|
|
7005
7879
|
}
|
|
7006
7880
|
function ensureGitignoreEntry(repoRoot, entry) {
|
|
7007
7881
|
try {
|
|
7008
|
-
const gitignorePath =
|
|
7009
|
-
if (
|
|
7010
|
-
const content =
|
|
7882
|
+
const gitignorePath = path20.join(repoRoot, ".gitignore");
|
|
7883
|
+
if (existsSync17(gitignorePath)) {
|
|
7884
|
+
const content = readFileSync13(gitignorePath, "utf-8");
|
|
7011
7885
|
if (content.includes(entry)) return;
|
|
7012
7886
|
appendFileSync2(gitignorePath, `
|
|
7013
7887
|
# Agent worktrees (exe-os)
|
|
@@ -7031,7 +7905,7 @@ init_store();
|
|
|
7031
7905
|
init_database();
|
|
7032
7906
|
init_task_scope();
|
|
7033
7907
|
init_project_name();
|
|
7034
|
-
import
|
|
7908
|
+
import crypto7 from "crypto";
|
|
7035
7909
|
import { execSync as execSync9 } from "child_process";
|
|
7036
7910
|
var agentName = process.argv[2];
|
|
7037
7911
|
var exeSession = process.argv[3];
|
|
@@ -7085,7 +7959,7 @@ try {
|
|
|
7085
7959
|
} catch {
|
|
7086
7960
|
}
|
|
7087
7961
|
await writeMemory({
|
|
7088
|
-
id:
|
|
7962
|
+
id: crypto7.randomUUID(),
|
|
7089
7963
|
agent_id: agentName,
|
|
7090
7964
|
agent_role: "employee",
|
|
7091
7965
|
session_id: `cleanup-${Date.now()}`,
|