@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/bin/exe-boot.js
CHANGED
|
@@ -26,6 +26,44 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
26
|
};
|
|
27
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
28
|
|
|
29
|
+
// src/lib/secure-files.ts
|
|
30
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
31
|
+
import { chmod, mkdir } from "fs/promises";
|
|
32
|
+
async function ensurePrivateDir(dirPath) {
|
|
33
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
34
|
+
try {
|
|
35
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function ensurePrivateDirSync(dirPath) {
|
|
40
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
41
|
+
try {
|
|
42
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function enforcePrivateFile(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function enforcePrivateFileSync(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
59
|
+
var init_secure_files = __esm({
|
|
60
|
+
"src/lib/secure-files.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
PRIVATE_DIR_MODE = 448;
|
|
63
|
+
PRIVATE_FILE_MODE = 384;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
29
67
|
// src/lib/config.ts
|
|
30
68
|
var config_exports = {};
|
|
31
69
|
__export(config_exports, {
|
|
@@ -42,8 +80,8 @@ __export(config_exports, {
|
|
|
42
80
|
migrateConfig: () => migrateConfig,
|
|
43
81
|
saveConfig: () => saveConfig
|
|
44
82
|
});
|
|
45
|
-
import { readFile, writeFile
|
|
46
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
83
|
+
import { readFile, writeFile } from "fs/promises";
|
|
84
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
47
85
|
import path from "path";
|
|
48
86
|
import os from "os";
|
|
49
87
|
function resolveDataDir() {
|
|
@@ -51,7 +89,7 @@ function resolveDataDir() {
|
|
|
51
89
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
52
90
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
53
91
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
54
|
-
if (!
|
|
92
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
55
93
|
try {
|
|
56
94
|
renameSync(legacyDir, newDir);
|
|
57
95
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -114,9 +152,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
114
152
|
}
|
|
115
153
|
async function loadConfig() {
|
|
116
154
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
117
|
-
await
|
|
155
|
+
await ensurePrivateDir(dir);
|
|
118
156
|
const configPath = path.join(dir, "config.json");
|
|
119
|
-
if (!
|
|
157
|
+
if (!existsSync2(configPath)) {
|
|
120
158
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
121
159
|
}
|
|
122
160
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -129,6 +167,7 @@ async function loadConfig() {
|
|
|
129
167
|
`);
|
|
130
168
|
try {
|
|
131
169
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
170
|
+
await enforcePrivateFile(configPath);
|
|
132
171
|
} catch {
|
|
133
172
|
}
|
|
134
173
|
}
|
|
@@ -147,7 +186,7 @@ async function loadConfig() {
|
|
|
147
186
|
function loadConfigSync() {
|
|
148
187
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
149
188
|
const configPath = path.join(dir, "config.json");
|
|
150
|
-
if (!
|
|
189
|
+
if (!existsSync2(configPath)) {
|
|
151
190
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
152
191
|
}
|
|
153
192
|
try {
|
|
@@ -165,12 +204,10 @@ function loadConfigSync() {
|
|
|
165
204
|
}
|
|
166
205
|
async function saveConfig(config) {
|
|
167
206
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
168
|
-
await
|
|
207
|
+
await ensurePrivateDir(dir);
|
|
169
208
|
const configPath = path.join(dir, "config.json");
|
|
170
209
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
171
|
-
|
|
172
|
-
await chmod(configPath, 384);
|
|
173
|
-
}
|
|
210
|
+
await enforcePrivateFile(configPath);
|
|
174
211
|
}
|
|
175
212
|
async function loadConfigFrom(configPath) {
|
|
176
213
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -190,6 +227,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
190
227
|
var init_config = __esm({
|
|
191
228
|
"src/lib/config.ts"() {
|
|
192
229
|
"use strict";
|
|
230
|
+
init_secure_files();
|
|
193
231
|
EXE_AI_DIR = resolveDataDir();
|
|
194
232
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
195
233
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -268,7 +306,7 @@ var init_config = __esm({
|
|
|
268
306
|
|
|
269
307
|
// src/lib/employees.ts
|
|
270
308
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
271
|
-
import { existsSync as
|
|
309
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
272
310
|
import { execSync } from "child_process";
|
|
273
311
|
import path2 from "path";
|
|
274
312
|
import os2 from "os";
|
|
@@ -289,7 +327,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
289
327
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
290
328
|
}
|
|
291
329
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
292
|
-
if (!
|
|
330
|
+
if (!existsSync3(employeesPath)) {
|
|
293
331
|
return [];
|
|
294
332
|
}
|
|
295
333
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -304,7 +342,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
304
342
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
305
343
|
}
|
|
306
344
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
307
|
-
if (!
|
|
345
|
+
if (!existsSync3(employeesPath)) return [];
|
|
308
346
|
try {
|
|
309
347
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
310
348
|
} catch {
|
|
@@ -355,7 +393,7 @@ function registerBinSymlinks(name) {
|
|
|
355
393
|
for (const suffix of ["", "-opencode"]) {
|
|
356
394
|
const linkName = `${name}${suffix}`;
|
|
357
395
|
const linkPath = path2.join(binDir, linkName);
|
|
358
|
-
if (
|
|
396
|
+
if (existsSync3(linkPath)) {
|
|
359
397
|
skipped.push(linkName);
|
|
360
398
|
continue;
|
|
361
399
|
}
|
|
@@ -368,7 +406,7 @@ function registerBinSymlinks(name) {
|
|
|
368
406
|
}
|
|
369
407
|
return { created, skipped, errors };
|
|
370
408
|
}
|
|
371
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
409
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
372
410
|
var init_employees = __esm({
|
|
373
411
|
"src/lib/employees.ts"() {
|
|
374
412
|
"use strict";
|
|
@@ -377,6 +415,7 @@ var init_employees = __esm({
|
|
|
377
415
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
378
416
|
COORDINATOR_ROLE = "COO";
|
|
379
417
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
418
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
380
419
|
}
|
|
381
420
|
});
|
|
382
421
|
|
|
@@ -435,13 +474,634 @@ var init_db_retry = __esm({
|
|
|
435
474
|
}
|
|
436
475
|
});
|
|
437
476
|
|
|
477
|
+
// src/lib/database-adapter.ts
|
|
478
|
+
import os3 from "os";
|
|
479
|
+
import path3 from "path";
|
|
480
|
+
import { createRequire } from "module";
|
|
481
|
+
import { pathToFileURL } from "url";
|
|
482
|
+
function quotedIdentifier(identifier) {
|
|
483
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
484
|
+
}
|
|
485
|
+
function unqualifiedTableName(name) {
|
|
486
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
487
|
+
const parts = raw.split(".");
|
|
488
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
489
|
+
}
|
|
490
|
+
function stripTrailingSemicolon(sql) {
|
|
491
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
492
|
+
}
|
|
493
|
+
function appendClause(sql, clause) {
|
|
494
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
495
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
496
|
+
if (!returningMatch) {
|
|
497
|
+
return `${trimmed}${clause}`;
|
|
498
|
+
}
|
|
499
|
+
const idx = returningMatch.index;
|
|
500
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
501
|
+
}
|
|
502
|
+
function normalizeStatement(stmt) {
|
|
503
|
+
if (typeof stmt === "string") {
|
|
504
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
505
|
+
}
|
|
506
|
+
const sql = stmt.sql;
|
|
507
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
508
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
509
|
+
}
|
|
510
|
+
return { kind: "named", sql, args: stmt.args };
|
|
511
|
+
}
|
|
512
|
+
function rewriteBooleanLiterals(sql) {
|
|
513
|
+
let out = sql;
|
|
514
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
515
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
516
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
517
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
518
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
519
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
520
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
521
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
522
|
+
}
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
function rewriteInsertOrIgnore(sql) {
|
|
526
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
527
|
+
return sql;
|
|
528
|
+
}
|
|
529
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
530
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
531
|
+
}
|
|
532
|
+
function rewriteInsertOrReplace(sql) {
|
|
533
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
534
|
+
if (!match) {
|
|
535
|
+
return sql;
|
|
536
|
+
}
|
|
537
|
+
const rawTable = match[1];
|
|
538
|
+
const rawColumns = match[2];
|
|
539
|
+
const remainder = match[3];
|
|
540
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
541
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
542
|
+
if (!conflictKeys?.length) {
|
|
543
|
+
return sql;
|
|
544
|
+
}
|
|
545
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
546
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
547
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
548
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
549
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
550
|
+
}
|
|
551
|
+
function rewriteSql(sql) {
|
|
552
|
+
let out = sql;
|
|
553
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
554
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
555
|
+
out = rewriteBooleanLiterals(out);
|
|
556
|
+
out = rewriteInsertOrReplace(out);
|
|
557
|
+
out = rewriteInsertOrIgnore(out);
|
|
558
|
+
return stripTrailingSemicolon(out);
|
|
559
|
+
}
|
|
560
|
+
function toBoolean(value) {
|
|
561
|
+
if (value === null || value === void 0) return value;
|
|
562
|
+
if (typeof value === "boolean") return value;
|
|
563
|
+
if (typeof value === "number") return value !== 0;
|
|
564
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
565
|
+
if (typeof value === "string") {
|
|
566
|
+
const normalized = value.trim().toLowerCase();
|
|
567
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
568
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
569
|
+
}
|
|
570
|
+
return Boolean(value);
|
|
571
|
+
}
|
|
572
|
+
function countQuestionMarks(sql, end) {
|
|
573
|
+
let count = 0;
|
|
574
|
+
let inSingle = false;
|
|
575
|
+
let inDouble = false;
|
|
576
|
+
let inLineComment = false;
|
|
577
|
+
let inBlockComment = false;
|
|
578
|
+
for (let i = 0; i < end; i++) {
|
|
579
|
+
const ch = sql[i];
|
|
580
|
+
const next = sql[i + 1];
|
|
581
|
+
if (inLineComment) {
|
|
582
|
+
if (ch === "\n") inLineComment = false;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (inBlockComment) {
|
|
586
|
+
if (ch === "*" && next === "/") {
|
|
587
|
+
inBlockComment = false;
|
|
588
|
+
i += 1;
|
|
589
|
+
}
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
593
|
+
inLineComment = true;
|
|
594
|
+
i += 1;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
598
|
+
inBlockComment = true;
|
|
599
|
+
i += 1;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
603
|
+
inSingle = !inSingle;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
607
|
+
inDouble = !inDouble;
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
611
|
+
count += 1;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return count;
|
|
615
|
+
}
|
|
616
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
617
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
618
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
619
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
620
|
+
for (const match of sql.matchAll(pattern)) {
|
|
621
|
+
const matchText = match[0];
|
|
622
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
623
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return indexes;
|
|
627
|
+
}
|
|
628
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
629
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
630
|
+
if (!match) return;
|
|
631
|
+
const rawTable = match[1];
|
|
632
|
+
const rawColumns = match[2];
|
|
633
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
634
|
+
if (!boolColumns?.size) return;
|
|
635
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
636
|
+
for (const [index, column] of columns.entries()) {
|
|
637
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
638
|
+
args[index] = toBoolean(args[index]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
643
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
644
|
+
if (!match) return;
|
|
645
|
+
const rawTable = match[1];
|
|
646
|
+
const setClause = match[2];
|
|
647
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
648
|
+
if (!boolColumns?.size) return;
|
|
649
|
+
const assignments = setClause.split(",");
|
|
650
|
+
let placeholderIndex = 0;
|
|
651
|
+
for (const assignment of assignments) {
|
|
652
|
+
if (!assignment.includes("?")) continue;
|
|
653
|
+
placeholderIndex += 1;
|
|
654
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
655
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
656
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function coerceBooleanArgs(sql, args) {
|
|
661
|
+
const nextArgs = [...args];
|
|
662
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
663
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
664
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
665
|
+
for (const index of placeholderIndexes) {
|
|
666
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
667
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return nextArgs;
|
|
671
|
+
}
|
|
672
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
673
|
+
let out = "";
|
|
674
|
+
let placeholder = 0;
|
|
675
|
+
let inSingle = false;
|
|
676
|
+
let inDouble = false;
|
|
677
|
+
let inLineComment = false;
|
|
678
|
+
let inBlockComment = false;
|
|
679
|
+
for (let i = 0; i < sql.length; i++) {
|
|
680
|
+
const ch = sql[i];
|
|
681
|
+
const next = sql[i + 1];
|
|
682
|
+
if (inLineComment) {
|
|
683
|
+
out += ch;
|
|
684
|
+
if (ch === "\n") inLineComment = false;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (inBlockComment) {
|
|
688
|
+
out += ch;
|
|
689
|
+
if (ch === "*" && next === "/") {
|
|
690
|
+
out += next;
|
|
691
|
+
inBlockComment = false;
|
|
692
|
+
i += 1;
|
|
693
|
+
}
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
697
|
+
out += ch + next;
|
|
698
|
+
inLineComment = true;
|
|
699
|
+
i += 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
703
|
+
out += ch + next;
|
|
704
|
+
inBlockComment = true;
|
|
705
|
+
i += 1;
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
709
|
+
inSingle = !inSingle;
|
|
710
|
+
out += ch;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
714
|
+
inDouble = !inDouble;
|
|
715
|
+
out += ch;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
719
|
+
placeholder += 1;
|
|
720
|
+
out += `$${placeholder}`;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
out += ch;
|
|
724
|
+
}
|
|
725
|
+
return out;
|
|
726
|
+
}
|
|
727
|
+
function translateStatementForPostgres(stmt) {
|
|
728
|
+
const normalized = normalizeStatement(stmt);
|
|
729
|
+
if (normalized.kind === "named") {
|
|
730
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
731
|
+
}
|
|
732
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
733
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
734
|
+
return {
|
|
735
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
736
|
+
args: coercedArgs
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function shouldBypassPostgres(stmt) {
|
|
740
|
+
const normalized = normalizeStatement(stmt);
|
|
741
|
+
if (normalized.kind === "named") {
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
745
|
+
}
|
|
746
|
+
function shouldFallbackOnError(error) {
|
|
747
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
748
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
749
|
+
}
|
|
750
|
+
function isReadQuery(sql) {
|
|
751
|
+
const trimmed = sql.trimStart();
|
|
752
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
753
|
+
}
|
|
754
|
+
function buildRow(row, columns) {
|
|
755
|
+
const values = columns.map((column) => row[column]);
|
|
756
|
+
return Object.assign(values, row);
|
|
757
|
+
}
|
|
758
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
759
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
760
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
761
|
+
return {
|
|
762
|
+
columns,
|
|
763
|
+
columnTypes: columns.map(() => ""),
|
|
764
|
+
rows: resultRows,
|
|
765
|
+
rowsAffected,
|
|
766
|
+
lastInsertRowid: void 0,
|
|
767
|
+
toJSON() {
|
|
768
|
+
return {
|
|
769
|
+
columns,
|
|
770
|
+
columnTypes: columns.map(() => ""),
|
|
771
|
+
rows,
|
|
772
|
+
rowsAffected,
|
|
773
|
+
lastInsertRowid: void 0
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
async function loadPrismaClient() {
|
|
779
|
+
if (!prismaClientPromise) {
|
|
780
|
+
prismaClientPromise = (async () => {
|
|
781
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
782
|
+
if (explicitPath) {
|
|
783
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
784
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
785
|
+
if (!PrismaClient2) {
|
|
786
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
787
|
+
}
|
|
788
|
+
return new PrismaClient2();
|
|
789
|
+
}
|
|
790
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
|
|
791
|
+
const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
|
|
792
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
793
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
794
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
795
|
+
if (!PrismaClient) {
|
|
796
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
797
|
+
}
|
|
798
|
+
return new PrismaClient();
|
|
799
|
+
})();
|
|
800
|
+
}
|
|
801
|
+
return prismaClientPromise;
|
|
802
|
+
}
|
|
803
|
+
async function ensureCompatibilityViews(prisma) {
|
|
804
|
+
if (!compatibilityBootstrapPromise) {
|
|
805
|
+
compatibilityBootstrapPromise = (async () => {
|
|
806
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
807
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
808
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
809
|
+
"SELECT to_regclass($1) AS regclass",
|
|
810
|
+
relation
|
|
811
|
+
);
|
|
812
|
+
if (!rows[0]?.regclass) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
await prisma.$executeRawUnsafe(
|
|
816
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
})();
|
|
820
|
+
}
|
|
821
|
+
return compatibilityBootstrapPromise;
|
|
822
|
+
}
|
|
823
|
+
async function executeOnPrisma(executor, stmt) {
|
|
824
|
+
const translated = translateStatementForPostgres(stmt);
|
|
825
|
+
if (isReadQuery(translated.sql)) {
|
|
826
|
+
const rows = await executor.$queryRawUnsafe(
|
|
827
|
+
translated.sql,
|
|
828
|
+
...translated.args
|
|
829
|
+
);
|
|
830
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
831
|
+
}
|
|
832
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
833
|
+
return buildResultSet([], rowsAffected);
|
|
834
|
+
}
|
|
835
|
+
function splitSqlStatements(sql) {
|
|
836
|
+
const parts = [];
|
|
837
|
+
let current = "";
|
|
838
|
+
let inSingle = false;
|
|
839
|
+
let inDouble = false;
|
|
840
|
+
let inLineComment = false;
|
|
841
|
+
let inBlockComment = false;
|
|
842
|
+
for (let i = 0; i < sql.length; i++) {
|
|
843
|
+
const ch = sql[i];
|
|
844
|
+
const next = sql[i + 1];
|
|
845
|
+
if (inLineComment) {
|
|
846
|
+
current += ch;
|
|
847
|
+
if (ch === "\n") inLineComment = false;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
if (inBlockComment) {
|
|
851
|
+
current += ch;
|
|
852
|
+
if (ch === "*" && next === "/") {
|
|
853
|
+
current += next;
|
|
854
|
+
inBlockComment = false;
|
|
855
|
+
i += 1;
|
|
856
|
+
}
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
860
|
+
current += ch + next;
|
|
861
|
+
inLineComment = true;
|
|
862
|
+
i += 1;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
866
|
+
current += ch + next;
|
|
867
|
+
inBlockComment = true;
|
|
868
|
+
i += 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
872
|
+
inSingle = !inSingle;
|
|
873
|
+
current += ch;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
877
|
+
inDouble = !inDouble;
|
|
878
|
+
current += ch;
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
882
|
+
if (current.trim()) {
|
|
883
|
+
parts.push(current.trim());
|
|
884
|
+
}
|
|
885
|
+
current = "";
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
current += ch;
|
|
889
|
+
}
|
|
890
|
+
if (current.trim()) {
|
|
891
|
+
parts.push(current.trim());
|
|
892
|
+
}
|
|
893
|
+
return parts;
|
|
894
|
+
}
|
|
895
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
896
|
+
const prisma = await loadPrismaClient();
|
|
897
|
+
await ensureCompatibilityViews(prisma);
|
|
898
|
+
let closed = false;
|
|
899
|
+
let adapter;
|
|
900
|
+
const fallbackExecute = async (stmt, error) => {
|
|
901
|
+
if (!fallbackClient) {
|
|
902
|
+
if (error) throw error;
|
|
903
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
904
|
+
}
|
|
905
|
+
if (error) {
|
|
906
|
+
process.stderr.write(
|
|
907
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
908
|
+
`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
return fallbackClient.execute(stmt);
|
|
912
|
+
};
|
|
913
|
+
adapter = {
|
|
914
|
+
async execute(stmt) {
|
|
915
|
+
if (shouldBypassPostgres(stmt)) {
|
|
916
|
+
return fallbackExecute(stmt);
|
|
917
|
+
}
|
|
918
|
+
try {
|
|
919
|
+
return await executeOnPrisma(prisma, stmt);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
if (shouldFallbackOnError(error)) {
|
|
922
|
+
return fallbackExecute(stmt, error);
|
|
923
|
+
}
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
async batch(stmts, mode) {
|
|
928
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
929
|
+
if (!fallbackClient) {
|
|
930
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
931
|
+
}
|
|
932
|
+
return fallbackClient.batch(stmts, mode);
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
if (prisma.$transaction) {
|
|
936
|
+
return await prisma.$transaction(async (tx) => {
|
|
937
|
+
const results2 = [];
|
|
938
|
+
for (const stmt of stmts) {
|
|
939
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
940
|
+
}
|
|
941
|
+
return results2;
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
const results = [];
|
|
945
|
+
for (const stmt of stmts) {
|
|
946
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
947
|
+
}
|
|
948
|
+
return results;
|
|
949
|
+
} catch (error) {
|
|
950
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
951
|
+
process.stderr.write(
|
|
952
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
953
|
+
`
|
|
954
|
+
);
|
|
955
|
+
return fallbackClient.batch(stmts, mode);
|
|
956
|
+
}
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
async migrate(stmts) {
|
|
961
|
+
if (fallbackClient) {
|
|
962
|
+
return fallbackClient.migrate(stmts);
|
|
963
|
+
}
|
|
964
|
+
return adapter.batch(stmts, "deferred");
|
|
965
|
+
},
|
|
966
|
+
async transaction(mode) {
|
|
967
|
+
if (!fallbackClient) {
|
|
968
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
969
|
+
}
|
|
970
|
+
return fallbackClient.transaction(mode);
|
|
971
|
+
},
|
|
972
|
+
async executeMultiple(sql) {
|
|
973
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
974
|
+
return fallbackClient.executeMultiple(sql);
|
|
975
|
+
}
|
|
976
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
977
|
+
await adapter.execute(statement);
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
async sync() {
|
|
981
|
+
if (fallbackClient) {
|
|
982
|
+
return fallbackClient.sync();
|
|
983
|
+
}
|
|
984
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
985
|
+
},
|
|
986
|
+
close() {
|
|
987
|
+
closed = true;
|
|
988
|
+
prismaClientPromise = null;
|
|
989
|
+
compatibilityBootstrapPromise = null;
|
|
990
|
+
void prisma.$disconnect?.();
|
|
991
|
+
},
|
|
992
|
+
get closed() {
|
|
993
|
+
return closed;
|
|
994
|
+
},
|
|
995
|
+
get protocol() {
|
|
996
|
+
return "prisma-postgres";
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
return adapter;
|
|
1000
|
+
}
|
|
1001
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1002
|
+
var init_database_adapter = __esm({
|
|
1003
|
+
"src/lib/database-adapter.ts"() {
|
|
1004
|
+
"use strict";
|
|
1005
|
+
VIEW_MAPPINGS = [
|
|
1006
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1007
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1008
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1009
|
+
{ view: "entities", source: "memory.entities" },
|
|
1010
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1011
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1012
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1013
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1014
|
+
{ view: "messages", source: "memory.messages" },
|
|
1015
|
+
{ view: "users", source: "wiki.users" },
|
|
1016
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1017
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1018
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1019
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1020
|
+
];
|
|
1021
|
+
UPSERT_KEYS = {
|
|
1022
|
+
memories: ["id"],
|
|
1023
|
+
tasks: ["id"],
|
|
1024
|
+
behaviors: ["id"],
|
|
1025
|
+
entities: ["id"],
|
|
1026
|
+
relationships: ["id"],
|
|
1027
|
+
entity_aliases: ["alias"],
|
|
1028
|
+
notifications: ["id"],
|
|
1029
|
+
messages: ["id"],
|
|
1030
|
+
users: ["id"],
|
|
1031
|
+
workspaces: ["id"],
|
|
1032
|
+
workspace_users: ["id"],
|
|
1033
|
+
documents: ["id"],
|
|
1034
|
+
chats: ["id"]
|
|
1035
|
+
};
|
|
1036
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1037
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1038
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1039
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1040
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1041
|
+
};
|
|
1042
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1043
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1044
|
+
);
|
|
1045
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1046
|
+
/\bPRAGMA\b/i,
|
|
1047
|
+
/\bsqlite_master\b/i,
|
|
1048
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1049
|
+
/\bMATCH\b/i,
|
|
1050
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1051
|
+
/\bjson_extract\s*\(/i,
|
|
1052
|
+
/\bjulianday\s*\(/i,
|
|
1053
|
+
/\bstrftime\s*\(/i,
|
|
1054
|
+
/\blast_insert_rowid\s*\(/i
|
|
1055
|
+
];
|
|
1056
|
+
prismaClientPromise = null;
|
|
1057
|
+
compatibilityBootstrapPromise = null;
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// src/lib/daemon-auth.ts
|
|
1062
|
+
import crypto from "crypto";
|
|
1063
|
+
import path4 from "path";
|
|
1064
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1065
|
+
function normalizeToken(token) {
|
|
1066
|
+
if (!token) return null;
|
|
1067
|
+
const trimmed = token.trim();
|
|
1068
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1069
|
+
}
|
|
1070
|
+
function readDaemonToken() {
|
|
1071
|
+
try {
|
|
1072
|
+
if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
|
|
1073
|
+
return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function ensureDaemonToken(seed) {
|
|
1079
|
+
const existing = readDaemonToken();
|
|
1080
|
+
if (existing) return existing;
|
|
1081
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1082
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1083
|
+
writeFileSync2(DAEMON_TOKEN_PATH, `${token}
|
|
1084
|
+
`, "utf8");
|
|
1085
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1086
|
+
return token;
|
|
1087
|
+
}
|
|
1088
|
+
var DAEMON_TOKEN_PATH;
|
|
1089
|
+
var init_daemon_auth = __esm({
|
|
1090
|
+
"src/lib/daemon-auth.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_config();
|
|
1093
|
+
init_secure_files();
|
|
1094
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
438
1098
|
// src/lib/exe-daemon-client.ts
|
|
439
1099
|
import net from "net";
|
|
440
|
-
import
|
|
1100
|
+
import os4 from "os";
|
|
441
1101
|
import { spawn } from "child_process";
|
|
442
1102
|
import { randomUUID } from "crypto";
|
|
443
|
-
import { existsSync as
|
|
444
|
-
import
|
|
1103
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
|
|
1104
|
+
import path5 from "path";
|
|
445
1105
|
import { fileURLToPath } from "url";
|
|
446
1106
|
function handleData(chunk) {
|
|
447
1107
|
_buffer += chunk.toString();
|
|
@@ -469,9 +1129,9 @@ function handleData(chunk) {
|
|
|
469
1129
|
}
|
|
470
1130
|
}
|
|
471
1131
|
function cleanupStaleFiles() {
|
|
472
|
-
if (
|
|
1132
|
+
if (existsSync5(PID_PATH)) {
|
|
473
1133
|
try {
|
|
474
|
-
const pid = parseInt(
|
|
1134
|
+
const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
|
|
475
1135
|
if (pid > 0) {
|
|
476
1136
|
try {
|
|
477
1137
|
process.kill(pid, 0);
|
|
@@ -492,17 +1152,17 @@ function cleanupStaleFiles() {
|
|
|
492
1152
|
}
|
|
493
1153
|
}
|
|
494
1154
|
function findPackageRoot() {
|
|
495
|
-
let dir =
|
|
496
|
-
const { root } =
|
|
1155
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1156
|
+
const { root } = path5.parse(dir);
|
|
497
1157
|
while (dir !== root) {
|
|
498
|
-
if (
|
|
499
|
-
dir =
|
|
1158
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1159
|
+
dir = path5.dirname(dir);
|
|
500
1160
|
}
|
|
501
1161
|
return null;
|
|
502
1162
|
}
|
|
503
1163
|
function spawnDaemon() {
|
|
504
|
-
const freeGB =
|
|
505
|
-
const totalGB =
|
|
1164
|
+
const freeGB = os4.freemem() / (1024 * 1024 * 1024);
|
|
1165
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
506
1166
|
if (totalGB <= 8) {
|
|
507
1167
|
process.stderr.write(
|
|
508
1168
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -522,16 +1182,17 @@ function spawnDaemon() {
|
|
|
522
1182
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
523
1183
|
return;
|
|
524
1184
|
}
|
|
525
|
-
const daemonPath =
|
|
526
|
-
if (!
|
|
1185
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1186
|
+
if (!existsSync5(daemonPath)) {
|
|
527
1187
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
528
1188
|
`);
|
|
529
1189
|
return;
|
|
530
1190
|
}
|
|
531
1191
|
const resolvedPath = daemonPath;
|
|
1192
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
532
1193
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
533
1194
|
`);
|
|
534
|
-
const logPath =
|
|
1195
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
535
1196
|
let stderrFd = "ignore";
|
|
536
1197
|
try {
|
|
537
1198
|
stderrFd = openSync(logPath, "a");
|
|
@@ -549,7 +1210,8 @@ function spawnDaemon() {
|
|
|
549
1210
|
TMUX_PANE: void 0,
|
|
550
1211
|
// Prevents resolveExeSession() from scoping to one session
|
|
551
1212
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
552
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1213
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1214
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
553
1215
|
}
|
|
554
1216
|
});
|
|
555
1217
|
child.unref();
|
|
@@ -656,13 +1318,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
656
1318
|
return;
|
|
657
1319
|
}
|
|
658
1320
|
const id = randomUUID();
|
|
1321
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
659
1322
|
const timer = setTimeout(() => {
|
|
660
1323
|
_pending.delete(id);
|
|
661
1324
|
resolve({ error: "Request timeout" });
|
|
662
1325
|
}, timeoutMs);
|
|
663
1326
|
_pending.set(id, { resolve, timer });
|
|
664
1327
|
try {
|
|
665
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1328
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
666
1329
|
} catch {
|
|
667
1330
|
clearTimeout(timer);
|
|
668
1331
|
_pending.delete(id);
|
|
@@ -673,17 +1336,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
673
1336
|
function isClientConnected() {
|
|
674
1337
|
return _connected;
|
|
675
1338
|
}
|
|
676
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1339
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
677
1340
|
var init_exe_daemon_client = __esm({
|
|
678
1341
|
"src/lib/exe-daemon-client.ts"() {
|
|
679
1342
|
"use strict";
|
|
680
1343
|
init_config();
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1344
|
+
init_daemon_auth();
|
|
1345
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1346
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1347
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
684
1348
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
685
1349
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
686
1350
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1351
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
687
1352
|
_socket = null;
|
|
688
1353
|
_connected = false;
|
|
689
1354
|
_buffer = "";
|
|
@@ -762,7 +1427,7 @@ __export(db_daemon_client_exports, {
|
|
|
762
1427
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
763
1428
|
initDaemonDbClient: () => initDaemonDbClient
|
|
764
1429
|
});
|
|
765
|
-
function
|
|
1430
|
+
function normalizeStatement2(stmt) {
|
|
766
1431
|
if (typeof stmt === "string") {
|
|
767
1432
|
return { sql: stmt, args: [] };
|
|
768
1433
|
}
|
|
@@ -786,7 +1451,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
786
1451
|
if (!_useDaemon || !isClientConnected()) {
|
|
787
1452
|
return fallbackClient.execute(stmt);
|
|
788
1453
|
}
|
|
789
|
-
const { sql, args } =
|
|
1454
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
790
1455
|
const response = await sendDaemonRequest({
|
|
791
1456
|
type: "db-execute",
|
|
792
1457
|
sql,
|
|
@@ -811,7 +1476,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
811
1476
|
if (!_useDaemon || !isClientConnected()) {
|
|
812
1477
|
return fallbackClient.batch(stmts, mode);
|
|
813
1478
|
}
|
|
814
|
-
const statements = stmts.map(
|
|
1479
|
+
const statements = stmts.map(normalizeStatement2);
|
|
815
1480
|
const response = await sendDaemonRequest({
|
|
816
1481
|
type: "db-batch",
|
|
817
1482
|
statements,
|
|
@@ -906,6 +1571,18 @@ __export(database_exports, {
|
|
|
906
1571
|
});
|
|
907
1572
|
import { createClient } from "@libsql/client";
|
|
908
1573
|
async function initDatabase(config) {
|
|
1574
|
+
if (_walCheckpointTimer) {
|
|
1575
|
+
clearInterval(_walCheckpointTimer);
|
|
1576
|
+
_walCheckpointTimer = null;
|
|
1577
|
+
}
|
|
1578
|
+
if (_daemonClient) {
|
|
1579
|
+
_daemonClient.close();
|
|
1580
|
+
_daemonClient = null;
|
|
1581
|
+
}
|
|
1582
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1583
|
+
_adapterClient.close();
|
|
1584
|
+
}
|
|
1585
|
+
_adapterClient = null;
|
|
909
1586
|
if (_client) {
|
|
910
1587
|
_client.close();
|
|
911
1588
|
_client = null;
|
|
@@ -919,6 +1596,7 @@ async function initDatabase(config) {
|
|
|
919
1596
|
}
|
|
920
1597
|
_client = createClient(opts);
|
|
921
1598
|
_resilientClient = wrapWithRetry(_client);
|
|
1599
|
+
_adapterClient = _resilientClient;
|
|
922
1600
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
923
1601
|
});
|
|
924
1602
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -929,14 +1607,20 @@ async function initDatabase(config) {
|
|
|
929
1607
|
});
|
|
930
1608
|
}, 3e4);
|
|
931
1609
|
_walCheckpointTimer.unref();
|
|
1610
|
+
if (process.env.DATABASE_URL) {
|
|
1611
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1612
|
+
}
|
|
932
1613
|
}
|
|
933
1614
|
function isInitialized() {
|
|
934
|
-
return _client !== null;
|
|
1615
|
+
return _adapterClient !== null || _client !== null;
|
|
935
1616
|
}
|
|
936
1617
|
function getClient() {
|
|
937
|
-
if (!
|
|
1618
|
+
if (!_adapterClient) {
|
|
938
1619
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
939
1620
|
}
|
|
1621
|
+
if (process.env.DATABASE_URL) {
|
|
1622
|
+
return _adapterClient;
|
|
1623
|
+
}
|
|
940
1624
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
941
1625
|
return _resilientClient;
|
|
942
1626
|
}
|
|
@@ -946,6 +1630,7 @@ function getClient() {
|
|
|
946
1630
|
return _resilientClient;
|
|
947
1631
|
}
|
|
948
1632
|
async function initDaemonClient() {
|
|
1633
|
+
if (process.env.DATABASE_URL) return;
|
|
949
1634
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
950
1635
|
if (!_resilientClient) return;
|
|
951
1636
|
try {
|
|
@@ -1242,6 +1927,7 @@ async function ensureSchema() {
|
|
|
1242
1927
|
project TEXT NOT NULL,
|
|
1243
1928
|
summary TEXT NOT NULL,
|
|
1244
1929
|
task_file TEXT,
|
|
1930
|
+
session_scope TEXT,
|
|
1245
1931
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1246
1932
|
created_at TEXT NOT NULL
|
|
1247
1933
|
);
|
|
@@ -1250,7 +1936,7 @@ async function ensureSchema() {
|
|
|
1250
1936
|
ON notifications(read);
|
|
1251
1937
|
|
|
1252
1938
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1253
|
-
ON notifications(agent_id);
|
|
1939
|
+
ON notifications(agent_id, session_scope);
|
|
1254
1940
|
|
|
1255
1941
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1256
1942
|
ON notifications(task_file);
|
|
@@ -1288,6 +1974,7 @@ async function ensureSchema() {
|
|
|
1288
1974
|
target_agent TEXT NOT NULL,
|
|
1289
1975
|
target_project TEXT,
|
|
1290
1976
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1977
|
+
session_scope TEXT,
|
|
1291
1978
|
content TEXT NOT NULL,
|
|
1292
1979
|
priority TEXT DEFAULT 'normal',
|
|
1293
1980
|
status TEXT DEFAULT 'pending',
|
|
@@ -1301,10 +1988,31 @@ async function ensureSchema() {
|
|
|
1301
1988
|
);
|
|
1302
1989
|
|
|
1303
1990
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1304
|
-
ON messages(target_agent, status);
|
|
1991
|
+
ON messages(target_agent, session_scope, status);
|
|
1305
1992
|
|
|
1306
1993
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1307
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1994
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1995
|
+
`);
|
|
1996
|
+
try {
|
|
1997
|
+
await client.execute({
|
|
1998
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1999
|
+
args: []
|
|
2000
|
+
});
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
try {
|
|
2004
|
+
await client.execute({
|
|
2005
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2006
|
+
args: []
|
|
2007
|
+
});
|
|
2008
|
+
} catch {
|
|
2009
|
+
}
|
|
2010
|
+
await client.executeMultiple(`
|
|
2011
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2012
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2013
|
+
|
|
2014
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2015
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1308
2016
|
`);
|
|
1309
2017
|
try {
|
|
1310
2018
|
await client.execute({
|
|
@@ -1888,28 +2596,45 @@ async function ensureSchema() {
|
|
|
1888
2596
|
} catch {
|
|
1889
2597
|
}
|
|
1890
2598
|
}
|
|
2599
|
+
try {
|
|
2600
|
+
await client.execute({
|
|
2601
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2602
|
+
args: []
|
|
2603
|
+
});
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
1891
2606
|
}
|
|
1892
2607
|
async function disposeDatabase() {
|
|
2608
|
+
if (_walCheckpointTimer) {
|
|
2609
|
+
clearInterval(_walCheckpointTimer);
|
|
2610
|
+
_walCheckpointTimer = null;
|
|
2611
|
+
}
|
|
1893
2612
|
if (_daemonClient) {
|
|
1894
2613
|
_daemonClient.close();
|
|
1895
2614
|
_daemonClient = null;
|
|
1896
2615
|
}
|
|
2616
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2617
|
+
_adapterClient.close();
|
|
2618
|
+
}
|
|
2619
|
+
_adapterClient = null;
|
|
1897
2620
|
if (_client) {
|
|
1898
2621
|
_client.close();
|
|
1899
2622
|
_client = null;
|
|
1900
2623
|
_resilientClient = null;
|
|
1901
2624
|
}
|
|
1902
2625
|
}
|
|
1903
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2626
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
1904
2627
|
var init_database = __esm({
|
|
1905
2628
|
"src/lib/database.ts"() {
|
|
1906
2629
|
"use strict";
|
|
1907
2630
|
init_db_retry();
|
|
1908
2631
|
init_employees();
|
|
2632
|
+
init_database_adapter();
|
|
1909
2633
|
_client = null;
|
|
1910
2634
|
_resilientClient = null;
|
|
1911
2635
|
_walCheckpointTimer = null;
|
|
1912
2636
|
_daemonClient = null;
|
|
2637
|
+
_adapterClient = null;
|
|
1913
2638
|
initTurso = initDatabase;
|
|
1914
2639
|
disposeTurso = disposeDatabase;
|
|
1915
2640
|
}
|
|
@@ -2119,14 +2844,14 @@ __export(keychain_exports, {
|
|
|
2119
2844
|
setMasterKey: () => setMasterKey
|
|
2120
2845
|
});
|
|
2121
2846
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2122
|
-
import { existsSync as
|
|
2123
|
-
import
|
|
2124
|
-
import
|
|
2847
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2848
|
+
import path6 from "path";
|
|
2849
|
+
import os5 from "os";
|
|
2125
2850
|
function getKeyDir() {
|
|
2126
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2851
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
|
|
2127
2852
|
}
|
|
2128
2853
|
function getKeyPath() {
|
|
2129
|
-
return
|
|
2854
|
+
return path6.join(getKeyDir(), "master.key");
|
|
2130
2855
|
}
|
|
2131
2856
|
async function tryKeytar() {
|
|
2132
2857
|
try {
|
|
@@ -2147,9 +2872,9 @@ async function getMasterKey() {
|
|
|
2147
2872
|
}
|
|
2148
2873
|
}
|
|
2149
2874
|
const keyPath = getKeyPath();
|
|
2150
|
-
if (!
|
|
2875
|
+
if (!existsSync6(keyPath)) {
|
|
2151
2876
|
process.stderr.write(
|
|
2152
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
2877
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2153
2878
|
`
|
|
2154
2879
|
);
|
|
2155
2880
|
return null;
|
|
@@ -2190,7 +2915,7 @@ async function deleteMasterKey() {
|
|
|
2190
2915
|
}
|
|
2191
2916
|
}
|
|
2192
2917
|
const keyPath = getKeyPath();
|
|
2193
|
-
if (
|
|
2918
|
+
if (existsSync6(keyPath)) {
|
|
2194
2919
|
await unlink(keyPath);
|
|
2195
2920
|
}
|
|
2196
2921
|
}
|
|
@@ -2292,6 +3017,7 @@ var shard_manager_exports = {};
|
|
|
2292
3017
|
__export(shard_manager_exports, {
|
|
2293
3018
|
disposeShards: () => disposeShards,
|
|
2294
3019
|
ensureShardSchema: () => ensureShardSchema,
|
|
3020
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
2295
3021
|
getReadyShardClient: () => getReadyShardClient,
|
|
2296
3022
|
getShardClient: () => getShardClient,
|
|
2297
3023
|
getShardsDir: () => getShardsDir,
|
|
@@ -2300,15 +3026,18 @@ __export(shard_manager_exports, {
|
|
|
2300
3026
|
listShards: () => listShards,
|
|
2301
3027
|
shardExists: () => shardExists
|
|
2302
3028
|
});
|
|
2303
|
-
import
|
|
2304
|
-
import { existsSync as
|
|
3029
|
+
import path7 from "path";
|
|
3030
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
2305
3031
|
import { createClient as createClient2 } from "@libsql/client";
|
|
2306
3032
|
function initShardManager(encryptionKey) {
|
|
2307
3033
|
_encryptionKey = encryptionKey;
|
|
2308
|
-
if (!
|
|
2309
|
-
|
|
3034
|
+
if (!existsSync7(SHARDS_DIR)) {
|
|
3035
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
2310
3036
|
}
|
|
2311
3037
|
_shardingEnabled = true;
|
|
3038
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3039
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3040
|
+
_evictionTimer.unref();
|
|
2312
3041
|
}
|
|
2313
3042
|
function isShardingEnabled() {
|
|
2314
3043
|
return _shardingEnabled;
|
|
@@ -2325,21 +3054,28 @@ function getShardClient(projectName) {
|
|
|
2325
3054
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
2326
3055
|
}
|
|
2327
3056
|
const cached = _shards.get(safeName);
|
|
2328
|
-
if (cached)
|
|
2329
|
-
|
|
3057
|
+
if (cached) {
|
|
3058
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3059
|
+
return cached;
|
|
3060
|
+
}
|
|
3061
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3062
|
+
evictLRU();
|
|
3063
|
+
}
|
|
3064
|
+
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
2330
3065
|
const client = createClient2({
|
|
2331
3066
|
url: `file:${dbPath}`,
|
|
2332
3067
|
encryptionKey: _encryptionKey
|
|
2333
3068
|
});
|
|
2334
3069
|
_shards.set(safeName, client);
|
|
3070
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
2335
3071
|
return client;
|
|
2336
3072
|
}
|
|
2337
3073
|
function shardExists(projectName) {
|
|
2338
3074
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2339
|
-
return
|
|
3075
|
+
return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
|
|
2340
3076
|
}
|
|
2341
3077
|
function listShards() {
|
|
2342
|
-
if (!
|
|
3078
|
+
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2343
3079
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2344
3080
|
}
|
|
2345
3081
|
async function ensureShardSchema(client) {
|
|
@@ -2391,6 +3127,8 @@ async function ensureShardSchema(client) {
|
|
|
2391
3127
|
for (const col of [
|
|
2392
3128
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
2393
3129
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3130
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3131
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
2394
3132
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
2395
3133
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
2396
3134
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -2413,7 +3151,23 @@ async function ensureShardSchema(client) {
|
|
|
2413
3151
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2414
3152
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2415
3153
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2416
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
3154
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3155
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3156
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3157
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3158
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3159
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3160
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3161
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3162
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3163
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3164
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3165
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3166
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3167
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3168
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3169
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3170
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2417
3171
|
]) {
|
|
2418
3172
|
try {
|
|
2419
3173
|
await client.execute(col);
|
|
@@ -2512,21 +3266,69 @@ async function getReadyShardClient(projectName) {
|
|
|
2512
3266
|
await ensureShardSchema(client);
|
|
2513
3267
|
return client;
|
|
2514
3268
|
}
|
|
3269
|
+
function evictLRU() {
|
|
3270
|
+
let oldest = null;
|
|
3271
|
+
let oldestTime = Infinity;
|
|
3272
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3273
|
+
if (time < oldestTime) {
|
|
3274
|
+
oldestTime = time;
|
|
3275
|
+
oldest = name;
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
if (oldest) {
|
|
3279
|
+
const client = _shards.get(oldest);
|
|
3280
|
+
if (client) {
|
|
3281
|
+
client.close();
|
|
3282
|
+
}
|
|
3283
|
+
_shards.delete(oldest);
|
|
3284
|
+
_shardLastAccess.delete(oldest);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
function evictIdleShards() {
|
|
3288
|
+
const now = Date.now();
|
|
3289
|
+
const toEvict = [];
|
|
3290
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3291
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3292
|
+
toEvict.push(name);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
for (const name of toEvict) {
|
|
3296
|
+
const client = _shards.get(name);
|
|
3297
|
+
if (client) {
|
|
3298
|
+
client.close();
|
|
3299
|
+
}
|
|
3300
|
+
_shards.delete(name);
|
|
3301
|
+
_shardLastAccess.delete(name);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function getOpenShardCount() {
|
|
3305
|
+
return _shards.size;
|
|
3306
|
+
}
|
|
2515
3307
|
function disposeShards() {
|
|
3308
|
+
if (_evictionTimer) {
|
|
3309
|
+
clearInterval(_evictionTimer);
|
|
3310
|
+
_evictionTimer = null;
|
|
3311
|
+
}
|
|
2516
3312
|
for (const [, client] of _shards) {
|
|
2517
3313
|
client.close();
|
|
2518
3314
|
}
|
|
2519
3315
|
_shards.clear();
|
|
3316
|
+
_shardLastAccess.clear();
|
|
2520
3317
|
_shardingEnabled = false;
|
|
2521
3318
|
_encryptionKey = null;
|
|
2522
3319
|
}
|
|
2523
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
3320
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
2524
3321
|
var init_shard_manager = __esm({
|
|
2525
3322
|
"src/lib/shard-manager.ts"() {
|
|
2526
3323
|
"use strict";
|
|
2527
3324
|
init_config();
|
|
2528
|
-
SHARDS_DIR =
|
|
3325
|
+
SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
|
|
3326
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3327
|
+
MAX_OPEN_SHARDS = 10;
|
|
3328
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
2529
3329
|
_shards = /* @__PURE__ */ new Map();
|
|
3330
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3331
|
+
_evictionTimer = null;
|
|
2530
3332
|
_encryptionKey = null;
|
|
2531
3333
|
_shardingEnabled = false;
|
|
2532
3334
|
}
|
|
@@ -2631,14 +3433,14 @@ __export(session_registry_exports, {
|
|
|
2631
3433
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
2632
3434
|
registerSession: () => registerSession
|
|
2633
3435
|
});
|
|
2634
|
-
import { readFileSync as
|
|
3436
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
|
|
2635
3437
|
import { execSync as execSync2 } from "child_process";
|
|
2636
|
-
import
|
|
2637
|
-
import
|
|
3438
|
+
import path8 from "path";
|
|
3439
|
+
import os6 from "os";
|
|
2638
3440
|
function registerSession(entry) {
|
|
2639
|
-
const dir =
|
|
2640
|
-
if (!
|
|
2641
|
-
|
|
3441
|
+
const dir = path8.dirname(REGISTRY_PATH);
|
|
3442
|
+
if (!existsSync8(dir)) {
|
|
3443
|
+
mkdirSync3(dir, { recursive: true });
|
|
2642
3444
|
}
|
|
2643
3445
|
const sessions = listSessions();
|
|
2644
3446
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -2647,11 +3449,11 @@ function registerSession(entry) {
|
|
|
2647
3449
|
} else {
|
|
2648
3450
|
sessions.push(entry);
|
|
2649
3451
|
}
|
|
2650
|
-
|
|
3452
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
2651
3453
|
}
|
|
2652
3454
|
function listSessions() {
|
|
2653
3455
|
try {
|
|
2654
|
-
const raw =
|
|
3456
|
+
const raw = readFileSync5(REGISTRY_PATH, "utf8");
|
|
2655
3457
|
return JSON.parse(raw);
|
|
2656
3458
|
} catch {
|
|
2657
3459
|
return [];
|
|
@@ -2672,7 +3474,7 @@ function pruneStaleSessions() {
|
|
|
2672
3474
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
2673
3475
|
const pruned = sessions.length - alive.length;
|
|
2674
3476
|
if (pruned > 0) {
|
|
2675
|
-
|
|
3477
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
2676
3478
|
}
|
|
2677
3479
|
return pruned;
|
|
2678
3480
|
}
|
|
@@ -2680,7 +3482,7 @@ var REGISTRY_PATH;
|
|
|
2680
3482
|
var init_session_registry = __esm({
|
|
2681
3483
|
"src/lib/session-registry.ts"() {
|
|
2682
3484
|
"use strict";
|
|
2683
|
-
REGISTRY_PATH =
|
|
3485
|
+
REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
2684
3486
|
}
|
|
2685
3487
|
});
|
|
2686
3488
|
|
|
@@ -2960,12 +3762,12 @@ var init_runtime_table = __esm({
|
|
|
2960
3762
|
});
|
|
2961
3763
|
|
|
2962
3764
|
// src/lib/agent-config.ts
|
|
2963
|
-
import { readFileSync as
|
|
2964
|
-
import
|
|
3765
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
|
|
3766
|
+
import path9 from "path";
|
|
2965
3767
|
function loadAgentConfig() {
|
|
2966
|
-
if (!
|
|
3768
|
+
if (!existsSync9(AGENT_CONFIG_PATH)) return {};
|
|
2967
3769
|
try {
|
|
2968
|
-
return JSON.parse(
|
|
3770
|
+
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
2969
3771
|
} catch {
|
|
2970
3772
|
return {};
|
|
2971
3773
|
}
|
|
@@ -2984,7 +3786,8 @@ var init_agent_config = __esm({
|
|
|
2984
3786
|
"use strict";
|
|
2985
3787
|
init_config();
|
|
2986
3788
|
init_runtime_table();
|
|
2987
|
-
|
|
3789
|
+
init_secure_files();
|
|
3790
|
+
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
2988
3791
|
DEFAULT_MODELS = {
|
|
2989
3792
|
claude: "claude-opus-4",
|
|
2990
3793
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -3002,17 +3805,17 @@ __export(intercom_queue_exports, {
|
|
|
3002
3805
|
queueIntercom: () => queueIntercom,
|
|
3003
3806
|
readQueue: () => readQueue
|
|
3004
3807
|
});
|
|
3005
|
-
import { readFileSync as
|
|
3006
|
-
import
|
|
3007
|
-
import
|
|
3808
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
|
|
3809
|
+
import path10 from "path";
|
|
3810
|
+
import os7 from "os";
|
|
3008
3811
|
function ensureDir() {
|
|
3009
|
-
const dir =
|
|
3010
|
-
if (!
|
|
3812
|
+
const dir = path10.dirname(QUEUE_PATH);
|
|
3813
|
+
if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
|
|
3011
3814
|
}
|
|
3012
3815
|
function readQueue() {
|
|
3013
3816
|
try {
|
|
3014
|
-
if (!
|
|
3015
|
-
return JSON.parse(
|
|
3817
|
+
if (!existsSync10(QUEUE_PATH)) return [];
|
|
3818
|
+
return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
|
|
3016
3819
|
} catch {
|
|
3017
3820
|
return [];
|
|
3018
3821
|
}
|
|
@@ -3020,7 +3823,7 @@ function readQueue() {
|
|
|
3020
3823
|
function writeQueue(queue) {
|
|
3021
3824
|
ensureDir();
|
|
3022
3825
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
3023
|
-
|
|
3826
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
3024
3827
|
renameSync3(tmp, QUEUE_PATH);
|
|
3025
3828
|
}
|
|
3026
3829
|
function queueIntercom(targetSession, reason) {
|
|
@@ -3112,10 +3915,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
|
|
|
3112
3915
|
var init_intercom_queue = __esm({
|
|
3113
3916
|
"src/lib/intercom-queue.ts"() {
|
|
3114
3917
|
"use strict";
|
|
3115
|
-
QUEUE_PATH =
|
|
3918
|
+
QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
|
|
3116
3919
|
MAX_RETRIES2 = 5;
|
|
3117
3920
|
TTL_MS = 60 * 60 * 1e3;
|
|
3118
|
-
INTERCOM_LOG =
|
|
3921
|
+
INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3119
3922
|
}
|
|
3120
3923
|
});
|
|
3121
3924
|
|
|
@@ -3136,9 +3939,12 @@ __export(license_exports, {
|
|
|
3136
3939
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
3137
3940
|
validateLicense: () => validateLicense
|
|
3138
3941
|
});
|
|
3139
|
-
import { readFileSync as
|
|
3942
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
|
|
3140
3943
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3141
|
-
import
|
|
3944
|
+
import { createRequire as createRequire2 } from "module";
|
|
3945
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3946
|
+
import os8 from "os";
|
|
3947
|
+
import path11 from "path";
|
|
3142
3948
|
import { jwtVerify, importSPKI } from "jose";
|
|
3143
3949
|
async function fetchRetry(url, init) {
|
|
3144
3950
|
try {
|
|
@@ -3149,37 +3955,37 @@ async function fetchRetry(url, init) {
|
|
|
3149
3955
|
}
|
|
3150
3956
|
}
|
|
3151
3957
|
function loadDeviceId() {
|
|
3152
|
-
const deviceJsonPath =
|
|
3958
|
+
const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
|
|
3153
3959
|
try {
|
|
3154
|
-
if (
|
|
3155
|
-
const data = JSON.parse(
|
|
3960
|
+
if (existsSync11(deviceJsonPath)) {
|
|
3961
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
3156
3962
|
if (data.deviceId) return data.deviceId;
|
|
3157
3963
|
}
|
|
3158
3964
|
} catch {
|
|
3159
3965
|
}
|
|
3160
3966
|
try {
|
|
3161
|
-
if (
|
|
3162
|
-
const id2 =
|
|
3967
|
+
if (existsSync11(DEVICE_ID_PATH)) {
|
|
3968
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
3163
3969
|
if (id2) return id2;
|
|
3164
3970
|
}
|
|
3165
3971
|
} catch {
|
|
3166
3972
|
}
|
|
3167
3973
|
const id = randomUUID3();
|
|
3168
3974
|
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3169
|
-
|
|
3975
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
3170
3976
|
return id;
|
|
3171
3977
|
}
|
|
3172
3978
|
function loadLicense() {
|
|
3173
3979
|
try {
|
|
3174
|
-
if (!
|
|
3175
|
-
return
|
|
3980
|
+
if (!existsSync11(LICENSE_PATH)) return null;
|
|
3981
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
3176
3982
|
} catch {
|
|
3177
3983
|
return null;
|
|
3178
3984
|
}
|
|
3179
3985
|
}
|
|
3180
3986
|
function saveLicense(apiKey) {
|
|
3181
3987
|
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3182
|
-
|
|
3988
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
3183
3989
|
}
|
|
3184
3990
|
async function verifyLicenseJwt(token) {
|
|
3185
3991
|
try {
|
|
@@ -3205,8 +4011,8 @@ async function verifyLicenseJwt(token) {
|
|
|
3205
4011
|
}
|
|
3206
4012
|
async function getCachedLicense() {
|
|
3207
4013
|
try {
|
|
3208
|
-
if (!
|
|
3209
|
-
const raw = JSON.parse(
|
|
4014
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4015
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
3210
4016
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3211
4017
|
return await verifyLicenseJwt(raw.token);
|
|
3212
4018
|
} catch {
|
|
@@ -3215,8 +4021,8 @@ async function getCachedLicense() {
|
|
|
3215
4021
|
}
|
|
3216
4022
|
function readCachedToken() {
|
|
3217
4023
|
try {
|
|
3218
|
-
if (!
|
|
3219
|
-
const raw = JSON.parse(
|
|
4024
|
+
if (!existsSync11(CACHE_PATH)) return null;
|
|
4025
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
3220
4026
|
return typeof raw.token === "string" ? raw.token : null;
|
|
3221
4027
|
} catch {
|
|
3222
4028
|
return null;
|
|
@@ -3250,57 +4056,131 @@ function getRawCachedPlan() {
|
|
|
3250
4056
|
}
|
|
3251
4057
|
function cacheResponse(token) {
|
|
3252
4058
|
try {
|
|
3253
|
-
|
|
4059
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
3254
4060
|
} catch {
|
|
3255
4061
|
}
|
|
3256
4062
|
}
|
|
3257
|
-
|
|
3258
|
-
|
|
4063
|
+
function loadPrismaForLicense() {
|
|
4064
|
+
if (_prismaFailed) return null;
|
|
4065
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4066
|
+
if (!dbUrl) {
|
|
4067
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4068
|
+
if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
|
|
4069
|
+
_prismaFailed = true;
|
|
4070
|
+
return null;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
if (!_prismaPromise) {
|
|
4074
|
+
_prismaPromise = (async () => {
|
|
4075
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4076
|
+
if (explicitPath) {
|
|
4077
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4078
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4079
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4080
|
+
return new Ctor2();
|
|
4081
|
+
}
|
|
4082
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
|
|
4083
|
+
const req = createRequire2(path11.join(exeDbRoot, "package.json"));
|
|
4084
|
+
const entry = req.resolve("@prisma/client");
|
|
4085
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4086
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4087
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4088
|
+
return new Ctor();
|
|
4089
|
+
})().catch((err) => {
|
|
4090
|
+
_prismaFailed = true;
|
|
4091
|
+
_prismaPromise = null;
|
|
4092
|
+
throw err;
|
|
4093
|
+
});
|
|
4094
|
+
}
|
|
4095
|
+
return _prismaPromise;
|
|
4096
|
+
}
|
|
4097
|
+
async function validateViaPostgres(apiKey) {
|
|
4098
|
+
const loader = loadPrismaForLicense();
|
|
4099
|
+
if (!loader) return null;
|
|
4100
|
+
try {
|
|
4101
|
+
const prisma = await loader;
|
|
4102
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4103
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4104
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4105
|
+
apiKey
|
|
4106
|
+
);
|
|
4107
|
+
if (!rows || rows.length === 0) return null;
|
|
4108
|
+
const row = rows[0];
|
|
4109
|
+
if (row.status !== "active") return null;
|
|
4110
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4111
|
+
const plan = row.plan;
|
|
4112
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4113
|
+
return {
|
|
4114
|
+
valid: true,
|
|
4115
|
+
plan,
|
|
4116
|
+
email: row.email,
|
|
4117
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4118
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4119
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4120
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4121
|
+
};
|
|
4122
|
+
} catch {
|
|
4123
|
+
return null;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
3259
4127
|
try {
|
|
3260
4128
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
3261
4129
|
method: "POST",
|
|
3262
4130
|
headers: { "Content-Type": "application/json" },
|
|
3263
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
4131
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
3264
4132
|
signal: AbortSignal.timeout(1e4)
|
|
3265
4133
|
});
|
|
3266
|
-
if (res.ok)
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
}
|
|
3275
|
-
if (data.token) {
|
|
3276
|
-
cacheResponse(data.token);
|
|
3277
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
3278
|
-
if (verified) return verified;
|
|
3279
|
-
}
|
|
3280
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3281
|
-
return {
|
|
3282
|
-
valid: data.valid,
|
|
3283
|
-
plan: data.plan,
|
|
3284
|
-
email: data.email,
|
|
3285
|
-
expiresAt: data.expiresAt,
|
|
3286
|
-
deviceLimit: limits.devices,
|
|
3287
|
-
employeeLimit: limits.employees,
|
|
3288
|
-
memoryLimit: limits.memories
|
|
3289
|
-
};
|
|
4134
|
+
if (!res.ok) return null;
|
|
4135
|
+
const data = await res.json();
|
|
4136
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4137
|
+
if (!data.valid) return null;
|
|
4138
|
+
if (data.token) {
|
|
4139
|
+
cacheResponse(data.token);
|
|
4140
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4141
|
+
if (verified) return verified;
|
|
3290
4142
|
}
|
|
3291
|
-
const
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
4143
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4144
|
+
return {
|
|
4145
|
+
valid: data.valid,
|
|
4146
|
+
plan: data.plan,
|
|
4147
|
+
email: data.email,
|
|
4148
|
+
expiresAt: data.expiresAt,
|
|
4149
|
+
deviceLimit: limits.devices,
|
|
4150
|
+
employeeLimit: limits.employees,
|
|
4151
|
+
memoryLimit: limits.memories
|
|
4152
|
+
};
|
|
3296
4153
|
} catch {
|
|
3297
|
-
|
|
3298
|
-
if (cached) return cached;
|
|
3299
|
-
const rawFallback = getRawCachedPlan();
|
|
3300
|
-
if (rawFallback) return rawFallback;
|
|
3301
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
4154
|
+
return null;
|
|
3302
4155
|
}
|
|
3303
4156
|
}
|
|
4157
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4158
|
+
const did = deviceId ?? loadDeviceId();
|
|
4159
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4160
|
+
if (pgResult) {
|
|
4161
|
+
try {
|
|
4162
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4163
|
+
} catch {
|
|
4164
|
+
}
|
|
4165
|
+
return pgResult;
|
|
4166
|
+
}
|
|
4167
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4168
|
+
if (cfResult) return cfResult;
|
|
4169
|
+
const cached = await getCachedLicense();
|
|
4170
|
+
if (cached) return cached;
|
|
4171
|
+
try {
|
|
4172
|
+
if (existsSync11(CACHE_PATH)) {
|
|
4173
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4174
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4175
|
+
return raw.pgLicense;
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
} catch {
|
|
4179
|
+
}
|
|
4180
|
+
const rawFallback = getRawCachedPlan();
|
|
4181
|
+
if (rawFallback) return rawFallback;
|
|
4182
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4183
|
+
}
|
|
3304
4184
|
function getCacheAgeMs() {
|
|
3305
4185
|
try {
|
|
3306
4186
|
const { statSync: statSync2 } = __require("fs");
|
|
@@ -3314,9 +4194,9 @@ async function checkLicense() {
|
|
|
3314
4194
|
let key = loadLicense();
|
|
3315
4195
|
if (!key) {
|
|
3316
4196
|
try {
|
|
3317
|
-
const configPath =
|
|
3318
|
-
if (
|
|
3319
|
-
const raw = JSON.parse(
|
|
4197
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
4198
|
+
if (existsSync11(configPath)) {
|
|
4199
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3320
4200
|
const cloud = raw.cloud;
|
|
3321
4201
|
if (cloud?.apiKey) {
|
|
3322
4202
|
key = cloud.apiKey;
|
|
@@ -3470,14 +4350,14 @@ function stopLicenseRevalidation() {
|
|
|
3470
4350
|
_revalTimer = null;
|
|
3471
4351
|
}
|
|
3472
4352
|
}
|
|
3473
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
4353
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
3474
4354
|
var init_license = __esm({
|
|
3475
4355
|
"src/lib/license.ts"() {
|
|
3476
4356
|
"use strict";
|
|
3477
4357
|
init_config();
|
|
3478
|
-
LICENSE_PATH =
|
|
3479
|
-
CACHE_PATH =
|
|
3480
|
-
DEVICE_ID_PATH =
|
|
4358
|
+
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
4359
|
+
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
4360
|
+
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
3481
4361
|
API_BASE = "https://askexe.com/cloud";
|
|
3482
4362
|
RETRY_DELAY_MS = 500;
|
|
3483
4363
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -3501,18 +4381,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
3501
4381
|
employeeLimit: 1,
|
|
3502
4382
|
memoryLimit: 5e3
|
|
3503
4383
|
};
|
|
4384
|
+
_prismaPromise = null;
|
|
4385
|
+
_prismaFailed = false;
|
|
3504
4386
|
CACHE_MAX_AGE_MS = 36e5;
|
|
3505
4387
|
_revalTimer = null;
|
|
3506
4388
|
}
|
|
3507
4389
|
});
|
|
3508
4390
|
|
|
3509
4391
|
// src/lib/plan-limits.ts
|
|
3510
|
-
import { readFileSync as
|
|
3511
|
-
import
|
|
4392
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
|
|
4393
|
+
import path12 from "path";
|
|
3512
4394
|
function getLicenseSync() {
|
|
3513
4395
|
try {
|
|
3514
|
-
if (!
|
|
3515
|
-
const raw = JSON.parse(
|
|
4396
|
+
if (!existsSync12(CACHE_PATH2)) return freeLicense();
|
|
4397
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
3516
4398
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3517
4399
|
const parts = raw.token.split(".");
|
|
3518
4400
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3550,8 +4432,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3550
4432
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
3551
4433
|
let count = 0;
|
|
3552
4434
|
try {
|
|
3553
|
-
if (
|
|
3554
|
-
const raw =
|
|
4435
|
+
if (existsSync12(filePath)) {
|
|
4436
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3555
4437
|
const employees = JSON.parse(raw);
|
|
3556
4438
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3557
4439
|
}
|
|
@@ -3580,29 +4462,30 @@ var init_plan_limits = __esm({
|
|
|
3580
4462
|
this.name = "PlanLimitError";
|
|
3581
4463
|
}
|
|
3582
4464
|
};
|
|
3583
|
-
CACHE_PATH2 =
|
|
4465
|
+
CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
|
|
3584
4466
|
}
|
|
3585
4467
|
});
|
|
3586
4468
|
|
|
3587
4469
|
// src/lib/notifications.ts
|
|
3588
|
-
import
|
|
3589
|
-
import
|
|
3590
|
-
import
|
|
4470
|
+
import crypto2 from "crypto";
|
|
4471
|
+
import path13 from "path";
|
|
4472
|
+
import os9 from "os";
|
|
3591
4473
|
import {
|
|
3592
|
-
readFileSync as
|
|
4474
|
+
readFileSync as readFileSync10,
|
|
3593
4475
|
readdirSync as readdirSync2,
|
|
3594
4476
|
unlinkSync as unlinkSync3,
|
|
3595
|
-
existsSync as
|
|
4477
|
+
existsSync as existsSync13,
|
|
3596
4478
|
rmdirSync
|
|
3597
4479
|
} from "fs";
|
|
3598
4480
|
async function writeNotification(notification) {
|
|
3599
4481
|
try {
|
|
3600
4482
|
const client = getClient();
|
|
3601
|
-
const id =
|
|
4483
|
+
const id = crypto2.randomUUID();
|
|
3602
4484
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4485
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3603
4486
|
await client.execute({
|
|
3604
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3605
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4487
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4488
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3606
4489
|
args: [
|
|
3607
4490
|
id,
|
|
3608
4491
|
notification.agentId,
|
|
@@ -3611,6 +4494,7 @@ async function writeNotification(notification) {
|
|
|
3611
4494
|
notification.project,
|
|
3612
4495
|
notification.summary,
|
|
3613
4496
|
notification.taskFile ?? null,
|
|
4497
|
+
sessionScope,
|
|
3614
4498
|
now
|
|
3615
4499
|
]
|
|
3616
4500
|
});
|
|
@@ -3619,21 +4503,22 @@ async function writeNotification(notification) {
|
|
|
3619
4503
|
`);
|
|
3620
4504
|
}
|
|
3621
4505
|
}
|
|
3622
|
-
async function readUnreadNotifications(agentFilter) {
|
|
4506
|
+
async function readUnreadNotifications(agentFilter, sessionScope) {
|
|
3623
4507
|
try {
|
|
3624
4508
|
const client = getClient();
|
|
3625
4509
|
const conditions = ["read = 0"];
|
|
3626
4510
|
const args = [];
|
|
4511
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3627
4512
|
if (agentFilter) {
|
|
3628
4513
|
conditions.push("agent_id = ?");
|
|
3629
4514
|
args.push(agentFilter);
|
|
3630
4515
|
}
|
|
3631
4516
|
const result = await client.execute({
|
|
3632
|
-
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
|
|
4517
|
+
sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
|
|
3633
4518
|
FROM notifications
|
|
3634
|
-
WHERE ${conditions.join(" AND ")}
|
|
4519
|
+
WHERE ${conditions.join(" AND ")}${scope.sql}
|
|
3635
4520
|
ORDER BY created_at ASC`,
|
|
3636
|
-
args
|
|
4521
|
+
args: [...args, ...scope.args]
|
|
3637
4522
|
});
|
|
3638
4523
|
return result.rows.map((r) => ({
|
|
3639
4524
|
id: String(r.id),
|
|
@@ -3643,6 +4528,7 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
3643
4528
|
project: String(r.project),
|
|
3644
4529
|
summary: String(r.summary),
|
|
3645
4530
|
taskFile: r.task_file ? String(r.task_file) : void 0,
|
|
4531
|
+
sessionScope: r.session_scope == null ? null : String(r.session_scope),
|
|
3646
4532
|
timestamp: String(r.created_at),
|
|
3647
4533
|
read: false
|
|
3648
4534
|
}));
|
|
@@ -3650,54 +4536,60 @@ async function readUnreadNotifications(agentFilter) {
|
|
|
3650
4536
|
return [];
|
|
3651
4537
|
}
|
|
3652
4538
|
}
|
|
3653
|
-
async function markAsRead(ids) {
|
|
4539
|
+
async function markAsRead(ids, sessionScope) {
|
|
3654
4540
|
if (ids.length === 0) return;
|
|
3655
4541
|
try {
|
|
3656
4542
|
const client = getClient();
|
|
3657
4543
|
const placeholders = ids.map(() => "?").join(", ");
|
|
4544
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3658
4545
|
await client.execute({
|
|
3659
|
-
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
|
|
3660
|
-
args: ids
|
|
4546
|
+
sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
|
|
4547
|
+
args: [...ids, ...scope.args]
|
|
3661
4548
|
});
|
|
3662
4549
|
} catch {
|
|
3663
4550
|
}
|
|
3664
4551
|
}
|
|
3665
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
4552
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
3666
4553
|
try {
|
|
3667
4554
|
const client = getClient();
|
|
4555
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3668
4556
|
await client.execute({
|
|
3669
|
-
sql:
|
|
3670
|
-
|
|
4557
|
+
sql: `UPDATE notifications SET read = 1
|
|
4558
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
4559
|
+
args: [taskFile, ...scope.args]
|
|
3671
4560
|
});
|
|
3672
4561
|
} catch {
|
|
3673
4562
|
}
|
|
3674
4563
|
}
|
|
3675
|
-
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
|
|
4564
|
+
async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
|
|
3676
4565
|
try {
|
|
3677
4566
|
const client = getClient();
|
|
3678
4567
|
const cutoff = new Date(
|
|
3679
4568
|
Date.now() - daysOld * 24 * 60 * 60 * 1e3
|
|
3680
4569
|
).toISOString();
|
|
4570
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3681
4571
|
const result = await client.execute({
|
|
3682
|
-
sql:
|
|
3683
|
-
args: [cutoff]
|
|
4572
|
+
sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
|
|
4573
|
+
args: [cutoff, ...scope.args]
|
|
3684
4574
|
});
|
|
3685
4575
|
return result.rowsAffected;
|
|
3686
4576
|
} catch {
|
|
3687
4577
|
return 0;
|
|
3688
4578
|
}
|
|
3689
4579
|
}
|
|
3690
|
-
async function markDoneTaskNotificationsAsRead() {
|
|
4580
|
+
async function markDoneTaskNotificationsAsRead(sessionScope) {
|
|
3691
4581
|
try {
|
|
3692
4582
|
const client = getClient();
|
|
4583
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
3693
4584
|
const result = await client.execute({
|
|
3694
4585
|
sql: `UPDATE notifications SET read = 1
|
|
3695
4586
|
WHERE read = 0
|
|
3696
4587
|
AND task_file IS NOT NULL
|
|
4588
|
+
${scope.sql}
|
|
3697
4589
|
AND task_file IN (
|
|
3698
|
-
SELECT task_file FROM tasks WHERE status = 'done'
|
|
4590
|
+
SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
|
|
3699
4591
|
)`,
|
|
3700
|
-
args: []
|
|
4592
|
+
args: [...scope.args, ...scope.args]
|
|
3701
4593
|
});
|
|
3702
4594
|
return result.rowsAffected;
|
|
3703
4595
|
} catch {
|
|
@@ -3705,9 +4597,9 @@ async function markDoneTaskNotificationsAsRead() {
|
|
|
3705
4597
|
}
|
|
3706
4598
|
}
|
|
3707
4599
|
async function migrateJsonNotifications() {
|
|
3708
|
-
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR ||
|
|
3709
|
-
const notifDir =
|
|
3710
|
-
if (!
|
|
4600
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
|
|
4601
|
+
const notifDir = path13.join(base, "notifications");
|
|
4602
|
+
if (!existsSync13(notifDir)) return 0;
|
|
3711
4603
|
let migrated = 0;
|
|
3712
4604
|
try {
|
|
3713
4605
|
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
@@ -3715,19 +4607,20 @@ async function migrateJsonNotifications() {
|
|
|
3715
4607
|
const client = getClient();
|
|
3716
4608
|
for (const file of files) {
|
|
3717
4609
|
try {
|
|
3718
|
-
const filePath =
|
|
3719
|
-
const data = JSON.parse(
|
|
4610
|
+
const filePath = path13.join(notifDir, file);
|
|
4611
|
+
const data = JSON.parse(readFileSync10(filePath, "utf8"));
|
|
3720
4612
|
await client.execute({
|
|
3721
|
-
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
3722
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4613
|
+
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
4614
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3723
4615
|
args: [
|
|
3724
|
-
|
|
4616
|
+
crypto2.randomUUID(),
|
|
3725
4617
|
data.agentId ?? "unknown",
|
|
3726
4618
|
data.agentRole ?? "unknown",
|
|
3727
4619
|
data.event ?? "session_summary",
|
|
3728
4620
|
data.project ?? "unknown",
|
|
3729
4621
|
data.summary ?? "",
|
|
3730
4622
|
data.taskFile ?? null,
|
|
4623
|
+
null,
|
|
3731
4624
|
data.read ? 1 : 0,
|
|
3732
4625
|
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3733
4626
|
]
|
|
@@ -3753,6 +4646,7 @@ var init_notifications = __esm({
|
|
|
3753
4646
|
"src/lib/notifications.ts"() {
|
|
3754
4647
|
"use strict";
|
|
3755
4648
|
init_database();
|
|
4649
|
+
init_task_scope();
|
|
3756
4650
|
CLEANUP_DAYS = 7;
|
|
3757
4651
|
}
|
|
3758
4652
|
});
|
|
@@ -3770,7 +4664,7 @@ __export(session_kill_telemetry_exports, {
|
|
|
3770
4664
|
recordSessionKill: () => recordSessionKill,
|
|
3771
4665
|
sumTokensSavedSince: () => sumTokensSavedSince
|
|
3772
4666
|
});
|
|
3773
|
-
import
|
|
4667
|
+
import crypto3 from "crypto";
|
|
3774
4668
|
async function recordSessionKill(input) {
|
|
3775
4669
|
try {
|
|
3776
4670
|
const client = getClient();
|
|
@@ -3780,7 +4674,7 @@ async function recordSessionKill(input) {
|
|
|
3780
4674
|
ticks_idle, estimated_tokens_saved)
|
|
3781
4675
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3782
4676
|
args: [
|
|
3783
|
-
|
|
4677
|
+
crypto3.randomUUID(),
|
|
3784
4678
|
input.sessionName,
|
|
3785
4679
|
input.agentId,
|
|
3786
4680
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3862,12 +4756,12 @@ var init_session_kill_telemetry = __esm({
|
|
|
3862
4756
|
});
|
|
3863
4757
|
|
|
3864
4758
|
// src/lib/tasks-crud.ts
|
|
3865
|
-
import
|
|
3866
|
-
import
|
|
3867
|
-
import
|
|
4759
|
+
import crypto4 from "crypto";
|
|
4760
|
+
import path14 from "path";
|
|
4761
|
+
import os10 from "os";
|
|
3868
4762
|
import { execSync as execSync5 } from "child_process";
|
|
3869
4763
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3870
|
-
import { existsSync as
|
|
4764
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3871
4765
|
async function writeCheckpoint(input) {
|
|
3872
4766
|
const client = getClient();
|
|
3873
4767
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3983,7 +4877,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3983
4877
|
}
|
|
3984
4878
|
async function createTaskCore(input) {
|
|
3985
4879
|
const client = getClient();
|
|
3986
|
-
const id =
|
|
4880
|
+
const id = crypto4.randomUUID();
|
|
3987
4881
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3988
4882
|
const slug = slugify(input.title);
|
|
3989
4883
|
let earlySessionScope = null;
|
|
@@ -4042,8 +4936,8 @@ ${laneWarning}` : laneWarning;
|
|
|
4042
4936
|
}
|
|
4043
4937
|
if (input.baseDir) {
|
|
4044
4938
|
try {
|
|
4045
|
-
await mkdir4(
|
|
4046
|
-
await mkdir4(
|
|
4939
|
+
await mkdir4(path14.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
4940
|
+
await mkdir4(path14.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
4047
4941
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
4048
4942
|
await ensureGitignoreExe(input.baseDir);
|
|
4049
4943
|
} catch {
|
|
@@ -4079,13 +4973,19 @@ ${laneWarning}` : laneWarning;
|
|
|
4079
4973
|
});
|
|
4080
4974
|
if (input.baseDir) {
|
|
4081
4975
|
try {
|
|
4082
|
-
const EXE_OS_DIR =
|
|
4083
|
-
const mdPath =
|
|
4084
|
-
const mdDir =
|
|
4085
|
-
if (!
|
|
4976
|
+
const EXE_OS_DIR = path14.join(os10.homedir(), ".exe-os");
|
|
4977
|
+
const mdPath = path14.join(EXE_OS_DIR, taskFile);
|
|
4978
|
+
const mdDir = path14.dirname(mdPath);
|
|
4979
|
+
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4086
4980
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
4087
4981
|
const mdContent = `# ${input.title}
|
|
4088
4982
|
|
|
4983
|
+
## MANDATORY: When done
|
|
4984
|
+
|
|
4985
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
4986
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4987
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4988
|
+
|
|
4089
4989
|
**ID:** ${id}
|
|
4090
4990
|
**Status:** ${initialStatus}
|
|
4091
4991
|
**Priority:** ${input.priority}
|
|
@@ -4099,12 +4999,6 @@ ${laneWarning}` : laneWarning;
|
|
|
4099
4999
|
## Context
|
|
4100
5000
|
|
|
4101
5001
|
${input.context}
|
|
4102
|
-
|
|
4103
|
-
## MANDATORY: When done
|
|
4104
|
-
|
|
4105
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
4106
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
4107
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
4108
5002
|
`;
|
|
4109
5003
|
await writeFile4(mdPath, mdContent, "utf-8");
|
|
4110
5004
|
} catch (err) {
|
|
@@ -4353,7 +5247,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4353
5247
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
4354
5248
|
} catch {
|
|
4355
5249
|
}
|
|
4356
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
5250
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
4357
5251
|
try {
|
|
4358
5252
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4359
5253
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -4382,9 +5276,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
4382
5276
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4383
5277
|
}
|
|
4384
5278
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
4385
|
-
const archPath =
|
|
5279
|
+
const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
4386
5280
|
try {
|
|
4387
|
-
if (
|
|
5281
|
+
if (existsSync14(archPath)) return;
|
|
4388
5282
|
const template = [
|
|
4389
5283
|
`# ${projectName} \u2014 System Architecture`,
|
|
4390
5284
|
"",
|
|
@@ -4417,10 +5311,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
4417
5311
|
}
|
|
4418
5312
|
}
|
|
4419
5313
|
async function ensureGitignoreExe(baseDir) {
|
|
4420
|
-
const gitignorePath =
|
|
5314
|
+
const gitignorePath = path14.join(baseDir, ".gitignore");
|
|
4421
5315
|
try {
|
|
4422
|
-
if (
|
|
4423
|
-
const content =
|
|
5316
|
+
if (existsSync14(gitignorePath)) {
|
|
5317
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
4424
5318
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
4425
5319
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
4426
5320
|
} else {
|
|
@@ -4451,58 +5345,42 @@ var init_tasks_crud = __esm({
|
|
|
4451
5345
|
});
|
|
4452
5346
|
|
|
4453
5347
|
// src/lib/tasks-review.ts
|
|
4454
|
-
import
|
|
4455
|
-
import { existsSync as
|
|
5348
|
+
import path15 from "path";
|
|
5349
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
4456
5350
|
async function countPendingReviews(sessionScope) {
|
|
4457
5351
|
const client = getClient();
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
args: [sessionScope]
|
|
4462
|
-
});
|
|
4463
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4464
|
-
}
|
|
5352
|
+
const scope = strictSessionScopeFilter(
|
|
5353
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5354
|
+
);
|
|
4465
5355
|
const result = await client.execute({
|
|
4466
|
-
sql:
|
|
4467
|
-
|
|
5356
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
5357
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
5358
|
+
args: [...scope.args]
|
|
4468
5359
|
});
|
|
4469
5360
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4470
5361
|
}
|
|
4471
5362
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
4472
5363
|
const client = getClient();
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
4477
|
-
AND session_scope = ?`,
|
|
4478
|
-
args: [sinceIso, sessionScope]
|
|
4479
|
-
});
|
|
4480
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
4481
|
-
}
|
|
5364
|
+
const scope = strictSessionScopeFilter(
|
|
5365
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5366
|
+
);
|
|
4482
5367
|
const result = await client.execute({
|
|
4483
5368
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4484
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
4485
|
-
args: [sinceIso]
|
|
5369
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
5370
|
+
args: [sinceIso, ...scope.args]
|
|
4486
5371
|
});
|
|
4487
5372
|
return Number(result.rows[0]?.cnt) || 0;
|
|
4488
5373
|
}
|
|
4489
5374
|
async function listPendingReviews(limit, sessionScope) {
|
|
4490
5375
|
const client = getClient();
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
WHERE status = 'needs_review'
|
|
4495
|
-
AND session_scope = ?
|
|
4496
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
4497
|
-
args: [sessionScope, limit]
|
|
4498
|
-
});
|
|
4499
|
-
return result2.rows;
|
|
4500
|
-
}
|
|
5376
|
+
const scope = strictSessionScopeFilter(
|
|
5377
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
5378
|
+
);
|
|
4501
5379
|
const result = await client.execute({
|
|
4502
5380
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
4503
|
-
WHERE status = 'needs_review'
|
|
5381
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
4504
5382
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
4505
|
-
args: [limit]
|
|
5383
|
+
args: [...scope.args, limit]
|
|
4506
5384
|
});
|
|
4507
5385
|
return result.rows;
|
|
4508
5386
|
}
|
|
@@ -4514,7 +5392,7 @@ async function cleanupOrphanedReviews() {
|
|
|
4514
5392
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
4515
5393
|
AND assigned_by = 'system'
|
|
4516
5394
|
AND title LIKE 'Review:%'
|
|
4517
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
5395
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
4518
5396
|
args: [now]
|
|
4519
5397
|
});
|
|
4520
5398
|
const r1b = await client.execute({
|
|
@@ -4633,11 +5511,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4633
5511
|
);
|
|
4634
5512
|
}
|
|
4635
5513
|
try {
|
|
4636
|
-
const cacheDir =
|
|
4637
|
-
if (
|
|
5514
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
5515
|
+
if (existsSync15(cacheDir)) {
|
|
4638
5516
|
for (const f of readdirSync3(cacheDir)) {
|
|
4639
5517
|
if (f.startsWith("review-notified-")) {
|
|
4640
|
-
unlinkSync4(
|
|
5518
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
4641
5519
|
}
|
|
4642
5520
|
}
|
|
4643
5521
|
}
|
|
@@ -4654,11 +5532,12 @@ var init_tasks_review = __esm({
|
|
|
4654
5532
|
init_tmux_routing();
|
|
4655
5533
|
init_session_key();
|
|
4656
5534
|
init_state_bus();
|
|
5535
|
+
init_task_scope();
|
|
4657
5536
|
}
|
|
4658
5537
|
});
|
|
4659
5538
|
|
|
4660
5539
|
// src/lib/tasks-chain.ts
|
|
4661
|
-
import
|
|
5540
|
+
import path16 from "path";
|
|
4662
5541
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
4663
5542
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
4664
5543
|
const client = getClient();
|
|
@@ -4675,7 +5554,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
4675
5554
|
});
|
|
4676
5555
|
for (const ur of unblockedRows.rows) {
|
|
4677
5556
|
try {
|
|
4678
|
-
const ubFile =
|
|
5557
|
+
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
4679
5558
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
4680
5559
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
4681
5560
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -4710,7 +5589,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
4710
5589
|
const scScope = sessionScopeFilter();
|
|
4711
5590
|
const remaining = await client.execute({
|
|
4712
5591
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
4713
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
5592
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
4714
5593
|
args: [parentTaskId, ...scScope.args]
|
|
4715
5594
|
});
|
|
4716
5595
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -4749,7 +5628,7 @@ __export(project_name_exports, {
|
|
|
4749
5628
|
getProjectName: () => getProjectName
|
|
4750
5629
|
});
|
|
4751
5630
|
import { execSync as execSync6 } from "child_process";
|
|
4752
|
-
import
|
|
5631
|
+
import path17 from "path";
|
|
4753
5632
|
function getProjectName(cwd) {
|
|
4754
5633
|
const dir = cwd ?? process.cwd();
|
|
4755
5634
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -4762,7 +5641,7 @@ function getProjectName(cwd) {
|
|
|
4762
5641
|
timeout: 2e3,
|
|
4763
5642
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4764
5643
|
}).trim();
|
|
4765
|
-
repoRoot =
|
|
5644
|
+
repoRoot = path17.dirname(gitCommonDir);
|
|
4766
5645
|
} catch {
|
|
4767
5646
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
4768
5647
|
cwd: dir,
|
|
@@ -4771,11 +5650,11 @@ function getProjectName(cwd) {
|
|
|
4771
5650
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4772
5651
|
}).trim();
|
|
4773
5652
|
}
|
|
4774
|
-
_cached2 =
|
|
5653
|
+
_cached2 = path17.basename(repoRoot);
|
|
4775
5654
|
_cachedCwd = dir;
|
|
4776
5655
|
return _cached2;
|
|
4777
5656
|
} catch {
|
|
4778
|
-
_cached2 =
|
|
5657
|
+
_cached2 = path17.basename(dir);
|
|
4779
5658
|
_cachedCwd = dir;
|
|
4780
5659
|
return _cached2;
|
|
4781
5660
|
}
|
|
@@ -4922,10 +5801,10 @@ var init_tasks_notify = __esm({
|
|
|
4922
5801
|
});
|
|
4923
5802
|
|
|
4924
5803
|
// src/lib/behaviors.ts
|
|
4925
|
-
import
|
|
5804
|
+
import crypto5 from "crypto";
|
|
4926
5805
|
async function storeBehavior(opts) {
|
|
4927
5806
|
const client = getClient();
|
|
4928
|
-
const id =
|
|
5807
|
+
const id = crypto5.randomUUID();
|
|
4929
5808
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4930
5809
|
await client.execute({
|
|
4931
5810
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -4954,7 +5833,7 @@ __export(skill_learning_exports, {
|
|
|
4954
5833
|
storeTrajectory: () => storeTrajectory,
|
|
4955
5834
|
sweepTrajectories: () => sweepTrajectories
|
|
4956
5835
|
});
|
|
4957
|
-
import
|
|
5836
|
+
import crypto6 from "crypto";
|
|
4958
5837
|
async function extractTrajectory(taskId, agentId) {
|
|
4959
5838
|
const client = getClient();
|
|
4960
5839
|
const result = await client.execute({
|
|
@@ -4983,11 +5862,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
4983
5862
|
return signature;
|
|
4984
5863
|
}
|
|
4985
5864
|
function hashSignature(signature) {
|
|
4986
|
-
return
|
|
5865
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
4987
5866
|
}
|
|
4988
5867
|
async function storeTrajectory(opts) {
|
|
4989
5868
|
const client = getClient();
|
|
4990
|
-
const id =
|
|
5869
|
+
const id = crypto6.randomUUID();
|
|
4991
5870
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4992
5871
|
const signatureHash = hashSignature(opts.signature);
|
|
4993
5872
|
await client.execute({
|
|
@@ -5252,8 +6131,8 @@ __export(tasks_exports, {
|
|
|
5252
6131
|
updateTaskStatus: () => updateTaskStatus,
|
|
5253
6132
|
writeCheckpoint: () => writeCheckpoint
|
|
5254
6133
|
});
|
|
5255
|
-
import
|
|
5256
|
-
import { writeFileSync as
|
|
6134
|
+
import path18 from "path";
|
|
6135
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
5257
6136
|
async function createTask(input) {
|
|
5258
6137
|
const result = await createTaskCore(input);
|
|
5259
6138
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5272,12 +6151,12 @@ async function updateTask(input) {
|
|
|
5272
6151
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
5273
6152
|
try {
|
|
5274
6153
|
const agent = String(row.assigned_to);
|
|
5275
|
-
const cacheDir =
|
|
5276
|
-
const cachePath =
|
|
6154
|
+
const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
|
|
6155
|
+
const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
|
|
5277
6156
|
if (input.status === "in_progress") {
|
|
5278
6157
|
mkdirSync6(cacheDir, { recursive: true });
|
|
5279
|
-
|
|
5280
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
6158
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
6159
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
5281
6160
|
try {
|
|
5282
6161
|
unlinkSync5(cachePath);
|
|
5283
6162
|
} catch {
|
|
@@ -5285,10 +6164,10 @@ async function updateTask(input) {
|
|
|
5285
6164
|
}
|
|
5286
6165
|
} catch {
|
|
5287
6166
|
}
|
|
5288
|
-
if (input.status === "done") {
|
|
6167
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5289
6168
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
5290
6169
|
}
|
|
5291
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
6170
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
5292
6171
|
try {
|
|
5293
6172
|
const client = getClient();
|
|
5294
6173
|
const taskTitle = String(row.title);
|
|
@@ -5304,7 +6183,7 @@ async function updateTask(input) {
|
|
|
5304
6183
|
if (!isCoordinatorName(assignedAgent)) {
|
|
5305
6184
|
try {
|
|
5306
6185
|
const draftClient = getClient();
|
|
5307
|
-
if (input.status === "done") {
|
|
6186
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5308
6187
|
await draftClient.execute({
|
|
5309
6188
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
5310
6189
|
args: [assignedAgent]
|
|
@@ -5321,7 +6200,7 @@ async function updateTask(input) {
|
|
|
5321
6200
|
try {
|
|
5322
6201
|
const client = getClient();
|
|
5323
6202
|
const cascaded = await client.execute({
|
|
5324
|
-
sql: `UPDATE tasks SET status = '
|
|
6203
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
5325
6204
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
5326
6205
|
args: [now, taskId]
|
|
5327
6206
|
});
|
|
@@ -5334,14 +6213,14 @@ async function updateTask(input) {
|
|
|
5334
6213
|
} catch {
|
|
5335
6214
|
}
|
|
5336
6215
|
}
|
|
5337
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6216
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
5338
6217
|
if (isTerminal) {
|
|
5339
6218
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
5340
6219
|
if (!isCoordinator) {
|
|
5341
6220
|
notifyTaskDone();
|
|
5342
6221
|
}
|
|
5343
6222
|
await markTaskNotificationsRead(taskFile);
|
|
5344
|
-
if (input.status === "done") {
|
|
6223
|
+
if (input.status === "done" || input.status === "closed") {
|
|
5345
6224
|
try {
|
|
5346
6225
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5347
6226
|
} catch {
|
|
@@ -5361,7 +6240,7 @@ async function updateTask(input) {
|
|
|
5361
6240
|
}
|
|
5362
6241
|
}
|
|
5363
6242
|
}
|
|
5364
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6243
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5365
6244
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5366
6245
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
5367
6246
|
taskId,
|
|
@@ -5733,6 +6612,7 @@ __export(tmux_routing_exports, {
|
|
|
5733
6612
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
5734
6613
|
isExeSession: () => isExeSession,
|
|
5735
6614
|
isSessionBusy: () => isSessionBusy,
|
|
6615
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
5736
6616
|
notifyParentExe: () => notifyParentExe,
|
|
5737
6617
|
parseParentExe: () => parseParentExe,
|
|
5738
6618
|
registerParentExe: () => registerParentExe,
|
|
@@ -5743,13 +6623,13 @@ __export(tmux_routing_exports, {
|
|
|
5743
6623
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
5744
6624
|
});
|
|
5745
6625
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
5746
|
-
import { readFileSync as
|
|
5747
|
-
import
|
|
5748
|
-
import
|
|
6626
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
|
|
6627
|
+
import path19 from "path";
|
|
6628
|
+
import os11 from "os";
|
|
5749
6629
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5750
6630
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
5751
6631
|
function spawnLockPath(sessionName) {
|
|
5752
|
-
return
|
|
6632
|
+
return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
5753
6633
|
}
|
|
5754
6634
|
function isProcessAlive(pid) {
|
|
5755
6635
|
try {
|
|
@@ -5760,13 +6640,13 @@ function isProcessAlive(pid) {
|
|
|
5760
6640
|
}
|
|
5761
6641
|
}
|
|
5762
6642
|
function acquireSpawnLock2(sessionName) {
|
|
5763
|
-
if (!
|
|
6643
|
+
if (!existsSync16(SPAWN_LOCK_DIR)) {
|
|
5764
6644
|
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
5765
6645
|
}
|
|
5766
6646
|
const lockFile = spawnLockPath(sessionName);
|
|
5767
|
-
if (
|
|
6647
|
+
if (existsSync16(lockFile)) {
|
|
5768
6648
|
try {
|
|
5769
|
-
const lock = JSON.parse(
|
|
6649
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
5770
6650
|
const age = Date.now() - lock.timestamp;
|
|
5771
6651
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
5772
6652
|
return false;
|
|
@@ -5774,7 +6654,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
5774
6654
|
} catch {
|
|
5775
6655
|
}
|
|
5776
6656
|
}
|
|
5777
|
-
|
|
6657
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
5778
6658
|
return true;
|
|
5779
6659
|
}
|
|
5780
6660
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -5786,13 +6666,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
5786
6666
|
function resolveBehaviorsExporterScript() {
|
|
5787
6667
|
try {
|
|
5788
6668
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
5789
|
-
const scriptPath =
|
|
5790
|
-
|
|
6669
|
+
const scriptPath = path19.join(
|
|
6670
|
+
path19.dirname(thisFile),
|
|
5791
6671
|
"..",
|
|
5792
6672
|
"bin",
|
|
5793
6673
|
"exe-export-behaviors.js"
|
|
5794
6674
|
);
|
|
5795
|
-
return
|
|
6675
|
+
return existsSync16(scriptPath) ? scriptPath : null;
|
|
5796
6676
|
} catch {
|
|
5797
6677
|
return null;
|
|
5798
6678
|
}
|
|
@@ -5858,12 +6738,12 @@ function extractRootExe(name) {
|
|
|
5858
6738
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
5859
6739
|
}
|
|
5860
6740
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
5861
|
-
if (!
|
|
6741
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
5862
6742
|
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
5863
6743
|
}
|
|
5864
6744
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5865
|
-
const filePath =
|
|
5866
|
-
|
|
6745
|
+
const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
6746
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
5867
6747
|
parentExe: rootExe,
|
|
5868
6748
|
dispatchedBy: dispatchedBy || rootExe,
|
|
5869
6749
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5871,7 +6751,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5871
6751
|
}
|
|
5872
6752
|
function getParentExe(sessionKey) {
|
|
5873
6753
|
try {
|
|
5874
|
-
const data = JSON.parse(
|
|
6754
|
+
const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5875
6755
|
return data.parentExe || null;
|
|
5876
6756
|
} catch {
|
|
5877
6757
|
return null;
|
|
@@ -5879,8 +6759,8 @@ function getParentExe(sessionKey) {
|
|
|
5879
6759
|
}
|
|
5880
6760
|
function getDispatchedBy(sessionKey) {
|
|
5881
6761
|
try {
|
|
5882
|
-
const data = JSON.parse(
|
|
5883
|
-
|
|
6762
|
+
const data = JSON.parse(readFileSync12(
|
|
6763
|
+
path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5884
6764
|
"utf8"
|
|
5885
6765
|
));
|
|
5886
6766
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5950,8 +6830,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5950
6830
|
}
|
|
5951
6831
|
function readDebounceState() {
|
|
5952
6832
|
try {
|
|
5953
|
-
if (!
|
|
5954
|
-
const raw = JSON.parse(
|
|
6833
|
+
if (!existsSync16(DEBOUNCE_FILE)) return {};
|
|
6834
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
5955
6835
|
const state = {};
|
|
5956
6836
|
for (const [key, val] of Object.entries(raw)) {
|
|
5957
6837
|
if (typeof val === "number") {
|
|
@@ -5967,8 +6847,8 @@ function readDebounceState() {
|
|
|
5967
6847
|
}
|
|
5968
6848
|
function writeDebounceState(state) {
|
|
5969
6849
|
try {
|
|
5970
|
-
if (!
|
|
5971
|
-
|
|
6850
|
+
if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
6851
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5972
6852
|
} catch {
|
|
5973
6853
|
}
|
|
5974
6854
|
}
|
|
@@ -6066,8 +6946,8 @@ function sendIntercom(targetSession) {
|
|
|
6066
6946
|
try {
|
|
6067
6947
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6068
6948
|
const agent = baseAgentName(rawAgent);
|
|
6069
|
-
const markerPath =
|
|
6070
|
-
if (
|
|
6949
|
+
const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
6950
|
+
if (existsSync16(markerPath)) {
|
|
6071
6951
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
6072
6952
|
return "debounced";
|
|
6073
6953
|
}
|
|
@@ -6076,8 +6956,8 @@ function sendIntercom(targetSession) {
|
|
|
6076
6956
|
try {
|
|
6077
6957
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
6078
6958
|
const agent = baseAgentName(rawAgent);
|
|
6079
|
-
const taskDir =
|
|
6080
|
-
if (
|
|
6959
|
+
const taskDir = path19.join(process.cwd(), "exe", agent);
|
|
6960
|
+
if (existsSync16(taskDir)) {
|
|
6081
6961
|
const files = readdirSync4(taskDir).filter(
|
|
6082
6962
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
6083
6963
|
);
|
|
@@ -6137,6 +7017,21 @@ function notifyParentExe(sessionKey) {
|
|
|
6137
7017
|
}
|
|
6138
7018
|
return true;
|
|
6139
7019
|
}
|
|
7020
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
7021
|
+
const transport = getTransport();
|
|
7022
|
+
try {
|
|
7023
|
+
const sessions = transport.listSessions();
|
|
7024
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
7025
|
+
execSync7(
|
|
7026
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
7027
|
+
{ timeout: 3e3 }
|
|
7028
|
+
);
|
|
7029
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
7030
|
+
return true;
|
|
7031
|
+
} catch {
|
|
7032
|
+
return false;
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
6140
7035
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6141
7036
|
if (isCoordinatorName(employeeName)) {
|
|
6142
7037
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -6210,26 +7105,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6210
7105
|
const transport = getTransport();
|
|
6211
7106
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
6212
7107
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
6213
|
-
const logDir =
|
|
6214
|
-
const logFile =
|
|
6215
|
-
if (!
|
|
7108
|
+
const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
|
|
7109
|
+
const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
7110
|
+
if (!existsSync16(logDir)) {
|
|
6216
7111
|
mkdirSync7(logDir, { recursive: true });
|
|
6217
7112
|
}
|
|
6218
7113
|
transport.kill(sessionName);
|
|
6219
7114
|
let cleanupSuffix = "";
|
|
6220
7115
|
try {
|
|
6221
7116
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
6222
|
-
const cleanupScript =
|
|
6223
|
-
if (
|
|
7117
|
+
const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
7118
|
+
if (existsSync16(cleanupScript)) {
|
|
6224
7119
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
6225
7120
|
}
|
|
6226
7121
|
} catch {
|
|
6227
7122
|
}
|
|
6228
7123
|
try {
|
|
6229
|
-
const claudeJsonPath =
|
|
7124
|
+
const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
|
|
6230
7125
|
let claudeJson = {};
|
|
6231
7126
|
try {
|
|
6232
|
-
claudeJson = JSON.parse(
|
|
7127
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6233
7128
|
} catch {
|
|
6234
7129
|
}
|
|
6235
7130
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -6237,17 +7132,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6237
7132
|
const trustDir = opts?.cwd ?? projectDir;
|
|
6238
7133
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
6239
7134
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
6240
|
-
|
|
7135
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6241
7136
|
} catch {
|
|
6242
7137
|
}
|
|
6243
7138
|
try {
|
|
6244
|
-
const settingsDir =
|
|
7139
|
+
const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
|
|
6245
7140
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
6246
|
-
const projSettingsDir =
|
|
6247
|
-
const settingsPath =
|
|
7141
|
+
const projSettingsDir = path19.join(settingsDir, normalizedKey);
|
|
7142
|
+
const settingsPath = path19.join(projSettingsDir, "settings.json");
|
|
6248
7143
|
let settings = {};
|
|
6249
7144
|
try {
|
|
6250
|
-
settings = JSON.parse(
|
|
7145
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
6251
7146
|
} catch {
|
|
6252
7147
|
}
|
|
6253
7148
|
const perms = settings.permissions ?? {};
|
|
@@ -6276,7 +7171,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6276
7171
|
perms.allow = allow;
|
|
6277
7172
|
settings.permissions = perms;
|
|
6278
7173
|
mkdirSync7(projSettingsDir, { recursive: true });
|
|
6279
|
-
|
|
7174
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6280
7175
|
}
|
|
6281
7176
|
} catch {
|
|
6282
7177
|
}
|
|
@@ -6291,8 +7186,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6291
7186
|
let behaviorsFlag = "";
|
|
6292
7187
|
let legacyFallbackWarned = false;
|
|
6293
7188
|
if (!useExeAgent && !useBinSymlink) {
|
|
6294
|
-
const identityPath =
|
|
6295
|
-
|
|
7189
|
+
const identityPath = path19.join(
|
|
7190
|
+
os11.homedir(),
|
|
6296
7191
|
".exe-os",
|
|
6297
7192
|
"identity",
|
|
6298
7193
|
`${employeeName}.md`
|
|
@@ -6301,13 +7196,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6301
7196
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
6302
7197
|
if (hasAgentFlag) {
|
|
6303
7198
|
identityFlag = ` --agent ${employeeName}`;
|
|
6304
|
-
} else if (
|
|
7199
|
+
} else if (existsSync16(identityPath)) {
|
|
6305
7200
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
6306
7201
|
legacyFallbackWarned = true;
|
|
6307
7202
|
}
|
|
6308
7203
|
const behaviorsFile = exportBehaviorsSync(
|
|
6309
7204
|
employeeName,
|
|
6310
|
-
|
|
7205
|
+
path19.basename(spawnCwd),
|
|
6311
7206
|
sessionName
|
|
6312
7207
|
);
|
|
6313
7208
|
if (behaviorsFile) {
|
|
@@ -6322,16 +7217,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6322
7217
|
}
|
|
6323
7218
|
let sessionContextFlag = "";
|
|
6324
7219
|
try {
|
|
6325
|
-
const ctxDir =
|
|
7220
|
+
const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6326
7221
|
mkdirSync7(ctxDir, { recursive: true });
|
|
6327
|
-
const ctxFile =
|
|
7222
|
+
const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
|
|
6328
7223
|
const ctxContent = [
|
|
6329
7224
|
`## Session Context`,
|
|
6330
7225
|
`You are running in tmux session: ${sessionName}.`,
|
|
6331
7226
|
`Your parent coordinator session is ${exeSession}.`,
|
|
6332
7227
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
6333
7228
|
].join("\n");
|
|
6334
|
-
|
|
7229
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
6335
7230
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
6336
7231
|
} catch {
|
|
6337
7232
|
}
|
|
@@ -6408,8 +7303,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6408
7303
|
transport.pipeLog(sessionName, logFile);
|
|
6409
7304
|
try {
|
|
6410
7305
|
const mySession = getMySession();
|
|
6411
|
-
const dispatchInfo =
|
|
6412
|
-
|
|
7306
|
+
const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
7307
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
6413
7308
|
dispatchedBy: mySession,
|
|
6414
7309
|
rootExe: exeSession,
|
|
6415
7310
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -6483,15 +7378,15 @@ var init_tmux_routing = __esm({
|
|
|
6483
7378
|
init_intercom_queue();
|
|
6484
7379
|
init_plan_limits();
|
|
6485
7380
|
init_employees();
|
|
6486
|
-
SPAWN_LOCK_DIR =
|
|
6487
|
-
SESSION_CACHE =
|
|
7381
|
+
SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
|
|
7382
|
+
SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
|
|
6488
7383
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
6489
7384
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
6490
7385
|
VERIFY_PANE_LINES = 200;
|
|
6491
7386
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
6492
7387
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
6493
|
-
INTERCOM_LOG2 =
|
|
6494
|
-
DEBOUNCE_FILE =
|
|
7388
|
+
INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
|
|
7389
|
+
DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6495
7390
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6496
7391
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
6497
7392
|
}
|
|
@@ -6514,6 +7409,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
6514
7409
|
args: [scope]
|
|
6515
7410
|
};
|
|
6516
7411
|
}
|
|
7412
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
7413
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
7414
|
+
if (!scope) return { sql: "", args: [] };
|
|
7415
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
7416
|
+
return {
|
|
7417
|
+
sql: ` AND ${col} = ?`,
|
|
7418
|
+
args: [scope]
|
|
7419
|
+
};
|
|
7420
|
+
}
|
|
6517
7421
|
var init_task_scope = __esm({
|
|
6518
7422
|
"src/lib/task-scope.ts"() {
|
|
6519
7423
|
"use strict";
|
|
@@ -6548,14 +7452,14 @@ __export(worker_gate_exports, {
|
|
|
6548
7452
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
6549
7453
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
6550
7454
|
});
|
|
6551
|
-
import { readdirSync as readdirSync6, writeFileSync as
|
|
6552
|
-
import
|
|
7455
|
+
import { readdirSync as readdirSync6, writeFileSync as writeFileSync10, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync17 } from "fs";
|
|
7456
|
+
import path21 from "path";
|
|
6553
7457
|
function tryAcquireWorkerSlot() {
|
|
6554
7458
|
try {
|
|
6555
7459
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
6556
7460
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
6557
|
-
const reservationPath =
|
|
6558
|
-
|
|
7461
|
+
const reservationPath = path21.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
7462
|
+
writeFileSync10(reservationPath, String(process.pid));
|
|
6559
7463
|
const files = readdirSync6(WORKER_PID_DIR);
|
|
6560
7464
|
let alive = 0;
|
|
6561
7465
|
for (const f of files) {
|
|
@@ -6572,7 +7476,7 @@ function tryAcquireWorkerSlot() {
|
|
|
6572
7476
|
alive++;
|
|
6573
7477
|
} catch {
|
|
6574
7478
|
try {
|
|
6575
|
-
unlinkSync8(
|
|
7479
|
+
unlinkSync8(path21.join(WORKER_PID_DIR, f));
|
|
6576
7480
|
} catch {
|
|
6577
7481
|
}
|
|
6578
7482
|
}
|
|
@@ -6596,20 +7500,20 @@ function tryAcquireWorkerSlot() {
|
|
|
6596
7500
|
function registerWorkerPid(pid) {
|
|
6597
7501
|
try {
|
|
6598
7502
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
6599
|
-
|
|
7503
|
+
writeFileSync10(path21.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
6600
7504
|
} catch {
|
|
6601
7505
|
}
|
|
6602
7506
|
}
|
|
6603
7507
|
function cleanupWorkerPid() {
|
|
6604
7508
|
try {
|
|
6605
|
-
unlinkSync8(
|
|
7509
|
+
unlinkSync8(path21.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
6606
7510
|
} catch {
|
|
6607
7511
|
}
|
|
6608
7512
|
}
|
|
6609
7513
|
function tryAcquireBackfillLock() {
|
|
6610
7514
|
try {
|
|
6611
7515
|
mkdirSync9(WORKER_PID_DIR, { recursive: true });
|
|
6612
|
-
if (
|
|
7516
|
+
if (existsSync17(BACKFILL_LOCK)) {
|
|
6613
7517
|
try {
|
|
6614
7518
|
const pid = parseInt(
|
|
6615
7519
|
__require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
|
|
@@ -6625,7 +7529,7 @@ function tryAcquireBackfillLock() {
|
|
|
6625
7529
|
} catch {
|
|
6626
7530
|
}
|
|
6627
7531
|
}
|
|
6628
|
-
|
|
7532
|
+
writeFileSync10(BACKFILL_LOCK, String(process.pid));
|
|
6629
7533
|
return true;
|
|
6630
7534
|
} catch {
|
|
6631
7535
|
return true;
|
|
@@ -6642,9 +7546,9 @@ var init_worker_gate = __esm({
|
|
|
6642
7546
|
"src/lib/worker-gate.ts"() {
|
|
6643
7547
|
"use strict";
|
|
6644
7548
|
init_config();
|
|
6645
|
-
WORKER_PID_DIR =
|
|
7549
|
+
WORKER_PID_DIR = path21.join(EXE_AI_DIR, "worker-pids");
|
|
6646
7550
|
MAX_CONCURRENT_WORKERS = 3;
|
|
6647
|
-
BACKFILL_LOCK =
|
|
7551
|
+
BACKFILL_LOCK = path21.join(WORKER_PID_DIR, "backfill.lock");
|
|
6648
7552
|
}
|
|
6649
7553
|
});
|
|
6650
7554
|
|
|
@@ -6656,13 +7560,13 @@ __export(crypto_exports, {
|
|
|
6656
7560
|
initSyncCrypto: () => initSyncCrypto,
|
|
6657
7561
|
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
6658
7562
|
});
|
|
6659
|
-
import
|
|
7563
|
+
import crypto7 from "crypto";
|
|
6660
7564
|
function initSyncCrypto(masterKey) {
|
|
6661
7565
|
if (masterKey.length !== 32) {
|
|
6662
7566
|
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
6663
7567
|
}
|
|
6664
7568
|
_syncKey = Buffer.from(
|
|
6665
|
-
|
|
7569
|
+
crypto7.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
6666
7570
|
);
|
|
6667
7571
|
}
|
|
6668
7572
|
function isSyncCryptoInitialized() {
|
|
@@ -6676,8 +7580,8 @@ function requireSyncKey() {
|
|
|
6676
7580
|
}
|
|
6677
7581
|
function encryptSyncBlob(data) {
|
|
6678
7582
|
const key = requireSyncKey();
|
|
6679
|
-
const iv =
|
|
6680
|
-
const cipher =
|
|
7583
|
+
const iv = crypto7.randomBytes(IV_LENGTH);
|
|
7584
|
+
const cipher = crypto7.createCipheriv(ALGORITHM, key, iv);
|
|
6681
7585
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
6682
7586
|
const tag = cipher.getAuthTag();
|
|
6683
7587
|
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
@@ -6691,7 +7595,7 @@ function decryptSyncBlob(ciphertext) {
|
|
|
6691
7595
|
const iv = combined.subarray(0, IV_LENGTH);
|
|
6692
7596
|
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
6693
7597
|
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
6694
|
-
const decipher =
|
|
7598
|
+
const decipher = crypto7.createDecipheriv(ALGORITHM, key, iv);
|
|
6695
7599
|
decipher.setAuthTag(tag);
|
|
6696
7600
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
6697
7601
|
}
|
|
@@ -6746,8 +7650,8 @@ __export(crdt_sync_exports, {
|
|
|
6746
7650
|
rebuildFromDb: () => rebuildFromDb
|
|
6747
7651
|
});
|
|
6748
7652
|
import * as Y from "yjs";
|
|
6749
|
-
import { readFileSync as
|
|
6750
|
-
import
|
|
7653
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
|
|
7654
|
+
import path22 from "path";
|
|
6751
7655
|
import { homedir } from "os";
|
|
6752
7656
|
function getStatePath() {
|
|
6753
7657
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -6759,9 +7663,9 @@ function initCrdtDoc() {
|
|
|
6759
7663
|
if (doc) return doc;
|
|
6760
7664
|
doc = new Y.Doc();
|
|
6761
7665
|
const sp = getStatePath();
|
|
6762
|
-
if (
|
|
7666
|
+
if (existsSync18(sp)) {
|
|
6763
7667
|
try {
|
|
6764
|
-
const state =
|
|
7668
|
+
const state = readFileSync14(sp);
|
|
6765
7669
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
6766
7670
|
} catch {
|
|
6767
7671
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -6903,10 +7807,10 @@ function persistState() {
|
|
|
6903
7807
|
if (!doc) return;
|
|
6904
7808
|
try {
|
|
6905
7809
|
const sp = getStatePath();
|
|
6906
|
-
const dir =
|
|
6907
|
-
if (!
|
|
7810
|
+
const dir = path22.dirname(sp);
|
|
7811
|
+
if (!existsSync18(dir)) mkdirSync10(dir, { recursive: true });
|
|
6908
7812
|
const state = Y.encodeStateAsUpdate(doc);
|
|
6909
|
-
|
|
7813
|
+
writeFileSync11(sp, Buffer.from(state));
|
|
6910
7814
|
} catch {
|
|
6911
7815
|
}
|
|
6912
7816
|
}
|
|
@@ -6947,7 +7851,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
6947
7851
|
var init_crdt_sync = __esm({
|
|
6948
7852
|
"src/lib/crdt-sync.ts"() {
|
|
6949
7853
|
"use strict";
|
|
6950
|
-
DEFAULT_STATE_PATH =
|
|
7854
|
+
DEFAULT_STATE_PATH = path22.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
6951
7855
|
_statePathOverride = null;
|
|
6952
7856
|
doc = null;
|
|
6953
7857
|
}
|
|
@@ -6979,39 +7883,107 @@ __export(cloud_sync_exports, {
|
|
|
6979
7883
|
cloudSync: () => cloudSync,
|
|
6980
7884
|
mergeConfig: () => mergeConfig,
|
|
6981
7885
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
7886
|
+
pushToPostgres: () => pushToPostgres,
|
|
6982
7887
|
recordRosterDeletion: () => recordRosterDeletion
|
|
6983
7888
|
});
|
|
6984
|
-
import { readFileSync as
|
|
6985
|
-
import
|
|
6986
|
-
import
|
|
7889
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
7890
|
+
import crypto8 from "crypto";
|
|
7891
|
+
import path23 from "path";
|
|
6987
7892
|
import { homedir as homedir2 } from "os";
|
|
6988
7893
|
function sqlSafe(v) {
|
|
6989
7894
|
return v === void 0 ? null : v;
|
|
6990
7895
|
}
|
|
6991
7896
|
function logError(msg) {
|
|
6992
7897
|
try {
|
|
6993
|
-
const logPath =
|
|
7898
|
+
const logPath = path23.join(homedir2(), ".exe-os", "workers.log");
|
|
6994
7899
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
6995
7900
|
`);
|
|
6996
7901
|
} catch {
|
|
6997
7902
|
}
|
|
6998
7903
|
}
|
|
7904
|
+
function loadPgClient() {
|
|
7905
|
+
if (_pgFailed) return null;
|
|
7906
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
7907
|
+
const configPath = path23.join(EXE_AI_DIR, "config.json");
|
|
7908
|
+
let cloudPostgresUrl;
|
|
7909
|
+
try {
|
|
7910
|
+
if (existsSync19(configPath)) {
|
|
7911
|
+
const cfg = JSON.parse(readFileSync15(configPath, "utf8"));
|
|
7912
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
7913
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
7914
|
+
_pgFailed = true;
|
|
7915
|
+
return null;
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
} catch {
|
|
7919
|
+
}
|
|
7920
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
7921
|
+
if (!url) {
|
|
7922
|
+
_pgFailed = true;
|
|
7923
|
+
return null;
|
|
7924
|
+
}
|
|
7925
|
+
if (!_pgPromise) {
|
|
7926
|
+
_pgPromise = (async () => {
|
|
7927
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
7928
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
7929
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir2(), "exe-db");
|
|
7930
|
+
const req = createRequire3(path23.join(exeDbRoot, "package.json"));
|
|
7931
|
+
const entry = req.resolve("@prisma/client");
|
|
7932
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
7933
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
7934
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
7935
|
+
return new Ctor();
|
|
7936
|
+
})().catch(() => {
|
|
7937
|
+
_pgFailed = true;
|
|
7938
|
+
_pgPromise = null;
|
|
7939
|
+
throw new Error("pg_unavailable");
|
|
7940
|
+
});
|
|
7941
|
+
}
|
|
7942
|
+
return _pgPromise;
|
|
7943
|
+
}
|
|
7944
|
+
async function pushToPostgres(records) {
|
|
7945
|
+
const loader = loadPgClient();
|
|
7946
|
+
if (!loader) return 0;
|
|
7947
|
+
let prisma;
|
|
7948
|
+
try {
|
|
7949
|
+
prisma = await loader;
|
|
7950
|
+
} catch {
|
|
7951
|
+
return 0;
|
|
7952
|
+
}
|
|
7953
|
+
let inserted = 0;
|
|
7954
|
+
for (const rec of records) {
|
|
7955
|
+
try {
|
|
7956
|
+
await prisma.$executeRawUnsafe(
|
|
7957
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
7958
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
7959
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
7960
|
+
String(rec.id ?? ""),
|
|
7961
|
+
JSON.stringify(rec),
|
|
7962
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
7963
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
7964
|
+
);
|
|
7965
|
+
inserted++;
|
|
7966
|
+
} catch {
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
return inserted;
|
|
7970
|
+
}
|
|
6999
7971
|
async function withRosterLock(fn) {
|
|
7000
7972
|
try {
|
|
7001
7973
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
7002
7974
|
closeSync2(fd);
|
|
7003
|
-
|
|
7975
|
+
writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
|
|
7004
7976
|
} catch (err) {
|
|
7005
7977
|
if (err.code === "EEXIST") {
|
|
7006
7978
|
try {
|
|
7007
|
-
const ts = parseInt(
|
|
7979
|
+
const ts = parseInt(readFileSync15(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
7008
7980
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
7009
7981
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
7010
7982
|
}
|
|
7011
7983
|
unlinkSync10(ROSTER_LOCK_PATH);
|
|
7012
7984
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
7013
7985
|
closeSync2(fd);
|
|
7014
|
-
|
|
7986
|
+
writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
|
|
7015
7987
|
} catch (retryErr) {
|
|
7016
7988
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
7017
7989
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -7281,6 +8253,10 @@ async function cloudSync(config) {
|
|
|
7281
8253
|
const maxVersion = Number(records[records.length - 1].version);
|
|
7282
8254
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
7283
8255
|
if (!pushOk) break;
|
|
8256
|
+
try {
|
|
8257
|
+
await pushToPostgres(records);
|
|
8258
|
+
} catch {
|
|
8259
|
+
}
|
|
7284
8260
|
await client.execute({
|
|
7285
8261
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
7286
8262
|
args: [String(maxVersion)]
|
|
@@ -7385,8 +8361,8 @@ async function cloudSync(config) {
|
|
|
7385
8361
|
try {
|
|
7386
8362
|
const employees = await loadEmployees();
|
|
7387
8363
|
rosterResult.employees = employees.length;
|
|
7388
|
-
const idDir =
|
|
7389
|
-
if (
|
|
8364
|
+
const idDir = path23.join(EXE_AI_DIR, "identity");
|
|
8365
|
+
if (existsSync19(idDir)) {
|
|
7390
8366
|
rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
|
|
7391
8367
|
}
|
|
7392
8368
|
} catch {
|
|
@@ -7407,62 +8383,62 @@ async function cloudSync(config) {
|
|
|
7407
8383
|
function recordRosterDeletion(name) {
|
|
7408
8384
|
let deletions = [];
|
|
7409
8385
|
try {
|
|
7410
|
-
if (
|
|
7411
|
-
deletions = JSON.parse(
|
|
8386
|
+
if (existsSync19(ROSTER_DELETIONS_PATH)) {
|
|
8387
|
+
deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
7412
8388
|
}
|
|
7413
8389
|
} catch {
|
|
7414
8390
|
}
|
|
7415
8391
|
if (!deletions.includes(name)) deletions.push(name);
|
|
7416
|
-
|
|
8392
|
+
writeFileSync12(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
7417
8393
|
}
|
|
7418
8394
|
function consumeRosterDeletions() {
|
|
7419
8395
|
try {
|
|
7420
|
-
if (!
|
|
7421
|
-
const deletions = JSON.parse(
|
|
7422
|
-
|
|
8396
|
+
if (!existsSync19(ROSTER_DELETIONS_PATH)) return [];
|
|
8397
|
+
const deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
8398
|
+
writeFileSync12(ROSTER_DELETIONS_PATH, "[]");
|
|
7423
8399
|
return deletions;
|
|
7424
8400
|
} catch {
|
|
7425
8401
|
return [];
|
|
7426
8402
|
}
|
|
7427
8403
|
}
|
|
7428
8404
|
function buildRosterBlob(paths) {
|
|
7429
|
-
const rosterPath = paths?.rosterPath ??
|
|
7430
|
-
const identityDir = paths?.identityDir ??
|
|
7431
|
-
const configPath = paths?.configPath ??
|
|
8405
|
+
const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
|
|
8406
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
8407
|
+
const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
7432
8408
|
let roster = [];
|
|
7433
|
-
if (
|
|
8409
|
+
if (existsSync19(rosterPath)) {
|
|
7434
8410
|
try {
|
|
7435
|
-
roster = JSON.parse(
|
|
8411
|
+
roster = JSON.parse(readFileSync15(rosterPath, "utf-8"));
|
|
7436
8412
|
} catch {
|
|
7437
8413
|
}
|
|
7438
8414
|
}
|
|
7439
8415
|
const identities = {};
|
|
7440
|
-
if (
|
|
8416
|
+
if (existsSync19(identityDir)) {
|
|
7441
8417
|
for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
7442
8418
|
try {
|
|
7443
|
-
identities[file] =
|
|
8419
|
+
identities[file] = readFileSync15(path23.join(identityDir, file), "utf-8");
|
|
7444
8420
|
} catch {
|
|
7445
8421
|
}
|
|
7446
8422
|
}
|
|
7447
8423
|
}
|
|
7448
8424
|
let config;
|
|
7449
|
-
if (
|
|
8425
|
+
if (existsSync19(configPath)) {
|
|
7450
8426
|
try {
|
|
7451
|
-
config = JSON.parse(
|
|
8427
|
+
config = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
7452
8428
|
} catch {
|
|
7453
8429
|
}
|
|
7454
8430
|
}
|
|
7455
8431
|
let agentConfig;
|
|
7456
|
-
const agentConfigPath =
|
|
7457
|
-
if (
|
|
8432
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
8433
|
+
if (existsSync19(agentConfigPath)) {
|
|
7458
8434
|
try {
|
|
7459
|
-
agentConfig = JSON.parse(
|
|
8435
|
+
agentConfig = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
|
|
7460
8436
|
} catch {
|
|
7461
8437
|
}
|
|
7462
8438
|
}
|
|
7463
8439
|
const deletedNames = consumeRosterDeletions();
|
|
7464
8440
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
7465
|
-
const hash =
|
|
8441
|
+
const hash = crypto8.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
7466
8442
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
7467
8443
|
}
|
|
7468
8444
|
async function cloudPushRoster(config) {
|
|
@@ -7532,23 +8508,24 @@ async function cloudPullRoster(config) {
|
|
|
7532
8508
|
}
|
|
7533
8509
|
}
|
|
7534
8510
|
function mergeConfig(remoteConfig, configPath) {
|
|
7535
|
-
const cfgPath = configPath ??
|
|
8511
|
+
const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
7536
8512
|
let local = {};
|
|
7537
|
-
if (
|
|
8513
|
+
if (existsSync19(cfgPath)) {
|
|
7538
8514
|
try {
|
|
7539
|
-
local = JSON.parse(
|
|
8515
|
+
local = JSON.parse(readFileSync15(cfgPath, "utf-8"));
|
|
7540
8516
|
} catch {
|
|
7541
8517
|
}
|
|
7542
8518
|
}
|
|
7543
8519
|
const merged = { ...remoteConfig, ...local };
|
|
7544
|
-
const dir =
|
|
7545
|
-
|
|
7546
|
-
|
|
8520
|
+
const dir = path23.dirname(cfgPath);
|
|
8521
|
+
ensurePrivateDirSync(dir);
|
|
8522
|
+
writeFileSync12(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
8523
|
+
enforcePrivateFileSync(cfgPath);
|
|
7547
8524
|
}
|
|
7548
8525
|
async function mergeRosterFromRemote(remote, paths) {
|
|
7549
8526
|
return withRosterLock(async () => {
|
|
7550
8527
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
7551
|
-
const identityDir = paths?.identityDir ??
|
|
8528
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
7552
8529
|
const localEmployees = await loadEmployees(rosterPath);
|
|
7553
8530
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
7554
8531
|
let added = 0;
|
|
@@ -7569,15 +8546,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
7569
8546
|
) ?? lookupKey;
|
|
7570
8547
|
const remoteIdentity = remote.identities[matchedKey];
|
|
7571
8548
|
if (remoteIdentity) {
|
|
7572
|
-
if (!
|
|
7573
|
-
const idPath =
|
|
8549
|
+
if (!existsSync19(identityDir)) mkdirSync11(identityDir, { recursive: true });
|
|
8550
|
+
const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
|
|
7574
8551
|
let localIdentity = null;
|
|
7575
8552
|
try {
|
|
7576
|
-
localIdentity =
|
|
8553
|
+
localIdentity = existsSync19(idPath) ? readFileSync15(idPath, "utf-8") : null;
|
|
7577
8554
|
} catch {
|
|
7578
8555
|
}
|
|
7579
8556
|
if (localIdentity !== remoteIdentity) {
|
|
7580
|
-
|
|
8557
|
+
writeFileSync12(idPath, remoteIdentity, "utf-8");
|
|
7581
8558
|
identitiesUpdated++;
|
|
7582
8559
|
}
|
|
7583
8560
|
}
|
|
@@ -7603,16 +8580,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
7603
8580
|
}
|
|
7604
8581
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
7605
8582
|
try {
|
|
7606
|
-
const agentConfigPath =
|
|
8583
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
7607
8584
|
let local = {};
|
|
7608
|
-
if (
|
|
8585
|
+
if (existsSync19(agentConfigPath)) {
|
|
7609
8586
|
try {
|
|
7610
|
-
local = JSON.parse(
|
|
8587
|
+
local = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
|
|
7611
8588
|
} catch {
|
|
7612
8589
|
}
|
|
7613
8590
|
}
|
|
7614
8591
|
const merged = { ...remote.agentConfig, ...local };
|
|
7615
|
-
|
|
8592
|
+
ensurePrivateDirSync(path23.dirname(agentConfigPath));
|
|
8593
|
+
writeFileSync12(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8594
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
7616
8595
|
} catch {
|
|
7617
8596
|
}
|
|
7618
8597
|
}
|
|
@@ -8036,7 +9015,7 @@ async function cloudPullDocuments(config) {
|
|
|
8036
9015
|
}
|
|
8037
9016
|
return { pulled };
|
|
8038
9017
|
}
|
|
8039
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
9018
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
8040
9019
|
var init_cloud_sync = __esm({
|
|
8041
9020
|
"src/lib/cloud-sync.ts"() {
|
|
8042
9021
|
"use strict";
|
|
@@ -8047,12 +9026,15 @@ var init_cloud_sync = __esm({
|
|
|
8047
9026
|
init_config();
|
|
8048
9027
|
init_crdt_sync();
|
|
8049
9028
|
init_employees();
|
|
9029
|
+
init_secure_files();
|
|
8050
9030
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
8051
9031
|
FETCH_TIMEOUT_MS = 3e4;
|
|
8052
9032
|
PUSH_BATCH_SIZE = 5e3;
|
|
8053
|
-
ROSTER_LOCK_PATH =
|
|
9033
|
+
ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
|
|
8054
9034
|
LOCK_STALE_MS = 3e4;
|
|
8055
|
-
|
|
9035
|
+
_pgPromise = null;
|
|
9036
|
+
_pgFailed = false;
|
|
9037
|
+
ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
|
|
8056
9038
|
}
|
|
8057
9039
|
});
|
|
8058
9040
|
|
|
@@ -8064,7 +9046,7 @@ __export(schedules_exports, {
|
|
|
8064
9046
|
listSchedules: () => listSchedules,
|
|
8065
9047
|
parseHumanCron: () => parseHumanCron
|
|
8066
9048
|
});
|
|
8067
|
-
import
|
|
9049
|
+
import crypto9 from "crypto";
|
|
8068
9050
|
import { execSync as execSync9 } from "child_process";
|
|
8069
9051
|
async function ensureDb() {
|
|
8070
9052
|
if (!isInitialized()) {
|
|
@@ -8133,7 +9115,7 @@ function parseHumanCron(input) {
|
|
|
8133
9115
|
async function createSchedule(input) {
|
|
8134
9116
|
await ensureDb();
|
|
8135
9117
|
const client = getClient();
|
|
8136
|
-
const id =
|
|
9118
|
+
const id = crypto9.randomUUID().slice(0, 8);
|
|
8137
9119
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8138
9120
|
const prompt = input.prompt ?? input.description;
|
|
8139
9121
|
await client.execute({
|
|
@@ -8234,10 +9216,10 @@ var init_schedules = __esm({
|
|
|
8234
9216
|
|
|
8235
9217
|
// src/bin/exe-boot.ts
|
|
8236
9218
|
init_employees();
|
|
8237
|
-
import
|
|
9219
|
+
import path24 from "path";
|
|
8238
9220
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
8239
|
-
import { existsSync as
|
|
8240
|
-
import
|
|
9221
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
|
|
9222
|
+
import os12 from "os";
|
|
8241
9223
|
|
|
8242
9224
|
// src/lib/employee-templates.ts
|
|
8243
9225
|
init_global_procedures();
|
|
@@ -8739,18 +9721,18 @@ init_notifications();
|
|
|
8739
9721
|
init_config();
|
|
8740
9722
|
init_session_key();
|
|
8741
9723
|
init_employees();
|
|
8742
|
-
import { readFileSync as
|
|
9724
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
|
|
8743
9725
|
import { execSync as execSync8 } from "child_process";
|
|
8744
|
-
import
|
|
8745
|
-
var CACHE_DIR =
|
|
9726
|
+
import path20 from "path";
|
|
9727
|
+
var CACHE_DIR = path20.join(EXE_AI_DIR, "session-cache");
|
|
8746
9728
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
8747
9729
|
function getMarkerPath() {
|
|
8748
|
-
return
|
|
9730
|
+
return path20.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
8749
9731
|
}
|
|
8750
9732
|
function writeActiveAgent(agentId, agentRole) {
|
|
8751
9733
|
try {
|
|
8752
9734
|
mkdirSync8(CACHE_DIR, { recursive: true });
|
|
8753
|
-
|
|
9735
|
+
writeFileSync9(
|
|
8754
9736
|
getMarkerPath(),
|
|
8755
9737
|
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
8756
9738
|
);
|
|
@@ -8760,11 +9742,11 @@ function writeActiveAgent(agentId, agentRole) {
|
|
|
8760
9742
|
function cleanupSessionMarkers() {
|
|
8761
9743
|
const key = getSessionKey();
|
|
8762
9744
|
try {
|
|
8763
|
-
unlinkSync7(
|
|
9745
|
+
unlinkSync7(path20.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
8764
9746
|
} catch {
|
|
8765
9747
|
}
|
|
8766
9748
|
try {
|
|
8767
|
-
unlinkSync7(
|
|
9749
|
+
unlinkSync7(path20.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
8768
9750
|
} catch {
|
|
8769
9751
|
}
|
|
8770
9752
|
}
|
|
@@ -8854,7 +9836,7 @@ async function boot(options) {
|
|
|
8854
9836
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
8855
9837
|
for (const dir of employeeDirs) {
|
|
8856
9838
|
const employee = dir.name;
|
|
8857
|
-
const taskDir =
|
|
9839
|
+
const taskDir = path24.join(exeDir, employee);
|
|
8858
9840
|
let files;
|
|
8859
9841
|
try {
|
|
8860
9842
|
files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
|
|
@@ -8865,7 +9847,7 @@ async function boot(options) {
|
|
|
8865
9847
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
8866
9848
|
let content;
|
|
8867
9849
|
try {
|
|
8868
|
-
content = readFs(
|
|
9850
|
+
content = readFs(path24.join(taskDir, file), "utf8");
|
|
8869
9851
|
} catch {
|
|
8870
9852
|
continue;
|
|
8871
9853
|
}
|
|
@@ -8951,12 +9933,12 @@ async function boot(options) {
|
|
|
8951
9933
|
}
|
|
8952
9934
|
try {
|
|
8953
9935
|
for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
|
|
8954
|
-
const reviewDir =
|
|
8955
|
-
if (
|
|
9936
|
+
const reviewDir = path24.join(process.cwd(), "exe", reviewDirName);
|
|
9937
|
+
if (existsSync20(reviewDir)) {
|
|
8956
9938
|
for (const f of readdirSync8(reviewDir)) {
|
|
8957
9939
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
8958
9940
|
try {
|
|
8959
|
-
unlinkSync11(
|
|
9941
|
+
unlinkSync11(path24.join(reviewDir, f));
|
|
8960
9942
|
} catch {
|
|
8961
9943
|
}
|
|
8962
9944
|
}
|
|
@@ -9002,12 +9984,12 @@ async function boot(options) {
|
|
|
9002
9984
|
});
|
|
9003
9985
|
const taskFile = String(r.task_file);
|
|
9004
9986
|
try {
|
|
9005
|
-
const filePath =
|
|
9006
|
-
if (
|
|
9007
|
-
let content =
|
|
9987
|
+
const filePath = path24.join(process.cwd(), taskFile);
|
|
9988
|
+
if (existsSync20(filePath)) {
|
|
9989
|
+
let content = readFileSync16(filePath, "utf8");
|
|
9008
9990
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
9009
|
-
const { writeFileSync:
|
|
9010
|
-
|
|
9991
|
+
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
9992
|
+
writeFileSync13(filePath, content);
|
|
9011
9993
|
}
|
|
9012
9994
|
} catch {
|
|
9013
9995
|
}
|
|
@@ -9501,19 +10483,19 @@ async function boot(options) {
|
|
|
9501
10483
|
})()
|
|
9502
10484
|
]);
|
|
9503
10485
|
try {
|
|
9504
|
-
const configPath =
|
|
9505
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
10486
|
+
const configPath = path24.join(
|
|
10487
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
|
|
9506
10488
|
"config.json"
|
|
9507
10489
|
);
|
|
9508
|
-
if (
|
|
9509
|
-
const raw = JSON.parse(
|
|
10490
|
+
if (existsSync20(configPath)) {
|
|
10491
|
+
const raw = JSON.parse(readFileSync16(configPath, "utf8"));
|
|
9510
10492
|
briefData.cloudConnected = !!(raw.cloud || raw.turso);
|
|
9511
10493
|
}
|
|
9512
10494
|
} catch {
|
|
9513
10495
|
}
|
|
9514
10496
|
try {
|
|
9515
|
-
const backfillFlagPath =
|
|
9516
|
-
const isBackfillNeeded = () =>
|
|
10497
|
+
const backfillFlagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
10498
|
+
const isBackfillNeeded = () => existsSync20(backfillFlagPath);
|
|
9517
10499
|
const coverageResult = await client.execute({
|
|
9518
10500
|
sql: `SELECT COUNT(*) as total,
|
|
9519
10501
|
SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
|
|
@@ -9535,8 +10517,8 @@ async function boot(options) {
|
|
|
9535
10517
|
let daemonRunning = false;
|
|
9536
10518
|
let daemonUptime;
|
|
9537
10519
|
let daemonRequestsServed;
|
|
9538
|
-
const socketPath =
|
|
9539
|
-
if (
|
|
10520
|
+
const socketPath = path24.join(EXE_AI_DIR, "exed.sock");
|
|
10521
|
+
if (existsSync20(socketPath)) {
|
|
9540
10522
|
try {
|
|
9541
10523
|
const net2 = await import("net");
|
|
9542
10524
|
const health = await new Promise((resolve) => {
|
|
@@ -9578,10 +10560,10 @@ async function boot(options) {
|
|
|
9578
10560
|
}
|
|
9579
10561
|
}
|
|
9580
10562
|
if (!daemonRunning) {
|
|
9581
|
-
const pidPath =
|
|
9582
|
-
if (
|
|
10563
|
+
const pidPath = path24.join(EXE_AI_DIR, "exed.pid");
|
|
10564
|
+
if (existsSync20(pidPath)) {
|
|
9583
10565
|
try {
|
|
9584
|
-
const pid = parseInt(
|
|
10566
|
+
const pid = parseInt(readFileSync16(pidPath, "utf8").trim(), 10);
|
|
9585
10567
|
if (pid > 0) {
|
|
9586
10568
|
process.kill(pid, 0);
|
|
9587
10569
|
daemonRunning = true;
|
|
@@ -9592,8 +10574,8 @@ async function boot(options) {
|
|
|
9592
10574
|
}
|
|
9593
10575
|
if (nullCount === 0) {
|
|
9594
10576
|
try {
|
|
9595
|
-
const flagPath =
|
|
9596
|
-
if (
|
|
10577
|
+
const flagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
10578
|
+
if (existsSync20(flagPath)) {
|
|
9597
10579
|
const { unlinkSync: unlinkSync12 } = await import("fs");
|
|
9598
10580
|
unlinkSync12(flagPath);
|
|
9599
10581
|
}
|
|
@@ -9619,10 +10601,10 @@ async function boot(options) {
|
|
|
9619
10601
|
const { spawn: spawn2 } = await import("child_process");
|
|
9620
10602
|
const { fileURLToPath: fileURLToPath4 } = await import("url");
|
|
9621
10603
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
9622
|
-
const backfillPath =
|
|
9623
|
-
if (
|
|
10604
|
+
const backfillPath = path24.resolve(path24.dirname(thisFile), "backfill-vectors.js");
|
|
10605
|
+
if (existsSync20(backfillPath)) {
|
|
9624
10606
|
const { openSync: openSync3, closeSync: closeSync3 } = await import("fs");
|
|
9625
|
-
const workerLogPath =
|
|
10607
|
+
const workerLogPath = path24.join(EXE_AI_DIR, "workers.log");
|
|
9626
10608
|
let stderrFd = "ignore";
|
|
9627
10609
|
try {
|
|
9628
10610
|
stderrFd = openSync3(workerLogPath, "a");
|
|
@@ -9652,8 +10634,8 @@ async function boot(options) {
|
|
|
9652
10634
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
9653
10635
|
const missing = [];
|
|
9654
10636
|
for (const bin of criticalBinaries) {
|
|
9655
|
-
const binPath =
|
|
9656
|
-
if (!
|
|
10637
|
+
const binPath = path24.resolve(path24.dirname(thisFile), bin);
|
|
10638
|
+
if (!existsSync20(binPath)) {
|
|
9657
10639
|
missing.push(`dist/bin/${bin}`);
|
|
9658
10640
|
}
|
|
9659
10641
|
}
|
|
@@ -9682,7 +10664,7 @@ async function boot(options) {
|
|
|
9682
10664
|
console.log(brief);
|
|
9683
10665
|
return;
|
|
9684
10666
|
}
|
|
9685
|
-
const sessionDir =
|
|
10667
|
+
const sessionDir = path24.join(EXE_AI_DIR, "sessions", coordinatorName);
|
|
9686
10668
|
await mkdir5(sessionDir, { recursive: true });
|
|
9687
10669
|
const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
|
|
9688
10670
|
|
|
@@ -9691,7 +10673,7 @@ async function boot(options) {
|
|
|
9691
10673
|
# Status Brief
|
|
9692
10674
|
|
|
9693
10675
|
${brief}`;
|
|
9694
|
-
await writeFile6(
|
|
10676
|
+
await writeFile6(path24.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
9695
10677
|
const unread = await readUnreadNotifications();
|
|
9696
10678
|
if (unread.length > 0) {
|
|
9697
10679
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -9700,12 +10682,12 @@ ${brief}`;
|
|
|
9700
10682
|
await cleanupOldNotifications();
|
|
9701
10683
|
console.log(brief);
|
|
9702
10684
|
try {
|
|
9703
|
-
const configPath2 =
|
|
9704
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
10685
|
+
const configPath2 = path24.join(
|
|
10686
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
|
|
9705
10687
|
"config.json"
|
|
9706
10688
|
);
|
|
9707
|
-
if (
|
|
9708
|
-
const rawCfg = JSON.parse(
|
|
10689
|
+
if (existsSync20(configPath2)) {
|
|
10690
|
+
const rawCfg = JSON.parse(readFileSync16(configPath2, "utf8"));
|
|
9709
10691
|
const cloudCfg = rawCfg.cloud;
|
|
9710
10692
|
if (cloudCfg?.apiKey) {
|
|
9711
10693
|
const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|