@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
|
@@ -307,9 +307,47 @@ var init_provider_table = __esm({
|
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
// src/lib/secure-files.ts
|
|
311
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
312
|
+
import { chmod, mkdir } from "fs/promises";
|
|
313
|
+
async function ensurePrivateDir(dirPath) {
|
|
314
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
315
|
+
try {
|
|
316
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function ensurePrivateDirSync(dirPath) {
|
|
321
|
+
mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
322
|
+
try {
|
|
323
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async function enforcePrivateFile(filePath) {
|
|
328
|
+
try {
|
|
329
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function enforcePrivateFileSync(filePath) {
|
|
334
|
+
try {
|
|
335
|
+
if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
340
|
+
var init_secure_files = __esm({
|
|
341
|
+
"src/lib/secure-files.ts"() {
|
|
342
|
+
"use strict";
|
|
343
|
+
PRIVATE_DIR_MODE = 448;
|
|
344
|
+
PRIVATE_FILE_MODE = 384;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
310
348
|
// src/lib/config.ts
|
|
311
|
-
import { readFile, writeFile
|
|
312
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
349
|
+
import { readFile, writeFile } from "fs/promises";
|
|
350
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
313
351
|
import path2 from "path";
|
|
314
352
|
import os2 from "os";
|
|
315
353
|
function resolveDataDir() {
|
|
@@ -317,7 +355,7 @@ function resolveDataDir() {
|
|
|
317
355
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
318
356
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
319
357
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
320
|
-
if (!
|
|
358
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
321
359
|
try {
|
|
322
360
|
renameSync(legacyDir, newDir);
|
|
323
361
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -380,9 +418,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
380
418
|
}
|
|
381
419
|
async function loadConfig() {
|
|
382
420
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
383
|
-
await
|
|
421
|
+
await ensurePrivateDir(dir);
|
|
384
422
|
const configPath = path2.join(dir, "config.json");
|
|
385
|
-
if (!
|
|
423
|
+
if (!existsSync3(configPath)) {
|
|
386
424
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
387
425
|
}
|
|
388
426
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -395,6 +433,7 @@ async function loadConfig() {
|
|
|
395
433
|
`);
|
|
396
434
|
try {
|
|
397
435
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
436
|
+
await enforcePrivateFile(configPath);
|
|
398
437
|
} catch {
|
|
399
438
|
}
|
|
400
439
|
}
|
|
@@ -414,6 +453,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
414
453
|
var init_config = __esm({
|
|
415
454
|
"src/lib/config.ts"() {
|
|
416
455
|
"use strict";
|
|
456
|
+
init_secure_files();
|
|
417
457
|
EXE_AI_DIR = resolveDataDir();
|
|
418
458
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
419
459
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -518,10 +558,10 @@ var init_runtime_table = __esm({
|
|
|
518
558
|
});
|
|
519
559
|
|
|
520
560
|
// src/lib/agent-config.ts
|
|
521
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
561
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
522
562
|
import path3 from "path";
|
|
523
563
|
function loadAgentConfig() {
|
|
524
|
-
if (!
|
|
564
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
525
565
|
try {
|
|
526
566
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
527
567
|
} catch {
|
|
@@ -542,6 +582,7 @@ var init_agent_config = __esm({
|
|
|
542
582
|
"use strict";
|
|
543
583
|
init_config();
|
|
544
584
|
init_runtime_table();
|
|
585
|
+
init_secure_files();
|
|
545
586
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
546
587
|
DEFAULT_MODELS = {
|
|
547
588
|
claude: "claude-opus-4",
|
|
@@ -560,16 +601,16 @@ __export(intercom_queue_exports, {
|
|
|
560
601
|
queueIntercom: () => queueIntercom,
|
|
561
602
|
readQueue: () => readQueue
|
|
562
603
|
});
|
|
563
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
604
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
564
605
|
import path4 from "path";
|
|
565
606
|
import os3 from "os";
|
|
566
607
|
function ensureDir() {
|
|
567
608
|
const dir = path4.dirname(QUEUE_PATH);
|
|
568
|
-
if (!
|
|
609
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
569
610
|
}
|
|
570
611
|
function readQueue() {
|
|
571
612
|
try {
|
|
572
|
-
if (!
|
|
613
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
573
614
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
574
615
|
} catch {
|
|
575
616
|
return [];
|
|
@@ -734,7 +775,7 @@ var init_db_retry = __esm({
|
|
|
734
775
|
|
|
735
776
|
// src/lib/employees.ts
|
|
736
777
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
737
|
-
import { existsSync as
|
|
778
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
738
779
|
import { execSync as execSync3 } from "child_process";
|
|
739
780
|
import path5 from "path";
|
|
740
781
|
import os4 from "os";
|
|
@@ -755,7 +796,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
755
796
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
756
797
|
}
|
|
757
798
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
758
|
-
if (!
|
|
799
|
+
if (!existsSync6(employeesPath)) return [];
|
|
759
800
|
try {
|
|
760
801
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
761
802
|
} catch {
|
|
@@ -779,7 +820,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
779
820
|
if (!emp) return false;
|
|
780
821
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
781
822
|
}
|
|
782
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
823
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
783
824
|
var init_employees = __esm({
|
|
784
825
|
"src/lib/employees.ts"() {
|
|
785
826
|
"use strict";
|
|
@@ -788,16 +829,638 @@ var init_employees = __esm({
|
|
|
788
829
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
789
830
|
COORDINATOR_ROLE = "COO";
|
|
790
831
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
832
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
// src/lib/database-adapter.ts
|
|
837
|
+
import os5 from "os";
|
|
838
|
+
import path6 from "path";
|
|
839
|
+
import { createRequire } from "module";
|
|
840
|
+
import { pathToFileURL } from "url";
|
|
841
|
+
function quotedIdentifier(identifier) {
|
|
842
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
843
|
+
}
|
|
844
|
+
function unqualifiedTableName(name) {
|
|
845
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
846
|
+
const parts = raw.split(".");
|
|
847
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
848
|
+
}
|
|
849
|
+
function stripTrailingSemicolon(sql) {
|
|
850
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
851
|
+
}
|
|
852
|
+
function appendClause(sql, clause) {
|
|
853
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
854
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
855
|
+
if (!returningMatch) {
|
|
856
|
+
return `${trimmed}${clause}`;
|
|
857
|
+
}
|
|
858
|
+
const idx = returningMatch.index;
|
|
859
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
860
|
+
}
|
|
861
|
+
function normalizeStatement(stmt) {
|
|
862
|
+
if (typeof stmt === "string") {
|
|
863
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
864
|
+
}
|
|
865
|
+
const sql = stmt.sql;
|
|
866
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
867
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
868
|
+
}
|
|
869
|
+
return { kind: "named", sql, args: stmt.args };
|
|
870
|
+
}
|
|
871
|
+
function rewriteBooleanLiterals(sql) {
|
|
872
|
+
let out = sql;
|
|
873
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
874
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
875
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
876
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
877
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
878
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
879
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
880
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
881
|
+
}
|
|
882
|
+
return out;
|
|
883
|
+
}
|
|
884
|
+
function rewriteInsertOrIgnore(sql) {
|
|
885
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
886
|
+
return sql;
|
|
887
|
+
}
|
|
888
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
889
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
890
|
+
}
|
|
891
|
+
function rewriteInsertOrReplace(sql) {
|
|
892
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
893
|
+
if (!match) {
|
|
894
|
+
return sql;
|
|
895
|
+
}
|
|
896
|
+
const rawTable = match[1];
|
|
897
|
+
const rawColumns = match[2];
|
|
898
|
+
const remainder = match[3];
|
|
899
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
900
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
901
|
+
if (!conflictKeys?.length) {
|
|
902
|
+
return sql;
|
|
903
|
+
}
|
|
904
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
905
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
906
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
907
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
908
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
909
|
+
}
|
|
910
|
+
function rewriteSql(sql) {
|
|
911
|
+
let out = sql;
|
|
912
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
913
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
914
|
+
out = rewriteBooleanLiterals(out);
|
|
915
|
+
out = rewriteInsertOrReplace(out);
|
|
916
|
+
out = rewriteInsertOrIgnore(out);
|
|
917
|
+
return stripTrailingSemicolon(out);
|
|
918
|
+
}
|
|
919
|
+
function toBoolean(value) {
|
|
920
|
+
if (value === null || value === void 0) return value;
|
|
921
|
+
if (typeof value === "boolean") return value;
|
|
922
|
+
if (typeof value === "number") return value !== 0;
|
|
923
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
924
|
+
if (typeof value === "string") {
|
|
925
|
+
const normalized = value.trim().toLowerCase();
|
|
926
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
927
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
928
|
+
}
|
|
929
|
+
return Boolean(value);
|
|
930
|
+
}
|
|
931
|
+
function countQuestionMarks(sql, end) {
|
|
932
|
+
let count = 0;
|
|
933
|
+
let inSingle = false;
|
|
934
|
+
let inDouble = false;
|
|
935
|
+
let inLineComment = false;
|
|
936
|
+
let inBlockComment = false;
|
|
937
|
+
for (let i = 0; i < end; i++) {
|
|
938
|
+
const ch = sql[i];
|
|
939
|
+
const next = sql[i + 1];
|
|
940
|
+
if (inLineComment) {
|
|
941
|
+
if (ch === "\n") inLineComment = false;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (inBlockComment) {
|
|
945
|
+
if (ch === "*" && next === "/") {
|
|
946
|
+
inBlockComment = false;
|
|
947
|
+
i += 1;
|
|
948
|
+
}
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
952
|
+
inLineComment = true;
|
|
953
|
+
i += 1;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
957
|
+
inBlockComment = true;
|
|
958
|
+
i += 1;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
962
|
+
inSingle = !inSingle;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
966
|
+
inDouble = !inDouble;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
970
|
+
count += 1;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return count;
|
|
974
|
+
}
|
|
975
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
976
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
977
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
978
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
979
|
+
for (const match of sql.matchAll(pattern)) {
|
|
980
|
+
const matchText = match[0];
|
|
981
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
982
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return indexes;
|
|
986
|
+
}
|
|
987
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
988
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
989
|
+
if (!match) return;
|
|
990
|
+
const rawTable = match[1];
|
|
991
|
+
const rawColumns = match[2];
|
|
992
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
993
|
+
if (!boolColumns?.size) return;
|
|
994
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
995
|
+
for (const [index, column] of columns.entries()) {
|
|
996
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
997
|
+
args[index] = toBoolean(args[index]);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1002
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1003
|
+
if (!match) return;
|
|
1004
|
+
const rawTable = match[1];
|
|
1005
|
+
const setClause = match[2];
|
|
1006
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1007
|
+
if (!boolColumns?.size) return;
|
|
1008
|
+
const assignments = setClause.split(",");
|
|
1009
|
+
let placeholderIndex = 0;
|
|
1010
|
+
for (const assignment of assignments) {
|
|
1011
|
+
if (!assignment.includes("?")) continue;
|
|
1012
|
+
placeholderIndex += 1;
|
|
1013
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1014
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1015
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function coerceBooleanArgs(sql, args) {
|
|
1020
|
+
const nextArgs = [...args];
|
|
1021
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1022
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1023
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1024
|
+
for (const index of placeholderIndexes) {
|
|
1025
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1026
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return nextArgs;
|
|
1030
|
+
}
|
|
1031
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1032
|
+
let out = "";
|
|
1033
|
+
let placeholder = 0;
|
|
1034
|
+
let inSingle = false;
|
|
1035
|
+
let inDouble = false;
|
|
1036
|
+
let inLineComment = false;
|
|
1037
|
+
let inBlockComment = false;
|
|
1038
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1039
|
+
const ch = sql[i];
|
|
1040
|
+
const next = sql[i + 1];
|
|
1041
|
+
if (inLineComment) {
|
|
1042
|
+
out += ch;
|
|
1043
|
+
if (ch === "\n") inLineComment = false;
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (inBlockComment) {
|
|
1047
|
+
out += ch;
|
|
1048
|
+
if (ch === "*" && next === "/") {
|
|
1049
|
+
out += next;
|
|
1050
|
+
inBlockComment = false;
|
|
1051
|
+
i += 1;
|
|
1052
|
+
}
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1056
|
+
out += ch + next;
|
|
1057
|
+
inLineComment = true;
|
|
1058
|
+
i += 1;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1062
|
+
out += ch + next;
|
|
1063
|
+
inBlockComment = true;
|
|
1064
|
+
i += 1;
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1068
|
+
inSingle = !inSingle;
|
|
1069
|
+
out += ch;
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1073
|
+
inDouble = !inDouble;
|
|
1074
|
+
out += ch;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1078
|
+
placeholder += 1;
|
|
1079
|
+
out += `$${placeholder}`;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
out += ch;
|
|
1083
|
+
}
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
function translateStatementForPostgres(stmt) {
|
|
1087
|
+
const normalized = normalizeStatement(stmt);
|
|
1088
|
+
if (normalized.kind === "named") {
|
|
1089
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1090
|
+
}
|
|
1091
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1092
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1093
|
+
return {
|
|
1094
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1095
|
+
args: coercedArgs
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function shouldBypassPostgres(stmt) {
|
|
1099
|
+
const normalized = normalizeStatement(stmt);
|
|
1100
|
+
if (normalized.kind === "named") {
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1104
|
+
}
|
|
1105
|
+
function shouldFallbackOnError(error) {
|
|
1106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1108
|
+
}
|
|
1109
|
+
function isReadQuery(sql) {
|
|
1110
|
+
const trimmed = sql.trimStart();
|
|
1111
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1112
|
+
}
|
|
1113
|
+
function buildRow(row, columns) {
|
|
1114
|
+
const values = columns.map((column) => row[column]);
|
|
1115
|
+
return Object.assign(values, row);
|
|
1116
|
+
}
|
|
1117
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1118
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1119
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1120
|
+
return {
|
|
1121
|
+
columns,
|
|
1122
|
+
columnTypes: columns.map(() => ""),
|
|
1123
|
+
rows: resultRows,
|
|
1124
|
+
rowsAffected,
|
|
1125
|
+
lastInsertRowid: void 0,
|
|
1126
|
+
toJSON() {
|
|
1127
|
+
return {
|
|
1128
|
+
columns,
|
|
1129
|
+
columnTypes: columns.map(() => ""),
|
|
1130
|
+
rows,
|
|
1131
|
+
rowsAffected,
|
|
1132
|
+
lastInsertRowid: void 0
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
async function loadPrismaClient() {
|
|
1138
|
+
if (!prismaClientPromise) {
|
|
1139
|
+
prismaClientPromise = (async () => {
|
|
1140
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1141
|
+
if (explicitPath) {
|
|
1142
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1143
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1144
|
+
if (!PrismaClient2) {
|
|
1145
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1146
|
+
}
|
|
1147
|
+
return new PrismaClient2();
|
|
1148
|
+
}
|
|
1149
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
|
|
1150
|
+
const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1151
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1152
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1153
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1154
|
+
if (!PrismaClient) {
|
|
1155
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1156
|
+
}
|
|
1157
|
+
return new PrismaClient();
|
|
1158
|
+
})();
|
|
1159
|
+
}
|
|
1160
|
+
return prismaClientPromise;
|
|
1161
|
+
}
|
|
1162
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1163
|
+
if (!compatibilityBootstrapPromise) {
|
|
1164
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1165
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1166
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1167
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1168
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1169
|
+
relation
|
|
1170
|
+
);
|
|
1171
|
+
if (!rows[0]?.regclass) {
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
await prisma.$executeRawUnsafe(
|
|
1175
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
})();
|
|
1179
|
+
}
|
|
1180
|
+
return compatibilityBootstrapPromise;
|
|
1181
|
+
}
|
|
1182
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1183
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1184
|
+
if (isReadQuery(translated.sql)) {
|
|
1185
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1186
|
+
translated.sql,
|
|
1187
|
+
...translated.args
|
|
1188
|
+
);
|
|
1189
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1190
|
+
}
|
|
1191
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1192
|
+
return buildResultSet([], rowsAffected);
|
|
1193
|
+
}
|
|
1194
|
+
function splitSqlStatements(sql) {
|
|
1195
|
+
const parts = [];
|
|
1196
|
+
let current = "";
|
|
1197
|
+
let inSingle = false;
|
|
1198
|
+
let inDouble = false;
|
|
1199
|
+
let inLineComment = false;
|
|
1200
|
+
let inBlockComment = false;
|
|
1201
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1202
|
+
const ch = sql[i];
|
|
1203
|
+
const next = sql[i + 1];
|
|
1204
|
+
if (inLineComment) {
|
|
1205
|
+
current += ch;
|
|
1206
|
+
if (ch === "\n") inLineComment = false;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (inBlockComment) {
|
|
1210
|
+
current += ch;
|
|
1211
|
+
if (ch === "*" && next === "/") {
|
|
1212
|
+
current += next;
|
|
1213
|
+
inBlockComment = false;
|
|
1214
|
+
i += 1;
|
|
1215
|
+
}
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1219
|
+
current += ch + next;
|
|
1220
|
+
inLineComment = true;
|
|
1221
|
+
i += 1;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1225
|
+
current += ch + next;
|
|
1226
|
+
inBlockComment = true;
|
|
1227
|
+
i += 1;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1231
|
+
inSingle = !inSingle;
|
|
1232
|
+
current += ch;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1236
|
+
inDouble = !inDouble;
|
|
1237
|
+
current += ch;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1241
|
+
if (current.trim()) {
|
|
1242
|
+
parts.push(current.trim());
|
|
1243
|
+
}
|
|
1244
|
+
current = "";
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
current += ch;
|
|
1248
|
+
}
|
|
1249
|
+
if (current.trim()) {
|
|
1250
|
+
parts.push(current.trim());
|
|
1251
|
+
}
|
|
1252
|
+
return parts;
|
|
1253
|
+
}
|
|
1254
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1255
|
+
const prisma = await loadPrismaClient();
|
|
1256
|
+
await ensureCompatibilityViews(prisma);
|
|
1257
|
+
let closed = false;
|
|
1258
|
+
let adapter;
|
|
1259
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1260
|
+
if (!fallbackClient) {
|
|
1261
|
+
if (error) throw error;
|
|
1262
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1263
|
+
}
|
|
1264
|
+
if (error) {
|
|
1265
|
+
process.stderr.write(
|
|
1266
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1267
|
+
`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
return fallbackClient.execute(stmt);
|
|
1271
|
+
};
|
|
1272
|
+
adapter = {
|
|
1273
|
+
async execute(stmt) {
|
|
1274
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1275
|
+
return fallbackExecute(stmt);
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
if (shouldFallbackOnError(error)) {
|
|
1281
|
+
return fallbackExecute(stmt, error);
|
|
1282
|
+
}
|
|
1283
|
+
throw error;
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
async batch(stmts, mode) {
|
|
1287
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1288
|
+
if (!fallbackClient) {
|
|
1289
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1290
|
+
}
|
|
1291
|
+
return fallbackClient.batch(stmts, mode);
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
if (prisma.$transaction) {
|
|
1295
|
+
return await prisma.$transaction(async (tx) => {
|
|
1296
|
+
const results2 = [];
|
|
1297
|
+
for (const stmt of stmts) {
|
|
1298
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1299
|
+
}
|
|
1300
|
+
return results2;
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
const results = [];
|
|
1304
|
+
for (const stmt of stmts) {
|
|
1305
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1306
|
+
}
|
|
1307
|
+
return results;
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1310
|
+
process.stderr.write(
|
|
1311
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1312
|
+
`
|
|
1313
|
+
);
|
|
1314
|
+
return fallbackClient.batch(stmts, mode);
|
|
1315
|
+
}
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
async migrate(stmts) {
|
|
1320
|
+
if (fallbackClient) {
|
|
1321
|
+
return fallbackClient.migrate(stmts);
|
|
1322
|
+
}
|
|
1323
|
+
return adapter.batch(stmts, "deferred");
|
|
1324
|
+
},
|
|
1325
|
+
async transaction(mode) {
|
|
1326
|
+
if (!fallbackClient) {
|
|
1327
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1328
|
+
}
|
|
1329
|
+
return fallbackClient.transaction(mode);
|
|
1330
|
+
},
|
|
1331
|
+
async executeMultiple(sql) {
|
|
1332
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1333
|
+
return fallbackClient.executeMultiple(sql);
|
|
1334
|
+
}
|
|
1335
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1336
|
+
await adapter.execute(statement);
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
async sync() {
|
|
1340
|
+
if (fallbackClient) {
|
|
1341
|
+
return fallbackClient.sync();
|
|
1342
|
+
}
|
|
1343
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1344
|
+
},
|
|
1345
|
+
close() {
|
|
1346
|
+
closed = true;
|
|
1347
|
+
prismaClientPromise = null;
|
|
1348
|
+
compatibilityBootstrapPromise = null;
|
|
1349
|
+
void prisma.$disconnect?.();
|
|
1350
|
+
},
|
|
1351
|
+
get closed() {
|
|
1352
|
+
return closed;
|
|
1353
|
+
},
|
|
1354
|
+
get protocol() {
|
|
1355
|
+
return "prisma-postgres";
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
return adapter;
|
|
1359
|
+
}
|
|
1360
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1361
|
+
var init_database_adapter = __esm({
|
|
1362
|
+
"src/lib/database-adapter.ts"() {
|
|
1363
|
+
"use strict";
|
|
1364
|
+
VIEW_MAPPINGS = [
|
|
1365
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1366
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1367
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1368
|
+
{ view: "entities", source: "memory.entities" },
|
|
1369
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1370
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1371
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1372
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1373
|
+
{ view: "messages", source: "memory.messages" },
|
|
1374
|
+
{ view: "users", source: "wiki.users" },
|
|
1375
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1376
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1377
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1378
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1379
|
+
];
|
|
1380
|
+
UPSERT_KEYS = {
|
|
1381
|
+
memories: ["id"],
|
|
1382
|
+
tasks: ["id"],
|
|
1383
|
+
behaviors: ["id"],
|
|
1384
|
+
entities: ["id"],
|
|
1385
|
+
relationships: ["id"],
|
|
1386
|
+
entity_aliases: ["alias"],
|
|
1387
|
+
notifications: ["id"],
|
|
1388
|
+
messages: ["id"],
|
|
1389
|
+
users: ["id"],
|
|
1390
|
+
workspaces: ["id"],
|
|
1391
|
+
workspace_users: ["id"],
|
|
1392
|
+
documents: ["id"],
|
|
1393
|
+
chats: ["id"]
|
|
1394
|
+
};
|
|
1395
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1396
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1397
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1398
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1399
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1400
|
+
};
|
|
1401
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1402
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1403
|
+
);
|
|
1404
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1405
|
+
/\bPRAGMA\b/i,
|
|
1406
|
+
/\bsqlite_master\b/i,
|
|
1407
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1408
|
+
/\bMATCH\b/i,
|
|
1409
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1410
|
+
/\bjson_extract\s*\(/i,
|
|
1411
|
+
/\bjulianday\s*\(/i,
|
|
1412
|
+
/\bstrftime\s*\(/i,
|
|
1413
|
+
/\blast_insert_rowid\s*\(/i
|
|
1414
|
+
];
|
|
1415
|
+
prismaClientPromise = null;
|
|
1416
|
+
compatibilityBootstrapPromise = null;
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
// src/lib/daemon-auth.ts
|
|
1421
|
+
import crypto from "crypto";
|
|
1422
|
+
import path7 from "path";
|
|
1423
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1424
|
+
function normalizeToken(token) {
|
|
1425
|
+
if (!token) return null;
|
|
1426
|
+
const trimmed = token.trim();
|
|
1427
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1428
|
+
}
|
|
1429
|
+
function readDaemonToken() {
|
|
1430
|
+
try {
|
|
1431
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1432
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function ensureDaemonToken(seed) {
|
|
1438
|
+
const existing = readDaemonToken();
|
|
1439
|
+
if (existing) return existing;
|
|
1440
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1441
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1442
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1443
|
+
`, "utf8");
|
|
1444
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1445
|
+
return token;
|
|
1446
|
+
}
|
|
1447
|
+
var DAEMON_TOKEN_PATH;
|
|
1448
|
+
var init_daemon_auth = __esm({
|
|
1449
|
+
"src/lib/daemon-auth.ts"() {
|
|
1450
|
+
"use strict";
|
|
1451
|
+
init_config();
|
|
1452
|
+
init_secure_files();
|
|
1453
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
791
1454
|
}
|
|
792
1455
|
});
|
|
793
1456
|
|
|
794
1457
|
// src/lib/exe-daemon-client.ts
|
|
795
1458
|
import net from "net";
|
|
796
|
-
import
|
|
1459
|
+
import os6 from "os";
|
|
797
1460
|
import { spawn } from "child_process";
|
|
798
1461
|
import { randomUUID } from "crypto";
|
|
799
|
-
import { existsSync as
|
|
800
|
-
import
|
|
1462
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1463
|
+
import path8 from "path";
|
|
801
1464
|
import { fileURLToPath } from "url";
|
|
802
1465
|
function handleData(chunk) {
|
|
803
1466
|
_buffer += chunk.toString();
|
|
@@ -825,9 +1488,9 @@ function handleData(chunk) {
|
|
|
825
1488
|
}
|
|
826
1489
|
}
|
|
827
1490
|
function cleanupStaleFiles() {
|
|
828
|
-
if (
|
|
1491
|
+
if (existsSync8(PID_PATH)) {
|
|
829
1492
|
try {
|
|
830
|
-
const pid = parseInt(
|
|
1493
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
831
1494
|
if (pid > 0) {
|
|
832
1495
|
try {
|
|
833
1496
|
process.kill(pid, 0);
|
|
@@ -848,17 +1511,17 @@ function cleanupStaleFiles() {
|
|
|
848
1511
|
}
|
|
849
1512
|
}
|
|
850
1513
|
function findPackageRoot() {
|
|
851
|
-
let dir =
|
|
852
|
-
const { root } =
|
|
1514
|
+
let dir = path8.dirname(fileURLToPath(import.meta.url));
|
|
1515
|
+
const { root } = path8.parse(dir);
|
|
853
1516
|
while (dir !== root) {
|
|
854
|
-
if (
|
|
855
|
-
dir =
|
|
1517
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1518
|
+
dir = path8.dirname(dir);
|
|
856
1519
|
}
|
|
857
1520
|
return null;
|
|
858
1521
|
}
|
|
859
1522
|
function spawnDaemon() {
|
|
860
|
-
const freeGB =
|
|
861
|
-
const totalGB =
|
|
1523
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1524
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
862
1525
|
if (totalGB <= 8) {
|
|
863
1526
|
process.stderr.write(
|
|
864
1527
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -878,16 +1541,17 @@ function spawnDaemon() {
|
|
|
878
1541
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
879
1542
|
return;
|
|
880
1543
|
}
|
|
881
|
-
const daemonPath =
|
|
882
|
-
if (!
|
|
1544
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1545
|
+
if (!existsSync8(daemonPath)) {
|
|
883
1546
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
884
1547
|
`);
|
|
885
1548
|
return;
|
|
886
1549
|
}
|
|
887
1550
|
const resolvedPath = daemonPath;
|
|
1551
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
888
1552
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
889
1553
|
`);
|
|
890
|
-
const logPath =
|
|
1554
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
891
1555
|
let stderrFd = "ignore";
|
|
892
1556
|
try {
|
|
893
1557
|
stderrFd = openSync(logPath, "a");
|
|
@@ -905,7 +1569,8 @@ function spawnDaemon() {
|
|
|
905
1569
|
TMUX_PANE: void 0,
|
|
906
1570
|
// Prevents resolveExeSession() from scoping to one session
|
|
907
1571
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
908
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1572
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1573
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
909
1574
|
}
|
|
910
1575
|
});
|
|
911
1576
|
child.unref();
|
|
@@ -1012,13 +1677,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1012
1677
|
return;
|
|
1013
1678
|
}
|
|
1014
1679
|
const id = randomUUID();
|
|
1680
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1015
1681
|
const timer = setTimeout(() => {
|
|
1016
1682
|
_pending.delete(id);
|
|
1017
1683
|
resolve({ error: "Request timeout" });
|
|
1018
1684
|
}, timeoutMs);
|
|
1019
1685
|
_pending.set(id, { resolve, timer });
|
|
1020
1686
|
try {
|
|
1021
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1687
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1022
1688
|
} catch {
|
|
1023
1689
|
clearTimeout(timer);
|
|
1024
1690
|
_pending.delete(id);
|
|
@@ -1029,17 +1695,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1029
1695
|
function isClientConnected() {
|
|
1030
1696
|
return _connected;
|
|
1031
1697
|
}
|
|
1032
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1698
|
+
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;
|
|
1033
1699
|
var init_exe_daemon_client = __esm({
|
|
1034
1700
|
"src/lib/exe-daemon-client.ts"() {
|
|
1035
1701
|
"use strict";
|
|
1036
1702
|
init_config();
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1703
|
+
init_daemon_auth();
|
|
1704
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
1705
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
1706
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1040
1707
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1041
1708
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1042
1709
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1710
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1043
1711
|
_socket = null;
|
|
1044
1712
|
_connected = false;
|
|
1045
1713
|
_buffer = "";
|
|
@@ -1118,7 +1786,7 @@ __export(db_daemon_client_exports, {
|
|
|
1118
1786
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1119
1787
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1120
1788
|
});
|
|
1121
|
-
function
|
|
1789
|
+
function normalizeStatement2(stmt) {
|
|
1122
1790
|
if (typeof stmt === "string") {
|
|
1123
1791
|
return { sql: stmt, args: [] };
|
|
1124
1792
|
}
|
|
@@ -1142,7 +1810,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1142
1810
|
if (!_useDaemon || !isClientConnected()) {
|
|
1143
1811
|
return fallbackClient.execute(stmt);
|
|
1144
1812
|
}
|
|
1145
|
-
const { sql, args } =
|
|
1813
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1146
1814
|
const response = await sendDaemonRequest({
|
|
1147
1815
|
type: "db-execute",
|
|
1148
1816
|
sql,
|
|
@@ -1167,7 +1835,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1167
1835
|
if (!_useDaemon || !isClientConnected()) {
|
|
1168
1836
|
return fallbackClient.batch(stmts, mode);
|
|
1169
1837
|
}
|
|
1170
|
-
const statements = stmts.map(
|
|
1838
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1171
1839
|
const response = await sendDaemonRequest({
|
|
1172
1840
|
type: "db-batch",
|
|
1173
1841
|
statements,
|
|
@@ -1262,6 +1930,18 @@ __export(database_exports, {
|
|
|
1262
1930
|
});
|
|
1263
1931
|
import { createClient } from "@libsql/client";
|
|
1264
1932
|
async function initDatabase(config) {
|
|
1933
|
+
if (_walCheckpointTimer) {
|
|
1934
|
+
clearInterval(_walCheckpointTimer);
|
|
1935
|
+
_walCheckpointTimer = null;
|
|
1936
|
+
}
|
|
1937
|
+
if (_daemonClient) {
|
|
1938
|
+
_daemonClient.close();
|
|
1939
|
+
_daemonClient = null;
|
|
1940
|
+
}
|
|
1941
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1942
|
+
_adapterClient.close();
|
|
1943
|
+
}
|
|
1944
|
+
_adapterClient = null;
|
|
1265
1945
|
if (_client) {
|
|
1266
1946
|
_client.close();
|
|
1267
1947
|
_client = null;
|
|
@@ -1275,6 +1955,7 @@ async function initDatabase(config) {
|
|
|
1275
1955
|
}
|
|
1276
1956
|
_client = createClient(opts);
|
|
1277
1957
|
_resilientClient = wrapWithRetry(_client);
|
|
1958
|
+
_adapterClient = _resilientClient;
|
|
1278
1959
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1279
1960
|
});
|
|
1280
1961
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1285,14 +1966,20 @@ async function initDatabase(config) {
|
|
|
1285
1966
|
});
|
|
1286
1967
|
}, 3e4);
|
|
1287
1968
|
_walCheckpointTimer.unref();
|
|
1969
|
+
if (process.env.DATABASE_URL) {
|
|
1970
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1971
|
+
}
|
|
1288
1972
|
}
|
|
1289
1973
|
function isInitialized() {
|
|
1290
|
-
return _client !== null;
|
|
1974
|
+
return _adapterClient !== null || _client !== null;
|
|
1291
1975
|
}
|
|
1292
1976
|
function getClient() {
|
|
1293
|
-
if (!
|
|
1977
|
+
if (!_adapterClient) {
|
|
1294
1978
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1295
1979
|
}
|
|
1980
|
+
if (process.env.DATABASE_URL) {
|
|
1981
|
+
return _adapterClient;
|
|
1982
|
+
}
|
|
1296
1983
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1297
1984
|
return _resilientClient;
|
|
1298
1985
|
}
|
|
@@ -1302,6 +1989,7 @@ function getClient() {
|
|
|
1302
1989
|
return _resilientClient;
|
|
1303
1990
|
}
|
|
1304
1991
|
async function initDaemonClient() {
|
|
1992
|
+
if (process.env.DATABASE_URL) return;
|
|
1305
1993
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1306
1994
|
if (!_resilientClient) return;
|
|
1307
1995
|
try {
|
|
@@ -1598,6 +2286,7 @@ async function ensureSchema() {
|
|
|
1598
2286
|
project TEXT NOT NULL,
|
|
1599
2287
|
summary TEXT NOT NULL,
|
|
1600
2288
|
task_file TEXT,
|
|
2289
|
+
session_scope TEXT,
|
|
1601
2290
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1602
2291
|
created_at TEXT NOT NULL
|
|
1603
2292
|
);
|
|
@@ -1606,7 +2295,7 @@ async function ensureSchema() {
|
|
|
1606
2295
|
ON notifications(read);
|
|
1607
2296
|
|
|
1608
2297
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1609
|
-
ON notifications(agent_id);
|
|
2298
|
+
ON notifications(agent_id, session_scope);
|
|
1610
2299
|
|
|
1611
2300
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1612
2301
|
ON notifications(task_file);
|
|
@@ -1644,6 +2333,7 @@ async function ensureSchema() {
|
|
|
1644
2333
|
target_agent TEXT NOT NULL,
|
|
1645
2334
|
target_project TEXT,
|
|
1646
2335
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2336
|
+
session_scope TEXT,
|
|
1647
2337
|
content TEXT NOT NULL,
|
|
1648
2338
|
priority TEXT DEFAULT 'normal',
|
|
1649
2339
|
status TEXT DEFAULT 'pending',
|
|
@@ -1657,10 +2347,31 @@ async function ensureSchema() {
|
|
|
1657
2347
|
);
|
|
1658
2348
|
|
|
1659
2349
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1660
|
-
ON messages(target_agent, status);
|
|
2350
|
+
ON messages(target_agent, session_scope, status);
|
|
1661
2351
|
|
|
1662
2352
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1663
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2353
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2354
|
+
`);
|
|
2355
|
+
try {
|
|
2356
|
+
await client.execute({
|
|
2357
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2358
|
+
args: []
|
|
2359
|
+
});
|
|
2360
|
+
} catch {
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
await client.execute({
|
|
2364
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2365
|
+
args: []
|
|
2366
|
+
});
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
await client.executeMultiple(`
|
|
2370
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2371
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2372
|
+
|
|
2373
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2374
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1664
2375
|
`);
|
|
1665
2376
|
try {
|
|
1666
2377
|
await client.execute({
|
|
@@ -2244,46 +2955,66 @@ async function ensureSchema() {
|
|
|
2244
2955
|
} catch {
|
|
2245
2956
|
}
|
|
2246
2957
|
}
|
|
2958
|
+
try {
|
|
2959
|
+
await client.execute({
|
|
2960
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2961
|
+
args: []
|
|
2962
|
+
});
|
|
2963
|
+
} catch {
|
|
2964
|
+
}
|
|
2247
2965
|
}
|
|
2248
2966
|
async function disposeDatabase() {
|
|
2967
|
+
if (_walCheckpointTimer) {
|
|
2968
|
+
clearInterval(_walCheckpointTimer);
|
|
2969
|
+
_walCheckpointTimer = null;
|
|
2970
|
+
}
|
|
2249
2971
|
if (_daemonClient) {
|
|
2250
2972
|
_daemonClient.close();
|
|
2251
2973
|
_daemonClient = null;
|
|
2252
2974
|
}
|
|
2975
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2976
|
+
_adapterClient.close();
|
|
2977
|
+
}
|
|
2978
|
+
_adapterClient = null;
|
|
2253
2979
|
if (_client) {
|
|
2254
2980
|
_client.close();
|
|
2255
2981
|
_client = null;
|
|
2256
2982
|
_resilientClient = null;
|
|
2257
2983
|
}
|
|
2258
2984
|
}
|
|
2259
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2985
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2260
2986
|
var init_database = __esm({
|
|
2261
2987
|
"src/lib/database.ts"() {
|
|
2262
2988
|
"use strict";
|
|
2263
2989
|
init_db_retry();
|
|
2264
2990
|
init_employees();
|
|
2991
|
+
init_database_adapter();
|
|
2265
2992
|
_client = null;
|
|
2266
2993
|
_resilientClient = null;
|
|
2267
2994
|
_walCheckpointTimer = null;
|
|
2268
2995
|
_daemonClient = null;
|
|
2996
|
+
_adapterClient = null;
|
|
2269
2997
|
initTurso = initDatabase;
|
|
2270
2998
|
disposeTurso = disposeDatabase;
|
|
2271
2999
|
}
|
|
2272
3000
|
});
|
|
2273
3001
|
|
|
2274
3002
|
// src/lib/license.ts
|
|
2275
|
-
import { readFileSync as
|
|
3003
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2276
3004
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2277
|
-
import
|
|
3005
|
+
import { createRequire as createRequire2 } from "module";
|
|
3006
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3007
|
+
import os7 from "os";
|
|
3008
|
+
import path9 from "path";
|
|
2278
3009
|
import { jwtVerify, importSPKI } from "jose";
|
|
2279
3010
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2280
3011
|
var init_license = __esm({
|
|
2281
3012
|
"src/lib/license.ts"() {
|
|
2282
3013
|
"use strict";
|
|
2283
3014
|
init_config();
|
|
2284
|
-
LICENSE_PATH =
|
|
2285
|
-
CACHE_PATH =
|
|
2286
|
-
DEVICE_ID_PATH =
|
|
3015
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3016
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3017
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2287
3018
|
PLAN_LIMITS = {
|
|
2288
3019
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2289
3020
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2295,12 +3026,12 @@ var init_license = __esm({
|
|
|
2295
3026
|
});
|
|
2296
3027
|
|
|
2297
3028
|
// src/lib/plan-limits.ts
|
|
2298
|
-
import { readFileSync as
|
|
2299
|
-
import
|
|
3029
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3030
|
+
import path10 from "path";
|
|
2300
3031
|
function getLicenseSync() {
|
|
2301
3032
|
try {
|
|
2302
|
-
if (!
|
|
2303
|
-
const raw = JSON.parse(
|
|
3033
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3034
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2304
3035
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2305
3036
|
const parts = raw.token.split(".");
|
|
2306
3037
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2338,8 +3069,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2338
3069
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2339
3070
|
let count = 0;
|
|
2340
3071
|
try {
|
|
2341
|
-
if (
|
|
2342
|
-
const raw =
|
|
3072
|
+
if (existsSync10(filePath)) {
|
|
3073
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2343
3074
|
const employees = JSON.parse(raw);
|
|
2344
3075
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2345
3076
|
}
|
|
@@ -2368,29 +3099,30 @@ var init_plan_limits = __esm({
|
|
|
2368
3099
|
this.name = "PlanLimitError";
|
|
2369
3100
|
}
|
|
2370
3101
|
};
|
|
2371
|
-
CACHE_PATH2 =
|
|
3102
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2372
3103
|
}
|
|
2373
3104
|
});
|
|
2374
3105
|
|
|
2375
3106
|
// src/lib/notifications.ts
|
|
2376
|
-
import
|
|
2377
|
-
import
|
|
2378
|
-
import
|
|
3107
|
+
import crypto2 from "crypto";
|
|
3108
|
+
import path11 from "path";
|
|
3109
|
+
import os8 from "os";
|
|
2379
3110
|
import {
|
|
2380
|
-
readFileSync as
|
|
3111
|
+
readFileSync as readFileSync10,
|
|
2381
3112
|
readdirSync,
|
|
2382
3113
|
unlinkSync as unlinkSync3,
|
|
2383
|
-
existsSync as
|
|
3114
|
+
existsSync as existsSync11,
|
|
2384
3115
|
rmdirSync
|
|
2385
3116
|
} from "fs";
|
|
2386
3117
|
async function writeNotification(notification) {
|
|
2387
3118
|
try {
|
|
2388
3119
|
const client = getClient();
|
|
2389
|
-
const id =
|
|
3120
|
+
const id = crypto2.randomUUID();
|
|
2390
3121
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3122
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2391
3123
|
await client.execute({
|
|
2392
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2393
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3124
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3125
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2394
3126
|
args: [
|
|
2395
3127
|
id,
|
|
2396
3128
|
notification.agentId,
|
|
@@ -2399,6 +3131,7 @@ async function writeNotification(notification) {
|
|
|
2399
3131
|
notification.project,
|
|
2400
3132
|
notification.summary,
|
|
2401
3133
|
notification.taskFile ?? null,
|
|
3134
|
+
sessionScope,
|
|
2402
3135
|
now
|
|
2403
3136
|
]
|
|
2404
3137
|
});
|
|
@@ -2407,12 +3140,14 @@ async function writeNotification(notification) {
|
|
|
2407
3140
|
`);
|
|
2408
3141
|
}
|
|
2409
3142
|
}
|
|
2410
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3143
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2411
3144
|
try {
|
|
2412
3145
|
const client = getClient();
|
|
3146
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2413
3147
|
await client.execute({
|
|
2414
|
-
sql:
|
|
2415
|
-
|
|
3148
|
+
sql: `UPDATE notifications SET read = 1
|
|
3149
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3150
|
+
args: [taskFile, ...scope.args]
|
|
2416
3151
|
});
|
|
2417
3152
|
} catch {
|
|
2418
3153
|
}
|
|
@@ -2421,11 +3156,12 @@ var init_notifications = __esm({
|
|
|
2421
3156
|
"src/lib/notifications.ts"() {
|
|
2422
3157
|
"use strict";
|
|
2423
3158
|
init_database();
|
|
3159
|
+
init_task_scope();
|
|
2424
3160
|
}
|
|
2425
3161
|
});
|
|
2426
3162
|
|
|
2427
3163
|
// src/lib/session-kill-telemetry.ts
|
|
2428
|
-
import
|
|
3164
|
+
import crypto3 from "crypto";
|
|
2429
3165
|
async function recordSessionKill(input) {
|
|
2430
3166
|
try {
|
|
2431
3167
|
const client = getClient();
|
|
@@ -2435,7 +3171,7 @@ async function recordSessionKill(input) {
|
|
|
2435
3171
|
ticks_idle, estimated_tokens_saved)
|
|
2436
3172
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2437
3173
|
args: [
|
|
2438
|
-
|
|
3174
|
+
crypto3.randomUUID(),
|
|
2439
3175
|
input.sessionName,
|
|
2440
3176
|
input.agentId,
|
|
2441
3177
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2530,12 +3266,12 @@ __export(tasks_crud_exports, {
|
|
|
2530
3266
|
updateTaskStatus: () => updateTaskStatus,
|
|
2531
3267
|
writeCheckpoint: () => writeCheckpoint
|
|
2532
3268
|
});
|
|
2533
|
-
import
|
|
2534
|
-
import
|
|
2535
|
-
import
|
|
3269
|
+
import crypto4 from "crypto";
|
|
3270
|
+
import path12 from "path";
|
|
3271
|
+
import os9 from "os";
|
|
2536
3272
|
import { execSync as execSync4 } from "child_process";
|
|
2537
3273
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2538
|
-
import { existsSync as
|
|
3274
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2539
3275
|
async function writeCheckpoint(input) {
|
|
2540
3276
|
const client = getClient();
|
|
2541
3277
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2651,7 +3387,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2651
3387
|
}
|
|
2652
3388
|
async function createTaskCore(input) {
|
|
2653
3389
|
const client = getClient();
|
|
2654
|
-
const id =
|
|
3390
|
+
const id = crypto4.randomUUID();
|
|
2655
3391
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2656
3392
|
const slug = slugify(input.title);
|
|
2657
3393
|
let earlySessionScope = null;
|
|
@@ -2710,8 +3446,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2710
3446
|
}
|
|
2711
3447
|
if (input.baseDir) {
|
|
2712
3448
|
try {
|
|
2713
|
-
await mkdir3(
|
|
2714
|
-
await mkdir3(
|
|
3449
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3450
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2715
3451
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2716
3452
|
await ensureGitignoreExe(input.baseDir);
|
|
2717
3453
|
} catch {
|
|
@@ -2747,13 +3483,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2747
3483
|
});
|
|
2748
3484
|
if (input.baseDir) {
|
|
2749
3485
|
try {
|
|
2750
|
-
const EXE_OS_DIR =
|
|
2751
|
-
const mdPath =
|
|
2752
|
-
const mdDir =
|
|
2753
|
-
if (!
|
|
3486
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
3487
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
3488
|
+
const mdDir = path12.dirname(mdPath);
|
|
3489
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2754
3490
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2755
3491
|
const mdContent = `# ${input.title}
|
|
2756
3492
|
|
|
3493
|
+
## MANDATORY: When done
|
|
3494
|
+
|
|
3495
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3496
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3497
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3498
|
+
|
|
2757
3499
|
**ID:** ${id}
|
|
2758
3500
|
**Status:** ${initialStatus}
|
|
2759
3501
|
**Priority:** ${input.priority}
|
|
@@ -2767,12 +3509,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2767
3509
|
## Context
|
|
2768
3510
|
|
|
2769
3511
|
${input.context}
|
|
2770
|
-
|
|
2771
|
-
## MANDATORY: When done
|
|
2772
|
-
|
|
2773
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2774
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2775
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2776
3512
|
`;
|
|
2777
3513
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2778
3514
|
} catch (err) {
|
|
@@ -3021,7 +3757,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3021
3757
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3022
3758
|
} catch {
|
|
3023
3759
|
}
|
|
3024
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3760
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3025
3761
|
try {
|
|
3026
3762
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3027
3763
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3050,9 +3786,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3050
3786
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3051
3787
|
}
|
|
3052
3788
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3053
|
-
const archPath =
|
|
3789
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3054
3790
|
try {
|
|
3055
|
-
if (
|
|
3791
|
+
if (existsSync12(archPath)) return;
|
|
3056
3792
|
const template = [
|
|
3057
3793
|
`# ${projectName} \u2014 System Architecture`,
|
|
3058
3794
|
"",
|
|
@@ -3085,10 +3821,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3085
3821
|
}
|
|
3086
3822
|
}
|
|
3087
3823
|
async function ensureGitignoreExe(baseDir) {
|
|
3088
|
-
const gitignorePath =
|
|
3824
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3089
3825
|
try {
|
|
3090
|
-
if (
|
|
3091
|
-
const content =
|
|
3826
|
+
if (existsSync12(gitignorePath)) {
|
|
3827
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3092
3828
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3093
3829
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3094
3830
|
} else {
|
|
@@ -3119,58 +3855,42 @@ var init_tasks_crud = __esm({
|
|
|
3119
3855
|
});
|
|
3120
3856
|
|
|
3121
3857
|
// src/lib/tasks-review.ts
|
|
3122
|
-
import
|
|
3123
|
-
import { existsSync as
|
|
3858
|
+
import path13 from "path";
|
|
3859
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3124
3860
|
async function countPendingReviews(sessionScope) {
|
|
3125
3861
|
const client = getClient();
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
args: [sessionScope]
|
|
3130
|
-
});
|
|
3131
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3132
|
-
}
|
|
3862
|
+
const scope = strictSessionScopeFilter(
|
|
3863
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3864
|
+
);
|
|
3133
3865
|
const result = await client.execute({
|
|
3134
|
-
sql:
|
|
3135
|
-
|
|
3866
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3867
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3868
|
+
args: [...scope.args]
|
|
3136
3869
|
});
|
|
3137
3870
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3138
3871
|
}
|
|
3139
3872
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3140
3873
|
const client = getClient();
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3145
|
-
AND session_scope = ?`,
|
|
3146
|
-
args: [sinceIso, sessionScope]
|
|
3147
|
-
});
|
|
3148
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3149
|
-
}
|
|
3874
|
+
const scope = strictSessionScopeFilter(
|
|
3875
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3876
|
+
);
|
|
3150
3877
|
const result = await client.execute({
|
|
3151
3878
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3152
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3153
|
-
args: [sinceIso]
|
|
3879
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3880
|
+
args: [sinceIso, ...scope.args]
|
|
3154
3881
|
});
|
|
3155
3882
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3156
3883
|
}
|
|
3157
3884
|
async function listPendingReviews(limit, sessionScope) {
|
|
3158
3885
|
const client = getClient();
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
WHERE status = 'needs_review'
|
|
3163
|
-
AND session_scope = ?
|
|
3164
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3165
|
-
args: [sessionScope, limit]
|
|
3166
|
-
});
|
|
3167
|
-
return result2.rows;
|
|
3168
|
-
}
|
|
3886
|
+
const scope = strictSessionScopeFilter(
|
|
3887
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3888
|
+
);
|
|
3169
3889
|
const result = await client.execute({
|
|
3170
3890
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3171
|
-
WHERE status = 'needs_review'
|
|
3891
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3172
3892
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3173
|
-
args: [limit]
|
|
3893
|
+
args: [...scope.args, limit]
|
|
3174
3894
|
});
|
|
3175
3895
|
return result.rows;
|
|
3176
3896
|
}
|
|
@@ -3182,7 +3902,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3182
3902
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3183
3903
|
AND assigned_by = 'system'
|
|
3184
3904
|
AND title LIKE 'Review:%'
|
|
3185
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3905
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3186
3906
|
args: [now]
|
|
3187
3907
|
});
|
|
3188
3908
|
const r1b = await client.execute({
|
|
@@ -3301,11 +4021,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3301
4021
|
);
|
|
3302
4022
|
}
|
|
3303
4023
|
try {
|
|
3304
|
-
const cacheDir =
|
|
3305
|
-
if (
|
|
4024
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4025
|
+
if (existsSync13(cacheDir)) {
|
|
3306
4026
|
for (const f of readdirSync2(cacheDir)) {
|
|
3307
4027
|
if (f.startsWith("review-notified-")) {
|
|
3308
|
-
unlinkSync4(
|
|
4028
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3309
4029
|
}
|
|
3310
4030
|
}
|
|
3311
4031
|
}
|
|
@@ -3322,11 +4042,12 @@ var init_tasks_review = __esm({
|
|
|
3322
4042
|
init_tmux_routing();
|
|
3323
4043
|
init_session_key();
|
|
3324
4044
|
init_state_bus();
|
|
4045
|
+
init_task_scope();
|
|
3325
4046
|
}
|
|
3326
4047
|
});
|
|
3327
4048
|
|
|
3328
4049
|
// src/lib/tasks-chain.ts
|
|
3329
|
-
import
|
|
4050
|
+
import path14 from "path";
|
|
3330
4051
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3331
4052
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3332
4053
|
const client = getClient();
|
|
@@ -3343,7 +4064,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3343
4064
|
});
|
|
3344
4065
|
for (const ur of unblockedRows.rows) {
|
|
3345
4066
|
try {
|
|
3346
|
-
const ubFile =
|
|
4067
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3347
4068
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3348
4069
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3349
4070
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3378,7 +4099,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3378
4099
|
const scScope = sessionScopeFilter();
|
|
3379
4100
|
const remaining = await client.execute({
|
|
3380
4101
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3381
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4102
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3382
4103
|
args: [parentTaskId, ...scScope.args]
|
|
3383
4104
|
});
|
|
3384
4105
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3412,7 +4133,7 @@ var init_tasks_chain = __esm({
|
|
|
3412
4133
|
|
|
3413
4134
|
// src/lib/project-name.ts
|
|
3414
4135
|
import { execSync as execSync5 } from "child_process";
|
|
3415
|
-
import
|
|
4136
|
+
import path15 from "path";
|
|
3416
4137
|
function getProjectName(cwd) {
|
|
3417
4138
|
const dir = cwd ?? process.cwd();
|
|
3418
4139
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3425,7 +4146,7 @@ function getProjectName(cwd) {
|
|
|
3425
4146
|
timeout: 2e3,
|
|
3426
4147
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3427
4148
|
}).trim();
|
|
3428
|
-
repoRoot =
|
|
4149
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
3429
4150
|
} catch {
|
|
3430
4151
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3431
4152
|
cwd: dir,
|
|
@@ -3434,11 +4155,11 @@ function getProjectName(cwd) {
|
|
|
3434
4155
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3435
4156
|
}).trim();
|
|
3436
4157
|
}
|
|
3437
|
-
_cached2 =
|
|
4158
|
+
_cached2 = path15.basename(repoRoot);
|
|
3438
4159
|
_cachedCwd = dir;
|
|
3439
4160
|
return _cached2;
|
|
3440
4161
|
} catch {
|
|
3441
|
-
_cached2 =
|
|
4162
|
+
_cached2 = path15.basename(dir);
|
|
3442
4163
|
_cachedCwd = dir;
|
|
3443
4164
|
return _cached2;
|
|
3444
4165
|
}
|
|
@@ -3581,10 +4302,10 @@ var init_tasks_notify = __esm({
|
|
|
3581
4302
|
});
|
|
3582
4303
|
|
|
3583
4304
|
// src/lib/behaviors.ts
|
|
3584
|
-
import
|
|
4305
|
+
import crypto5 from "crypto";
|
|
3585
4306
|
async function storeBehavior(opts) {
|
|
3586
4307
|
const client = getClient();
|
|
3587
|
-
const id =
|
|
4308
|
+
const id = crypto5.randomUUID();
|
|
3588
4309
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3589
4310
|
await client.execute({
|
|
3590
4311
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -3613,7 +4334,7 @@ __export(skill_learning_exports, {
|
|
|
3613
4334
|
storeTrajectory: () => storeTrajectory,
|
|
3614
4335
|
sweepTrajectories: () => sweepTrajectories
|
|
3615
4336
|
});
|
|
3616
|
-
import
|
|
4337
|
+
import crypto6 from "crypto";
|
|
3617
4338
|
async function extractTrajectory(taskId, agentId) {
|
|
3618
4339
|
const client = getClient();
|
|
3619
4340
|
const result = await client.execute({
|
|
@@ -3642,11 +4363,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3642
4363
|
return signature;
|
|
3643
4364
|
}
|
|
3644
4365
|
function hashSignature(signature) {
|
|
3645
|
-
return
|
|
4366
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3646
4367
|
}
|
|
3647
4368
|
async function storeTrajectory(opts) {
|
|
3648
4369
|
const client = getClient();
|
|
3649
|
-
const id =
|
|
4370
|
+
const id = crypto6.randomUUID();
|
|
3650
4371
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3651
4372
|
const signatureHash = hashSignature(opts.signature);
|
|
3652
4373
|
await client.execute({
|
|
@@ -3911,8 +4632,8 @@ __export(tasks_exports, {
|
|
|
3911
4632
|
updateTaskStatus: () => updateTaskStatus,
|
|
3912
4633
|
writeCheckpoint: () => writeCheckpoint
|
|
3913
4634
|
});
|
|
3914
|
-
import
|
|
3915
|
-
import { writeFileSync as
|
|
4635
|
+
import path16 from "path";
|
|
4636
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3916
4637
|
async function createTask(input) {
|
|
3917
4638
|
const result = await createTaskCore(input);
|
|
3918
4639
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3931,12 +4652,12 @@ async function updateTask(input) {
|
|
|
3931
4652
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3932
4653
|
try {
|
|
3933
4654
|
const agent = String(row.assigned_to);
|
|
3934
|
-
const cacheDir =
|
|
3935
|
-
const cachePath =
|
|
4655
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4656
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
3936
4657
|
if (input.status === "in_progress") {
|
|
3937
4658
|
mkdirSync5(cacheDir, { recursive: true });
|
|
3938
|
-
|
|
3939
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4659
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4660
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3940
4661
|
try {
|
|
3941
4662
|
unlinkSync5(cachePath);
|
|
3942
4663
|
} catch {
|
|
@@ -3944,10 +4665,10 @@ async function updateTask(input) {
|
|
|
3944
4665
|
}
|
|
3945
4666
|
} catch {
|
|
3946
4667
|
}
|
|
3947
|
-
if (input.status === "done") {
|
|
4668
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3948
4669
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3949
4670
|
}
|
|
3950
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4671
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3951
4672
|
try {
|
|
3952
4673
|
const client = getClient();
|
|
3953
4674
|
const taskTitle = String(row.title);
|
|
@@ -3963,7 +4684,7 @@ async function updateTask(input) {
|
|
|
3963
4684
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3964
4685
|
try {
|
|
3965
4686
|
const draftClient = getClient();
|
|
3966
|
-
if (input.status === "done") {
|
|
4687
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3967
4688
|
await draftClient.execute({
|
|
3968
4689
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3969
4690
|
args: [assignedAgent]
|
|
@@ -3980,7 +4701,7 @@ async function updateTask(input) {
|
|
|
3980
4701
|
try {
|
|
3981
4702
|
const client = getClient();
|
|
3982
4703
|
const cascaded = await client.execute({
|
|
3983
|
-
sql: `UPDATE tasks SET status = '
|
|
4704
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3984
4705
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3985
4706
|
args: [now, taskId]
|
|
3986
4707
|
});
|
|
@@ -3993,14 +4714,14 @@ async function updateTask(input) {
|
|
|
3993
4714
|
} catch {
|
|
3994
4715
|
}
|
|
3995
4716
|
}
|
|
3996
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4717
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3997
4718
|
if (isTerminal) {
|
|
3998
4719
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3999
4720
|
if (!isCoordinator) {
|
|
4000
4721
|
notifyTaskDone();
|
|
4001
4722
|
}
|
|
4002
4723
|
await markTaskNotificationsRead(taskFile);
|
|
4003
|
-
if (input.status === "done") {
|
|
4724
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4004
4725
|
try {
|
|
4005
4726
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4006
4727
|
} catch {
|
|
@@ -4020,7 +4741,7 @@ async function updateTask(input) {
|
|
|
4020
4741
|
}
|
|
4021
4742
|
}
|
|
4022
4743
|
}
|
|
4023
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4744
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4024
4745
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4025
4746
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4026
4747
|
taskId,
|
|
@@ -4392,6 +5113,7 @@ __export(tmux_routing_exports, {
|
|
|
4392
5113
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4393
5114
|
isExeSession: () => isExeSession,
|
|
4394
5115
|
isSessionBusy: () => isSessionBusy,
|
|
5116
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4395
5117
|
notifyParentExe: () => notifyParentExe,
|
|
4396
5118
|
parseParentExe: () => parseParentExe,
|
|
4397
5119
|
registerParentExe: () => registerParentExe,
|
|
@@ -4402,13 +5124,13 @@ __export(tmux_routing_exports, {
|
|
|
4402
5124
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4403
5125
|
});
|
|
4404
5126
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
4405
|
-
import { readFileSync as
|
|
4406
|
-
import
|
|
4407
|
-
import
|
|
5127
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5128
|
+
import path17 from "path";
|
|
5129
|
+
import os10 from "os";
|
|
4408
5130
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4409
5131
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4410
5132
|
function spawnLockPath(sessionName) {
|
|
4411
|
-
return
|
|
5133
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4412
5134
|
}
|
|
4413
5135
|
function isProcessAlive(pid) {
|
|
4414
5136
|
try {
|
|
@@ -4419,13 +5141,13 @@ function isProcessAlive(pid) {
|
|
|
4419
5141
|
}
|
|
4420
5142
|
}
|
|
4421
5143
|
function acquireSpawnLock2(sessionName) {
|
|
4422
|
-
if (!
|
|
5144
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4423
5145
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4424
5146
|
}
|
|
4425
5147
|
const lockFile = spawnLockPath(sessionName);
|
|
4426
|
-
if (
|
|
5148
|
+
if (existsSync14(lockFile)) {
|
|
4427
5149
|
try {
|
|
4428
|
-
const lock = JSON.parse(
|
|
5150
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4429
5151
|
const age = Date.now() - lock.timestamp;
|
|
4430
5152
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4431
5153
|
return false;
|
|
@@ -4433,7 +5155,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4433
5155
|
} catch {
|
|
4434
5156
|
}
|
|
4435
5157
|
}
|
|
4436
|
-
|
|
5158
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4437
5159
|
return true;
|
|
4438
5160
|
}
|
|
4439
5161
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4445,13 +5167,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4445
5167
|
function resolveBehaviorsExporterScript() {
|
|
4446
5168
|
try {
|
|
4447
5169
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4448
|
-
const scriptPath =
|
|
4449
|
-
|
|
5170
|
+
const scriptPath = path17.join(
|
|
5171
|
+
path17.dirname(thisFile),
|
|
4450
5172
|
"..",
|
|
4451
5173
|
"bin",
|
|
4452
5174
|
"exe-export-behaviors.js"
|
|
4453
5175
|
);
|
|
4454
|
-
return
|
|
5176
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4455
5177
|
} catch {
|
|
4456
5178
|
return null;
|
|
4457
5179
|
}
|
|
@@ -4517,12 +5239,12 @@ function extractRootExe(name) {
|
|
|
4517
5239
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4518
5240
|
}
|
|
4519
5241
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4520
|
-
if (!
|
|
5242
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4521
5243
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4522
5244
|
}
|
|
4523
5245
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4524
|
-
const filePath =
|
|
4525
|
-
|
|
5246
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5247
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4526
5248
|
parentExe: rootExe,
|
|
4527
5249
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4528
5250
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4530,7 +5252,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4530
5252
|
}
|
|
4531
5253
|
function getParentExe(sessionKey) {
|
|
4532
5254
|
try {
|
|
4533
|
-
const data = JSON.parse(
|
|
5255
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4534
5256
|
return data.parentExe || null;
|
|
4535
5257
|
} catch {
|
|
4536
5258
|
return null;
|
|
@@ -4538,8 +5260,8 @@ function getParentExe(sessionKey) {
|
|
|
4538
5260
|
}
|
|
4539
5261
|
function getDispatchedBy(sessionKey) {
|
|
4540
5262
|
try {
|
|
4541
|
-
const data = JSON.parse(
|
|
4542
|
-
|
|
5263
|
+
const data = JSON.parse(readFileSync12(
|
|
5264
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4543
5265
|
"utf8"
|
|
4544
5266
|
));
|
|
4545
5267
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4609,8 +5331,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4609
5331
|
}
|
|
4610
5332
|
function readDebounceState() {
|
|
4611
5333
|
try {
|
|
4612
|
-
if (!
|
|
4613
|
-
const raw = JSON.parse(
|
|
5334
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5335
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
4614
5336
|
const state = {};
|
|
4615
5337
|
for (const [key, val] of Object.entries(raw)) {
|
|
4616
5338
|
if (typeof val === "number") {
|
|
@@ -4626,8 +5348,8 @@ function readDebounceState() {
|
|
|
4626
5348
|
}
|
|
4627
5349
|
function writeDebounceState(state) {
|
|
4628
5350
|
try {
|
|
4629
|
-
if (!
|
|
4630
|
-
|
|
5351
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5352
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4631
5353
|
} catch {
|
|
4632
5354
|
}
|
|
4633
5355
|
}
|
|
@@ -4725,8 +5447,8 @@ function sendIntercom(targetSession) {
|
|
|
4725
5447
|
try {
|
|
4726
5448
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4727
5449
|
const agent = baseAgentName(rawAgent);
|
|
4728
|
-
const markerPath =
|
|
4729
|
-
if (
|
|
5450
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5451
|
+
if (existsSync14(markerPath)) {
|
|
4730
5452
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4731
5453
|
return "debounced";
|
|
4732
5454
|
}
|
|
@@ -4735,8 +5457,8 @@ function sendIntercom(targetSession) {
|
|
|
4735
5457
|
try {
|
|
4736
5458
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4737
5459
|
const agent = baseAgentName(rawAgent);
|
|
4738
|
-
const taskDir =
|
|
4739
|
-
if (
|
|
5460
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
5461
|
+
if (existsSync14(taskDir)) {
|
|
4740
5462
|
const files = readdirSync3(taskDir).filter(
|
|
4741
5463
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4742
5464
|
);
|
|
@@ -4796,6 +5518,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4796
5518
|
}
|
|
4797
5519
|
return true;
|
|
4798
5520
|
}
|
|
5521
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5522
|
+
const transport = getTransport();
|
|
5523
|
+
try {
|
|
5524
|
+
const sessions = transport.listSessions();
|
|
5525
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5526
|
+
execSync6(
|
|
5527
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5528
|
+
{ timeout: 3e3 }
|
|
5529
|
+
);
|
|
5530
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5531
|
+
return true;
|
|
5532
|
+
} catch {
|
|
5533
|
+
return false;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
4799
5536
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4800
5537
|
if (isCoordinatorName(employeeName)) {
|
|
4801
5538
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4869,26 +5606,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4869
5606
|
const transport = getTransport();
|
|
4870
5607
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4871
5608
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4872
|
-
const logDir =
|
|
4873
|
-
const logFile =
|
|
4874
|
-
if (!
|
|
5609
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5610
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5611
|
+
if (!existsSync14(logDir)) {
|
|
4875
5612
|
mkdirSync6(logDir, { recursive: true });
|
|
4876
5613
|
}
|
|
4877
5614
|
transport.kill(sessionName);
|
|
4878
5615
|
let cleanupSuffix = "";
|
|
4879
5616
|
try {
|
|
4880
5617
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
4881
|
-
const cleanupScript =
|
|
4882
|
-
if (
|
|
5618
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5619
|
+
if (existsSync14(cleanupScript)) {
|
|
4883
5620
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4884
5621
|
}
|
|
4885
5622
|
} catch {
|
|
4886
5623
|
}
|
|
4887
5624
|
try {
|
|
4888
|
-
const claudeJsonPath =
|
|
5625
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
4889
5626
|
let claudeJson = {};
|
|
4890
5627
|
try {
|
|
4891
|
-
claudeJson = JSON.parse(
|
|
5628
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
4892
5629
|
} catch {
|
|
4893
5630
|
}
|
|
4894
5631
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4896,17 +5633,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4896
5633
|
const trustDir = opts?.cwd ?? projectDir;
|
|
4897
5634
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4898
5635
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4899
|
-
|
|
5636
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4900
5637
|
} catch {
|
|
4901
5638
|
}
|
|
4902
5639
|
try {
|
|
4903
|
-
const settingsDir =
|
|
5640
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
4904
5641
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4905
|
-
const projSettingsDir =
|
|
4906
|
-
const settingsPath =
|
|
5642
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
5643
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
4907
5644
|
let settings = {};
|
|
4908
5645
|
try {
|
|
4909
|
-
settings = JSON.parse(
|
|
5646
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
4910
5647
|
} catch {
|
|
4911
5648
|
}
|
|
4912
5649
|
const perms = settings.permissions ?? {};
|
|
@@ -4935,7 +5672,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4935
5672
|
perms.allow = allow;
|
|
4936
5673
|
settings.permissions = perms;
|
|
4937
5674
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4938
|
-
|
|
5675
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4939
5676
|
}
|
|
4940
5677
|
} catch {
|
|
4941
5678
|
}
|
|
@@ -4950,8 +5687,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4950
5687
|
let behaviorsFlag = "";
|
|
4951
5688
|
let legacyFallbackWarned = false;
|
|
4952
5689
|
if (!useExeAgent && !useBinSymlink) {
|
|
4953
|
-
const identityPath =
|
|
4954
|
-
|
|
5690
|
+
const identityPath = path17.join(
|
|
5691
|
+
os10.homedir(),
|
|
4955
5692
|
".exe-os",
|
|
4956
5693
|
"identity",
|
|
4957
5694
|
`${employeeName}.md`
|
|
@@ -4960,13 +5697,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4960
5697
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4961
5698
|
if (hasAgentFlag) {
|
|
4962
5699
|
identityFlag = ` --agent ${employeeName}`;
|
|
4963
|
-
} else if (
|
|
5700
|
+
} else if (existsSync14(identityPath)) {
|
|
4964
5701
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4965
5702
|
legacyFallbackWarned = true;
|
|
4966
5703
|
}
|
|
4967
5704
|
const behaviorsFile = exportBehaviorsSync(
|
|
4968
5705
|
employeeName,
|
|
4969
|
-
|
|
5706
|
+
path17.basename(spawnCwd),
|
|
4970
5707
|
sessionName
|
|
4971
5708
|
);
|
|
4972
5709
|
if (behaviorsFile) {
|
|
@@ -4981,16 +5718,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4981
5718
|
}
|
|
4982
5719
|
let sessionContextFlag = "";
|
|
4983
5720
|
try {
|
|
4984
|
-
const ctxDir =
|
|
5721
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
4985
5722
|
mkdirSync6(ctxDir, { recursive: true });
|
|
4986
|
-
const ctxFile =
|
|
5723
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4987
5724
|
const ctxContent = [
|
|
4988
5725
|
`## Session Context`,
|
|
4989
5726
|
`You are running in tmux session: ${sessionName}.`,
|
|
4990
5727
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4991
5728
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4992
5729
|
].join("\n");
|
|
4993
|
-
|
|
5730
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
4994
5731
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4995
5732
|
} catch {
|
|
4996
5733
|
}
|
|
@@ -5067,8 +5804,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5067
5804
|
transport.pipeLog(sessionName, logFile);
|
|
5068
5805
|
try {
|
|
5069
5806
|
const mySession = getMySession();
|
|
5070
|
-
const dispatchInfo =
|
|
5071
|
-
|
|
5807
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5808
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5072
5809
|
dispatchedBy: mySession,
|
|
5073
5810
|
rootExe: exeSession,
|
|
5074
5811
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5142,15 +5879,15 @@ var init_tmux_routing = __esm({
|
|
|
5142
5879
|
init_intercom_queue();
|
|
5143
5880
|
init_plan_limits();
|
|
5144
5881
|
init_employees();
|
|
5145
|
-
SPAWN_LOCK_DIR =
|
|
5146
|
-
SESSION_CACHE =
|
|
5882
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5883
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5147
5884
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5148
5885
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5149
5886
|
VERIFY_PANE_LINES = 200;
|
|
5150
5887
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5151
5888
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5152
|
-
INTERCOM_LOG2 =
|
|
5153
|
-
DEBOUNCE_FILE =
|
|
5889
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5890
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5154
5891
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5155
5892
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5156
5893
|
}
|
|
@@ -5173,6 +5910,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5173
5910
|
args: [scope]
|
|
5174
5911
|
};
|
|
5175
5912
|
}
|
|
5913
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5914
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5915
|
+
if (!scope) return { sql: "", args: [] };
|
|
5916
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5917
|
+
return {
|
|
5918
|
+
sql: ` AND ${col} = ?`,
|
|
5919
|
+
args: [scope]
|
|
5920
|
+
};
|
|
5921
|
+
}
|
|
5176
5922
|
var init_task_scope = __esm({
|
|
5177
5923
|
"src/lib/task-scope.ts"() {
|
|
5178
5924
|
"use strict";
|
|
@@ -5191,14 +5937,14 @@ var init_memory = __esm({
|
|
|
5191
5937
|
|
|
5192
5938
|
// src/lib/keychain.ts
|
|
5193
5939
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5194
|
-
import { existsSync as
|
|
5195
|
-
import
|
|
5196
|
-
import
|
|
5940
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5941
|
+
import path18 from "path";
|
|
5942
|
+
import os11 from "os";
|
|
5197
5943
|
function getKeyDir() {
|
|
5198
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5944
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
5199
5945
|
}
|
|
5200
5946
|
function getKeyPath() {
|
|
5201
|
-
return
|
|
5947
|
+
return path18.join(getKeyDir(), "master.key");
|
|
5202
5948
|
}
|
|
5203
5949
|
async function tryKeytar() {
|
|
5204
5950
|
try {
|
|
@@ -5219,9 +5965,9 @@ async function getMasterKey() {
|
|
|
5219
5965
|
}
|
|
5220
5966
|
}
|
|
5221
5967
|
const keyPath = getKeyPath();
|
|
5222
|
-
if (!
|
|
5968
|
+
if (!existsSync15(keyPath)) {
|
|
5223
5969
|
process.stderr.write(
|
|
5224
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5970
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5225
5971
|
`
|
|
5226
5972
|
);
|
|
5227
5973
|
return null;
|
|
@@ -5251,6 +5997,7 @@ var shard_manager_exports = {};
|
|
|
5251
5997
|
__export(shard_manager_exports, {
|
|
5252
5998
|
disposeShards: () => disposeShards,
|
|
5253
5999
|
ensureShardSchema: () => ensureShardSchema,
|
|
6000
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5254
6001
|
getReadyShardClient: () => getReadyShardClient,
|
|
5255
6002
|
getShardClient: () => getShardClient,
|
|
5256
6003
|
getShardsDir: () => getShardsDir,
|
|
@@ -5259,15 +6006,18 @@ __export(shard_manager_exports, {
|
|
|
5259
6006
|
listShards: () => listShards,
|
|
5260
6007
|
shardExists: () => shardExists
|
|
5261
6008
|
});
|
|
5262
|
-
import
|
|
5263
|
-
import { existsSync as
|
|
6009
|
+
import path19 from "path";
|
|
6010
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5264
6011
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5265
6012
|
function initShardManager(encryptionKey) {
|
|
5266
6013
|
_encryptionKey = encryptionKey;
|
|
5267
|
-
if (!
|
|
6014
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5268
6015
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5269
6016
|
}
|
|
5270
6017
|
_shardingEnabled = true;
|
|
6018
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6019
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6020
|
+
_evictionTimer.unref();
|
|
5271
6021
|
}
|
|
5272
6022
|
function isShardingEnabled() {
|
|
5273
6023
|
return _shardingEnabled;
|
|
@@ -5284,21 +6034,28 @@ function getShardClient(projectName) {
|
|
|
5284
6034
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5285
6035
|
}
|
|
5286
6036
|
const cached = _shards.get(safeName);
|
|
5287
|
-
if (cached)
|
|
5288
|
-
|
|
6037
|
+
if (cached) {
|
|
6038
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6039
|
+
return cached;
|
|
6040
|
+
}
|
|
6041
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6042
|
+
evictLRU();
|
|
6043
|
+
}
|
|
6044
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
5289
6045
|
const client = createClient2({
|
|
5290
6046
|
url: `file:${dbPath}`,
|
|
5291
6047
|
encryptionKey: _encryptionKey
|
|
5292
6048
|
});
|
|
5293
6049
|
_shards.set(safeName, client);
|
|
6050
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5294
6051
|
return client;
|
|
5295
6052
|
}
|
|
5296
6053
|
function shardExists(projectName) {
|
|
5297
6054
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5298
|
-
return
|
|
6055
|
+
return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
5299
6056
|
}
|
|
5300
6057
|
function listShards() {
|
|
5301
|
-
if (!
|
|
6058
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5302
6059
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5303
6060
|
}
|
|
5304
6061
|
async function ensureShardSchema(client) {
|
|
@@ -5350,6 +6107,8 @@ async function ensureShardSchema(client) {
|
|
|
5350
6107
|
for (const col of [
|
|
5351
6108
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5352
6109
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6110
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6111
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5353
6112
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5354
6113
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5355
6114
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5372,7 +6131,23 @@ async function ensureShardSchema(client) {
|
|
|
5372
6131
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5373
6132
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5374
6133
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5375
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6134
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6135
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6136
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6137
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6138
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6139
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6140
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6141
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6142
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6143
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6144
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6145
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6146
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6147
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6148
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6149
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6150
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5376
6151
|
]) {
|
|
5377
6152
|
try {
|
|
5378
6153
|
await client.execute(col);
|
|
@@ -5471,21 +6246,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5471
6246
|
await ensureShardSchema(client);
|
|
5472
6247
|
return client;
|
|
5473
6248
|
}
|
|
6249
|
+
function evictLRU() {
|
|
6250
|
+
let oldest = null;
|
|
6251
|
+
let oldestTime = Infinity;
|
|
6252
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6253
|
+
if (time < oldestTime) {
|
|
6254
|
+
oldestTime = time;
|
|
6255
|
+
oldest = name;
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
if (oldest) {
|
|
6259
|
+
const client = _shards.get(oldest);
|
|
6260
|
+
if (client) {
|
|
6261
|
+
client.close();
|
|
6262
|
+
}
|
|
6263
|
+
_shards.delete(oldest);
|
|
6264
|
+
_shardLastAccess.delete(oldest);
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
function evictIdleShards() {
|
|
6268
|
+
const now = Date.now();
|
|
6269
|
+
const toEvict = [];
|
|
6270
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6271
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6272
|
+
toEvict.push(name);
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
for (const name of toEvict) {
|
|
6276
|
+
const client = _shards.get(name);
|
|
6277
|
+
if (client) {
|
|
6278
|
+
client.close();
|
|
6279
|
+
}
|
|
6280
|
+
_shards.delete(name);
|
|
6281
|
+
_shardLastAccess.delete(name);
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6284
|
+
function getOpenShardCount() {
|
|
6285
|
+
return _shards.size;
|
|
6286
|
+
}
|
|
5474
6287
|
function disposeShards() {
|
|
6288
|
+
if (_evictionTimer) {
|
|
6289
|
+
clearInterval(_evictionTimer);
|
|
6290
|
+
_evictionTimer = null;
|
|
6291
|
+
}
|
|
5475
6292
|
for (const [, client] of _shards) {
|
|
5476
6293
|
client.close();
|
|
5477
6294
|
}
|
|
5478
6295
|
_shards.clear();
|
|
6296
|
+
_shardLastAccess.clear();
|
|
5479
6297
|
_shardingEnabled = false;
|
|
5480
6298
|
_encryptionKey = null;
|
|
5481
6299
|
}
|
|
5482
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6300
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5483
6301
|
var init_shard_manager = __esm({
|
|
5484
6302
|
"src/lib/shard-manager.ts"() {
|
|
5485
6303
|
"use strict";
|
|
5486
6304
|
init_config();
|
|
5487
|
-
SHARDS_DIR =
|
|
6305
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
6306
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6307
|
+
MAX_OPEN_SHARDS = 10;
|
|
6308
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5488
6309
|
_shards = /* @__PURE__ */ new Map();
|
|
6310
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6311
|
+
_evictionTimer = null;
|
|
5489
6312
|
_encryptionKey = null;
|
|
5490
6313
|
_shardingEnabled = false;
|
|
5491
6314
|
}
|