@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -320,9 +320,47 @@ var init_provider_table = __esm({
|
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
// src/lib/secure-files.ts
|
|
324
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
325
|
+
import { chmod, mkdir } from "fs/promises";
|
|
326
|
+
async function ensurePrivateDir(dirPath) {
|
|
327
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
328
|
+
try {
|
|
329
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function ensurePrivateDirSync(dirPath) {
|
|
334
|
+
mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
335
|
+
try {
|
|
336
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function enforcePrivateFile(filePath) {
|
|
341
|
+
try {
|
|
342
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function enforcePrivateFileSync(filePath) {
|
|
347
|
+
try {
|
|
348
|
+
if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
353
|
+
var init_secure_files = __esm({
|
|
354
|
+
"src/lib/secure-files.ts"() {
|
|
355
|
+
"use strict";
|
|
356
|
+
PRIVATE_DIR_MODE = 448;
|
|
357
|
+
PRIVATE_FILE_MODE = 384;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
323
361
|
// src/lib/config.ts
|
|
324
|
-
import { readFile, writeFile
|
|
325
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
362
|
+
import { readFile, writeFile } from "fs/promises";
|
|
363
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
326
364
|
import path2 from "path";
|
|
327
365
|
import os2 from "os";
|
|
328
366
|
function resolveDataDir() {
|
|
@@ -330,7 +368,7 @@ function resolveDataDir() {
|
|
|
330
368
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
331
369
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
332
370
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
333
|
-
if (!
|
|
371
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
334
372
|
try {
|
|
335
373
|
renameSync(legacyDir, newDir);
|
|
336
374
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -393,9 +431,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
393
431
|
}
|
|
394
432
|
async function loadConfig() {
|
|
395
433
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
396
|
-
await
|
|
434
|
+
await ensurePrivateDir(dir);
|
|
397
435
|
const configPath = path2.join(dir, "config.json");
|
|
398
|
-
if (!
|
|
436
|
+
if (!existsSync3(configPath)) {
|
|
399
437
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
400
438
|
}
|
|
401
439
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -408,6 +446,7 @@ async function loadConfig() {
|
|
|
408
446
|
`);
|
|
409
447
|
try {
|
|
410
448
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
449
|
+
await enforcePrivateFile(configPath);
|
|
411
450
|
} catch {
|
|
412
451
|
}
|
|
413
452
|
}
|
|
@@ -427,6 +466,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
427
466
|
var init_config = __esm({
|
|
428
467
|
"src/lib/config.ts"() {
|
|
429
468
|
"use strict";
|
|
469
|
+
init_secure_files();
|
|
430
470
|
EXE_AI_DIR = resolveDataDir();
|
|
431
471
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
432
472
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -531,10 +571,10 @@ var init_runtime_table = __esm({
|
|
|
531
571
|
});
|
|
532
572
|
|
|
533
573
|
// src/lib/agent-config.ts
|
|
534
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
574
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
535
575
|
import path3 from "path";
|
|
536
576
|
function loadAgentConfig() {
|
|
537
|
-
if (!
|
|
577
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
538
578
|
try {
|
|
539
579
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
540
580
|
} catch {
|
|
@@ -555,6 +595,7 @@ var init_agent_config = __esm({
|
|
|
555
595
|
"use strict";
|
|
556
596
|
init_config();
|
|
557
597
|
init_runtime_table();
|
|
598
|
+
init_secure_files();
|
|
558
599
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
559
600
|
DEFAULT_MODELS = {
|
|
560
601
|
claude: "claude-opus-4",
|
|
@@ -573,16 +614,16 @@ __export(intercom_queue_exports, {
|
|
|
573
614
|
queueIntercom: () => queueIntercom,
|
|
574
615
|
readQueue: () => readQueue
|
|
575
616
|
});
|
|
576
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
617
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
577
618
|
import path4 from "path";
|
|
578
619
|
import os3 from "os";
|
|
579
620
|
function ensureDir() {
|
|
580
621
|
const dir = path4.dirname(QUEUE_PATH);
|
|
581
|
-
if (!
|
|
622
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
582
623
|
}
|
|
583
624
|
function readQueue() {
|
|
584
625
|
try {
|
|
585
|
-
if (!
|
|
626
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
586
627
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
587
628
|
} catch {
|
|
588
629
|
return [];
|
|
@@ -747,7 +788,7 @@ var init_db_retry = __esm({
|
|
|
747
788
|
|
|
748
789
|
// src/lib/employees.ts
|
|
749
790
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
750
|
-
import { existsSync as
|
|
791
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
751
792
|
import { execSync as execSync3 } from "child_process";
|
|
752
793
|
import path5 from "path";
|
|
753
794
|
import os4 from "os";
|
|
@@ -768,7 +809,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
768
809
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
769
810
|
}
|
|
770
811
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
771
|
-
if (!
|
|
812
|
+
if (!existsSync6(employeesPath)) return [];
|
|
772
813
|
try {
|
|
773
814
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
774
815
|
} catch {
|
|
@@ -792,7 +833,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
792
833
|
if (!emp) return false;
|
|
793
834
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
794
835
|
}
|
|
795
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
836
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
796
837
|
var init_employees = __esm({
|
|
797
838
|
"src/lib/employees.ts"() {
|
|
798
839
|
"use strict";
|
|
@@ -801,16 +842,638 @@ var init_employees = __esm({
|
|
|
801
842
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
802
843
|
COORDINATOR_ROLE = "COO";
|
|
803
844
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
845
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// src/lib/database-adapter.ts
|
|
850
|
+
import os5 from "os";
|
|
851
|
+
import path6 from "path";
|
|
852
|
+
import { createRequire } from "module";
|
|
853
|
+
import { pathToFileURL } from "url";
|
|
854
|
+
function quotedIdentifier(identifier) {
|
|
855
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
856
|
+
}
|
|
857
|
+
function unqualifiedTableName(name) {
|
|
858
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
859
|
+
const parts = raw.split(".");
|
|
860
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
861
|
+
}
|
|
862
|
+
function stripTrailingSemicolon(sql) {
|
|
863
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
864
|
+
}
|
|
865
|
+
function appendClause(sql, clause) {
|
|
866
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
867
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
868
|
+
if (!returningMatch) {
|
|
869
|
+
return `${trimmed}${clause}`;
|
|
870
|
+
}
|
|
871
|
+
const idx = returningMatch.index;
|
|
872
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
873
|
+
}
|
|
874
|
+
function normalizeStatement(stmt) {
|
|
875
|
+
if (typeof stmt === "string") {
|
|
876
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
877
|
+
}
|
|
878
|
+
const sql = stmt.sql;
|
|
879
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
880
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
881
|
+
}
|
|
882
|
+
return { kind: "named", sql, args: stmt.args };
|
|
883
|
+
}
|
|
884
|
+
function rewriteBooleanLiterals(sql) {
|
|
885
|
+
let out = sql;
|
|
886
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
887
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
888
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
889
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
890
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
891
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
892
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
893
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
894
|
+
}
|
|
895
|
+
return out;
|
|
896
|
+
}
|
|
897
|
+
function rewriteInsertOrIgnore(sql) {
|
|
898
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
899
|
+
return sql;
|
|
900
|
+
}
|
|
901
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
902
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
903
|
+
}
|
|
904
|
+
function rewriteInsertOrReplace(sql) {
|
|
905
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
906
|
+
if (!match) {
|
|
907
|
+
return sql;
|
|
908
|
+
}
|
|
909
|
+
const rawTable = match[1];
|
|
910
|
+
const rawColumns = match[2];
|
|
911
|
+
const remainder = match[3];
|
|
912
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
913
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
914
|
+
if (!conflictKeys?.length) {
|
|
915
|
+
return sql;
|
|
916
|
+
}
|
|
917
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
918
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
919
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
920
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
921
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
922
|
+
}
|
|
923
|
+
function rewriteSql(sql) {
|
|
924
|
+
let out = sql;
|
|
925
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
926
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
927
|
+
out = rewriteBooleanLiterals(out);
|
|
928
|
+
out = rewriteInsertOrReplace(out);
|
|
929
|
+
out = rewriteInsertOrIgnore(out);
|
|
930
|
+
return stripTrailingSemicolon(out);
|
|
931
|
+
}
|
|
932
|
+
function toBoolean(value) {
|
|
933
|
+
if (value === null || value === void 0) return value;
|
|
934
|
+
if (typeof value === "boolean") return value;
|
|
935
|
+
if (typeof value === "number") return value !== 0;
|
|
936
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
937
|
+
if (typeof value === "string") {
|
|
938
|
+
const normalized = value.trim().toLowerCase();
|
|
939
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
940
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
941
|
+
}
|
|
942
|
+
return Boolean(value);
|
|
943
|
+
}
|
|
944
|
+
function countQuestionMarks(sql, end) {
|
|
945
|
+
let count = 0;
|
|
946
|
+
let inSingle = false;
|
|
947
|
+
let inDouble = false;
|
|
948
|
+
let inLineComment = false;
|
|
949
|
+
let inBlockComment = false;
|
|
950
|
+
for (let i = 0; i < end; i++) {
|
|
951
|
+
const ch = sql[i];
|
|
952
|
+
const next = sql[i + 1];
|
|
953
|
+
if (inLineComment) {
|
|
954
|
+
if (ch === "\n") inLineComment = false;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (inBlockComment) {
|
|
958
|
+
if (ch === "*" && next === "/") {
|
|
959
|
+
inBlockComment = false;
|
|
960
|
+
i += 1;
|
|
961
|
+
}
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
965
|
+
inLineComment = true;
|
|
966
|
+
i += 1;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
970
|
+
inBlockComment = true;
|
|
971
|
+
i += 1;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
975
|
+
inSingle = !inSingle;
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
979
|
+
inDouble = !inDouble;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
983
|
+
count += 1;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return count;
|
|
987
|
+
}
|
|
988
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
989
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
990
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
991
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
992
|
+
for (const match of sql.matchAll(pattern)) {
|
|
993
|
+
const matchText = match[0];
|
|
994
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
995
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return indexes;
|
|
999
|
+
}
|
|
1000
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1001
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1002
|
+
if (!match) return;
|
|
1003
|
+
const rawTable = match[1];
|
|
1004
|
+
const rawColumns = match[2];
|
|
1005
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1006
|
+
if (!boolColumns?.size) return;
|
|
1007
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1008
|
+
for (const [index, column] of columns.entries()) {
|
|
1009
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1010
|
+
args[index] = toBoolean(args[index]);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1015
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1016
|
+
if (!match) return;
|
|
1017
|
+
const rawTable = match[1];
|
|
1018
|
+
const setClause = match[2];
|
|
1019
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1020
|
+
if (!boolColumns?.size) return;
|
|
1021
|
+
const assignments = setClause.split(",");
|
|
1022
|
+
let placeholderIndex = 0;
|
|
1023
|
+
for (const assignment of assignments) {
|
|
1024
|
+
if (!assignment.includes("?")) continue;
|
|
1025
|
+
placeholderIndex += 1;
|
|
1026
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1027
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1028
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function coerceBooleanArgs(sql, args) {
|
|
1033
|
+
const nextArgs = [...args];
|
|
1034
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1035
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1036
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1037
|
+
for (const index of placeholderIndexes) {
|
|
1038
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1039
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return nextArgs;
|
|
1043
|
+
}
|
|
1044
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1045
|
+
let out = "";
|
|
1046
|
+
let placeholder = 0;
|
|
1047
|
+
let inSingle = false;
|
|
1048
|
+
let inDouble = false;
|
|
1049
|
+
let inLineComment = false;
|
|
1050
|
+
let inBlockComment = false;
|
|
1051
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1052
|
+
const ch = sql[i];
|
|
1053
|
+
const next = sql[i + 1];
|
|
1054
|
+
if (inLineComment) {
|
|
1055
|
+
out += ch;
|
|
1056
|
+
if (ch === "\n") inLineComment = false;
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (inBlockComment) {
|
|
1060
|
+
out += ch;
|
|
1061
|
+
if (ch === "*" && next === "/") {
|
|
1062
|
+
out += next;
|
|
1063
|
+
inBlockComment = false;
|
|
1064
|
+
i += 1;
|
|
1065
|
+
}
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1069
|
+
out += ch + next;
|
|
1070
|
+
inLineComment = true;
|
|
1071
|
+
i += 1;
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1075
|
+
out += ch + next;
|
|
1076
|
+
inBlockComment = true;
|
|
1077
|
+
i += 1;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1081
|
+
inSingle = !inSingle;
|
|
1082
|
+
out += ch;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1086
|
+
inDouble = !inDouble;
|
|
1087
|
+
out += ch;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1091
|
+
placeholder += 1;
|
|
1092
|
+
out += `$${placeholder}`;
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
out += ch;
|
|
1096
|
+
}
|
|
1097
|
+
return out;
|
|
1098
|
+
}
|
|
1099
|
+
function translateStatementForPostgres(stmt) {
|
|
1100
|
+
const normalized = normalizeStatement(stmt);
|
|
1101
|
+
if (normalized.kind === "named") {
|
|
1102
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1103
|
+
}
|
|
1104
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1105
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1106
|
+
return {
|
|
1107
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1108
|
+
args: coercedArgs
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function shouldBypassPostgres(stmt) {
|
|
1112
|
+
const normalized = normalizeStatement(stmt);
|
|
1113
|
+
if (normalized.kind === "named") {
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1117
|
+
}
|
|
1118
|
+
function shouldFallbackOnError(error) {
|
|
1119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1120
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1121
|
+
}
|
|
1122
|
+
function isReadQuery(sql) {
|
|
1123
|
+
const trimmed = sql.trimStart();
|
|
1124
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1125
|
+
}
|
|
1126
|
+
function buildRow(row, columns) {
|
|
1127
|
+
const values = columns.map((column) => row[column]);
|
|
1128
|
+
return Object.assign(values, row);
|
|
1129
|
+
}
|
|
1130
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1131
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1132
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1133
|
+
return {
|
|
1134
|
+
columns,
|
|
1135
|
+
columnTypes: columns.map(() => ""),
|
|
1136
|
+
rows: resultRows,
|
|
1137
|
+
rowsAffected,
|
|
1138
|
+
lastInsertRowid: void 0,
|
|
1139
|
+
toJSON() {
|
|
1140
|
+
return {
|
|
1141
|
+
columns,
|
|
1142
|
+
columnTypes: columns.map(() => ""),
|
|
1143
|
+
rows,
|
|
1144
|
+
rowsAffected,
|
|
1145
|
+
lastInsertRowid: void 0
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
async function loadPrismaClient() {
|
|
1151
|
+
if (!prismaClientPromise) {
|
|
1152
|
+
prismaClientPromise = (async () => {
|
|
1153
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1154
|
+
if (explicitPath) {
|
|
1155
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1156
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1157
|
+
if (!PrismaClient2) {
|
|
1158
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1159
|
+
}
|
|
1160
|
+
return new PrismaClient2();
|
|
1161
|
+
}
|
|
1162
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
|
|
1163
|
+
const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1164
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1165
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1166
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1167
|
+
if (!PrismaClient) {
|
|
1168
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1169
|
+
}
|
|
1170
|
+
return new PrismaClient();
|
|
1171
|
+
})();
|
|
1172
|
+
}
|
|
1173
|
+
return prismaClientPromise;
|
|
1174
|
+
}
|
|
1175
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1176
|
+
if (!compatibilityBootstrapPromise) {
|
|
1177
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1178
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1179
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1180
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1181
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1182
|
+
relation
|
|
1183
|
+
);
|
|
1184
|
+
if (!rows[0]?.regclass) {
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
await prisma.$executeRawUnsafe(
|
|
1188
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
})();
|
|
1192
|
+
}
|
|
1193
|
+
return compatibilityBootstrapPromise;
|
|
1194
|
+
}
|
|
1195
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1196
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1197
|
+
if (isReadQuery(translated.sql)) {
|
|
1198
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1199
|
+
translated.sql,
|
|
1200
|
+
...translated.args
|
|
1201
|
+
);
|
|
1202
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1203
|
+
}
|
|
1204
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1205
|
+
return buildResultSet([], rowsAffected);
|
|
1206
|
+
}
|
|
1207
|
+
function splitSqlStatements(sql) {
|
|
1208
|
+
const parts = [];
|
|
1209
|
+
let current = "";
|
|
1210
|
+
let inSingle = false;
|
|
1211
|
+
let inDouble = false;
|
|
1212
|
+
let inLineComment = false;
|
|
1213
|
+
let inBlockComment = false;
|
|
1214
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1215
|
+
const ch = sql[i];
|
|
1216
|
+
const next = sql[i + 1];
|
|
1217
|
+
if (inLineComment) {
|
|
1218
|
+
current += ch;
|
|
1219
|
+
if (ch === "\n") inLineComment = false;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (inBlockComment) {
|
|
1223
|
+
current += ch;
|
|
1224
|
+
if (ch === "*" && next === "/") {
|
|
1225
|
+
current += next;
|
|
1226
|
+
inBlockComment = false;
|
|
1227
|
+
i += 1;
|
|
1228
|
+
}
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1232
|
+
current += ch + next;
|
|
1233
|
+
inLineComment = true;
|
|
1234
|
+
i += 1;
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1238
|
+
current += ch + next;
|
|
1239
|
+
inBlockComment = true;
|
|
1240
|
+
i += 1;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1244
|
+
inSingle = !inSingle;
|
|
1245
|
+
current += ch;
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1249
|
+
inDouble = !inDouble;
|
|
1250
|
+
current += ch;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1254
|
+
if (current.trim()) {
|
|
1255
|
+
parts.push(current.trim());
|
|
1256
|
+
}
|
|
1257
|
+
current = "";
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
current += ch;
|
|
1261
|
+
}
|
|
1262
|
+
if (current.trim()) {
|
|
1263
|
+
parts.push(current.trim());
|
|
1264
|
+
}
|
|
1265
|
+
return parts;
|
|
1266
|
+
}
|
|
1267
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1268
|
+
const prisma = await loadPrismaClient();
|
|
1269
|
+
await ensureCompatibilityViews(prisma);
|
|
1270
|
+
let closed = false;
|
|
1271
|
+
let adapter;
|
|
1272
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1273
|
+
if (!fallbackClient) {
|
|
1274
|
+
if (error) throw error;
|
|
1275
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1276
|
+
}
|
|
1277
|
+
if (error) {
|
|
1278
|
+
process.stderr.write(
|
|
1279
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1280
|
+
`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
return fallbackClient.execute(stmt);
|
|
1284
|
+
};
|
|
1285
|
+
adapter = {
|
|
1286
|
+
async execute(stmt) {
|
|
1287
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1288
|
+
return fallbackExecute(stmt);
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
if (shouldFallbackOnError(error)) {
|
|
1294
|
+
return fallbackExecute(stmt, error);
|
|
1295
|
+
}
|
|
1296
|
+
throw error;
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
async batch(stmts, mode) {
|
|
1300
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1301
|
+
if (!fallbackClient) {
|
|
1302
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1303
|
+
}
|
|
1304
|
+
return fallbackClient.batch(stmts, mode);
|
|
1305
|
+
}
|
|
1306
|
+
try {
|
|
1307
|
+
if (prisma.$transaction) {
|
|
1308
|
+
return await prisma.$transaction(async (tx) => {
|
|
1309
|
+
const results2 = [];
|
|
1310
|
+
for (const stmt of stmts) {
|
|
1311
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1312
|
+
}
|
|
1313
|
+
return results2;
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
const results = [];
|
|
1317
|
+
for (const stmt of stmts) {
|
|
1318
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1319
|
+
}
|
|
1320
|
+
return results;
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1323
|
+
process.stderr.write(
|
|
1324
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1325
|
+
`
|
|
1326
|
+
);
|
|
1327
|
+
return fallbackClient.batch(stmts, mode);
|
|
1328
|
+
}
|
|
1329
|
+
throw error;
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
async migrate(stmts) {
|
|
1333
|
+
if (fallbackClient) {
|
|
1334
|
+
return fallbackClient.migrate(stmts);
|
|
1335
|
+
}
|
|
1336
|
+
return adapter.batch(stmts, "deferred");
|
|
1337
|
+
},
|
|
1338
|
+
async transaction(mode) {
|
|
1339
|
+
if (!fallbackClient) {
|
|
1340
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1341
|
+
}
|
|
1342
|
+
return fallbackClient.transaction(mode);
|
|
1343
|
+
},
|
|
1344
|
+
async executeMultiple(sql) {
|
|
1345
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1346
|
+
return fallbackClient.executeMultiple(sql);
|
|
1347
|
+
}
|
|
1348
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1349
|
+
await adapter.execute(statement);
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
async sync() {
|
|
1353
|
+
if (fallbackClient) {
|
|
1354
|
+
return fallbackClient.sync();
|
|
1355
|
+
}
|
|
1356
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1357
|
+
},
|
|
1358
|
+
close() {
|
|
1359
|
+
closed = true;
|
|
1360
|
+
prismaClientPromise = null;
|
|
1361
|
+
compatibilityBootstrapPromise = null;
|
|
1362
|
+
void prisma.$disconnect?.();
|
|
1363
|
+
},
|
|
1364
|
+
get closed() {
|
|
1365
|
+
return closed;
|
|
1366
|
+
},
|
|
1367
|
+
get protocol() {
|
|
1368
|
+
return "prisma-postgres";
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
return adapter;
|
|
1372
|
+
}
|
|
1373
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1374
|
+
var init_database_adapter = __esm({
|
|
1375
|
+
"src/lib/database-adapter.ts"() {
|
|
1376
|
+
"use strict";
|
|
1377
|
+
VIEW_MAPPINGS = [
|
|
1378
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1379
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1380
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1381
|
+
{ view: "entities", source: "memory.entities" },
|
|
1382
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1383
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1384
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1385
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1386
|
+
{ view: "messages", source: "memory.messages" },
|
|
1387
|
+
{ view: "users", source: "wiki.users" },
|
|
1388
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1389
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1390
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1391
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1392
|
+
];
|
|
1393
|
+
UPSERT_KEYS = {
|
|
1394
|
+
memories: ["id"],
|
|
1395
|
+
tasks: ["id"],
|
|
1396
|
+
behaviors: ["id"],
|
|
1397
|
+
entities: ["id"],
|
|
1398
|
+
relationships: ["id"],
|
|
1399
|
+
entity_aliases: ["alias"],
|
|
1400
|
+
notifications: ["id"],
|
|
1401
|
+
messages: ["id"],
|
|
1402
|
+
users: ["id"],
|
|
1403
|
+
workspaces: ["id"],
|
|
1404
|
+
workspace_users: ["id"],
|
|
1405
|
+
documents: ["id"],
|
|
1406
|
+
chats: ["id"]
|
|
1407
|
+
};
|
|
1408
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1409
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1410
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1411
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1412
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1413
|
+
};
|
|
1414
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1415
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1416
|
+
);
|
|
1417
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1418
|
+
/\bPRAGMA\b/i,
|
|
1419
|
+
/\bsqlite_master\b/i,
|
|
1420
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1421
|
+
/\bMATCH\b/i,
|
|
1422
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1423
|
+
/\bjson_extract\s*\(/i,
|
|
1424
|
+
/\bjulianday\s*\(/i,
|
|
1425
|
+
/\bstrftime\s*\(/i,
|
|
1426
|
+
/\blast_insert_rowid\s*\(/i
|
|
1427
|
+
];
|
|
1428
|
+
prismaClientPromise = null;
|
|
1429
|
+
compatibilityBootstrapPromise = null;
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// src/lib/daemon-auth.ts
|
|
1434
|
+
import crypto from "crypto";
|
|
1435
|
+
import path7 from "path";
|
|
1436
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1437
|
+
function normalizeToken(token) {
|
|
1438
|
+
if (!token) return null;
|
|
1439
|
+
const trimmed = token.trim();
|
|
1440
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1441
|
+
}
|
|
1442
|
+
function readDaemonToken() {
|
|
1443
|
+
try {
|
|
1444
|
+
if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
|
|
1445
|
+
return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
|
|
1446
|
+
} catch {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function ensureDaemonToken(seed) {
|
|
1451
|
+
const existing = readDaemonToken();
|
|
1452
|
+
if (existing) return existing;
|
|
1453
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1454
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1455
|
+
writeFileSync5(DAEMON_TOKEN_PATH, `${token}
|
|
1456
|
+
`, "utf8");
|
|
1457
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1458
|
+
return token;
|
|
1459
|
+
}
|
|
1460
|
+
var DAEMON_TOKEN_PATH;
|
|
1461
|
+
var init_daemon_auth = __esm({
|
|
1462
|
+
"src/lib/daemon-auth.ts"() {
|
|
1463
|
+
"use strict";
|
|
1464
|
+
init_config();
|
|
1465
|
+
init_secure_files();
|
|
1466
|
+
DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
|
|
804
1467
|
}
|
|
805
1468
|
});
|
|
806
1469
|
|
|
807
1470
|
// src/lib/exe-daemon-client.ts
|
|
808
1471
|
import net from "net";
|
|
809
|
-
import
|
|
1472
|
+
import os6 from "os";
|
|
810
1473
|
import { spawn } from "child_process";
|
|
811
1474
|
import { randomUUID } from "crypto";
|
|
812
|
-
import { existsSync as
|
|
813
|
-
import
|
|
1475
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
1476
|
+
import path8 from "path";
|
|
814
1477
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
815
1478
|
function handleData(chunk) {
|
|
816
1479
|
_buffer += chunk.toString();
|
|
@@ -838,9 +1501,9 @@ function handleData(chunk) {
|
|
|
838
1501
|
}
|
|
839
1502
|
}
|
|
840
1503
|
function cleanupStaleFiles() {
|
|
841
|
-
if (
|
|
1504
|
+
if (existsSync8(PID_PATH)) {
|
|
842
1505
|
try {
|
|
843
|
-
const pid = parseInt(
|
|
1506
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
844
1507
|
if (pid > 0) {
|
|
845
1508
|
try {
|
|
846
1509
|
process.kill(pid, 0);
|
|
@@ -861,17 +1524,17 @@ function cleanupStaleFiles() {
|
|
|
861
1524
|
}
|
|
862
1525
|
}
|
|
863
1526
|
function findPackageRoot() {
|
|
864
|
-
let dir =
|
|
865
|
-
const { root } =
|
|
1527
|
+
let dir = path8.dirname(fileURLToPath2(import.meta.url));
|
|
1528
|
+
const { root } = path8.parse(dir);
|
|
866
1529
|
while (dir !== root) {
|
|
867
|
-
if (
|
|
868
|
-
dir =
|
|
1530
|
+
if (existsSync8(path8.join(dir, "package.json"))) return dir;
|
|
1531
|
+
dir = path8.dirname(dir);
|
|
869
1532
|
}
|
|
870
1533
|
return null;
|
|
871
1534
|
}
|
|
872
1535
|
function spawnDaemon() {
|
|
873
|
-
const freeGB =
|
|
874
|
-
const totalGB =
|
|
1536
|
+
const freeGB = os6.freemem() / (1024 * 1024 * 1024);
|
|
1537
|
+
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
875
1538
|
if (totalGB <= 8) {
|
|
876
1539
|
process.stderr.write(
|
|
877
1540
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -891,16 +1554,17 @@ function spawnDaemon() {
|
|
|
891
1554
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
892
1555
|
return;
|
|
893
1556
|
}
|
|
894
|
-
const daemonPath =
|
|
895
|
-
if (!
|
|
1557
|
+
const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1558
|
+
if (!existsSync8(daemonPath)) {
|
|
896
1559
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
897
1560
|
`);
|
|
898
1561
|
return;
|
|
899
1562
|
}
|
|
900
1563
|
const resolvedPath = daemonPath;
|
|
1564
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
901
1565
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
902
1566
|
`);
|
|
903
|
-
const logPath =
|
|
1567
|
+
const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
|
|
904
1568
|
let stderrFd = "ignore";
|
|
905
1569
|
try {
|
|
906
1570
|
stderrFd = openSync(logPath, "a");
|
|
@@ -918,7 +1582,8 @@ function spawnDaemon() {
|
|
|
918
1582
|
TMUX_PANE: void 0,
|
|
919
1583
|
// Prevents resolveExeSession() from scoping to one session
|
|
920
1584
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
921
|
-
EXE_DAEMON_PID: PID_PATH
|
|
1585
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
1586
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
922
1587
|
}
|
|
923
1588
|
});
|
|
924
1589
|
child.unref();
|
|
@@ -1025,13 +1690,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1025
1690
|
return;
|
|
1026
1691
|
}
|
|
1027
1692
|
const id = randomUUID();
|
|
1693
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
1028
1694
|
const timer = setTimeout(() => {
|
|
1029
1695
|
_pending.delete(id);
|
|
1030
1696
|
resolve({ error: "Request timeout" });
|
|
1031
1697
|
}, timeoutMs);
|
|
1032
1698
|
_pending.set(id, { resolve, timer });
|
|
1033
1699
|
try {
|
|
1034
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1700
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
1035
1701
|
} catch {
|
|
1036
1702
|
clearTimeout(timer);
|
|
1037
1703
|
_pending.delete(id);
|
|
@@ -1042,17 +1708,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
1042
1708
|
function isClientConnected() {
|
|
1043
1709
|
return _connected;
|
|
1044
1710
|
}
|
|
1045
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
1711
|
+
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;
|
|
1046
1712
|
var init_exe_daemon_client = __esm({
|
|
1047
1713
|
"src/lib/exe-daemon-client.ts"() {
|
|
1048
1714
|
"use strict";
|
|
1049
1715
|
init_config();
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1716
|
+
init_daemon_auth();
|
|
1717
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
|
|
1718
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
|
|
1719
|
+
SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1053
1720
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1054
1721
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1055
1722
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
1723
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
1056
1724
|
_socket = null;
|
|
1057
1725
|
_connected = false;
|
|
1058
1726
|
_buffer = "";
|
|
@@ -1131,7 +1799,7 @@ __export(db_daemon_client_exports, {
|
|
|
1131
1799
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1132
1800
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1133
1801
|
});
|
|
1134
|
-
function
|
|
1802
|
+
function normalizeStatement2(stmt) {
|
|
1135
1803
|
if (typeof stmt === "string") {
|
|
1136
1804
|
return { sql: stmt, args: [] };
|
|
1137
1805
|
}
|
|
@@ -1155,7 +1823,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1155
1823
|
if (!_useDaemon || !isClientConnected()) {
|
|
1156
1824
|
return fallbackClient.execute(stmt);
|
|
1157
1825
|
}
|
|
1158
|
-
const { sql, args } =
|
|
1826
|
+
const { sql, args } = normalizeStatement2(stmt);
|
|
1159
1827
|
const response = await sendDaemonRequest({
|
|
1160
1828
|
type: "db-execute",
|
|
1161
1829
|
sql,
|
|
@@ -1180,7 +1848,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1180
1848
|
if (!_useDaemon || !isClientConnected()) {
|
|
1181
1849
|
return fallbackClient.batch(stmts, mode);
|
|
1182
1850
|
}
|
|
1183
|
-
const statements = stmts.map(
|
|
1851
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1184
1852
|
const response = await sendDaemonRequest({
|
|
1185
1853
|
type: "db-batch",
|
|
1186
1854
|
statements,
|
|
@@ -1275,6 +1943,18 @@ __export(database_exports, {
|
|
|
1275
1943
|
});
|
|
1276
1944
|
import { createClient } from "@libsql/client";
|
|
1277
1945
|
async function initDatabase(config) {
|
|
1946
|
+
if (_walCheckpointTimer) {
|
|
1947
|
+
clearInterval(_walCheckpointTimer);
|
|
1948
|
+
_walCheckpointTimer = null;
|
|
1949
|
+
}
|
|
1950
|
+
if (_daemonClient) {
|
|
1951
|
+
_daemonClient.close();
|
|
1952
|
+
_daemonClient = null;
|
|
1953
|
+
}
|
|
1954
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1955
|
+
_adapterClient.close();
|
|
1956
|
+
}
|
|
1957
|
+
_adapterClient = null;
|
|
1278
1958
|
if (_client) {
|
|
1279
1959
|
_client.close();
|
|
1280
1960
|
_client = null;
|
|
@@ -1288,6 +1968,7 @@ async function initDatabase(config) {
|
|
|
1288
1968
|
}
|
|
1289
1969
|
_client = createClient(opts);
|
|
1290
1970
|
_resilientClient = wrapWithRetry(_client);
|
|
1971
|
+
_adapterClient = _resilientClient;
|
|
1291
1972
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1292
1973
|
});
|
|
1293
1974
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1298,14 +1979,20 @@ async function initDatabase(config) {
|
|
|
1298
1979
|
});
|
|
1299
1980
|
}, 3e4);
|
|
1300
1981
|
_walCheckpointTimer.unref();
|
|
1982
|
+
if (process.env.DATABASE_URL) {
|
|
1983
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1984
|
+
}
|
|
1301
1985
|
}
|
|
1302
1986
|
function isInitialized() {
|
|
1303
|
-
return _client !== null;
|
|
1987
|
+
return _adapterClient !== null || _client !== null;
|
|
1304
1988
|
}
|
|
1305
1989
|
function getClient() {
|
|
1306
|
-
if (!
|
|
1990
|
+
if (!_adapterClient) {
|
|
1307
1991
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1308
1992
|
}
|
|
1993
|
+
if (process.env.DATABASE_URL) {
|
|
1994
|
+
return _adapterClient;
|
|
1995
|
+
}
|
|
1309
1996
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1310
1997
|
return _resilientClient;
|
|
1311
1998
|
}
|
|
@@ -1315,6 +2002,7 @@ function getClient() {
|
|
|
1315
2002
|
return _resilientClient;
|
|
1316
2003
|
}
|
|
1317
2004
|
async function initDaemonClient() {
|
|
2005
|
+
if (process.env.DATABASE_URL) return;
|
|
1318
2006
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1319
2007
|
if (!_resilientClient) return;
|
|
1320
2008
|
try {
|
|
@@ -1611,6 +2299,7 @@ async function ensureSchema() {
|
|
|
1611
2299
|
project TEXT NOT NULL,
|
|
1612
2300
|
summary TEXT NOT NULL,
|
|
1613
2301
|
task_file TEXT,
|
|
2302
|
+
session_scope TEXT,
|
|
1614
2303
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1615
2304
|
created_at TEXT NOT NULL
|
|
1616
2305
|
);
|
|
@@ -1619,7 +2308,7 @@ async function ensureSchema() {
|
|
|
1619
2308
|
ON notifications(read);
|
|
1620
2309
|
|
|
1621
2310
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1622
|
-
ON notifications(agent_id);
|
|
2311
|
+
ON notifications(agent_id, session_scope);
|
|
1623
2312
|
|
|
1624
2313
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1625
2314
|
ON notifications(task_file);
|
|
@@ -1657,6 +2346,7 @@ async function ensureSchema() {
|
|
|
1657
2346
|
target_agent TEXT NOT NULL,
|
|
1658
2347
|
target_project TEXT,
|
|
1659
2348
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
2349
|
+
session_scope TEXT,
|
|
1660
2350
|
content TEXT NOT NULL,
|
|
1661
2351
|
priority TEXT DEFAULT 'normal',
|
|
1662
2352
|
status TEXT DEFAULT 'pending',
|
|
@@ -1670,10 +2360,31 @@ async function ensureSchema() {
|
|
|
1670
2360
|
);
|
|
1671
2361
|
|
|
1672
2362
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1673
|
-
ON messages(target_agent, status);
|
|
2363
|
+
ON messages(target_agent, session_scope, status);
|
|
1674
2364
|
|
|
1675
2365
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1676
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
2366
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
2367
|
+
`);
|
|
2368
|
+
try {
|
|
2369
|
+
await client.execute({
|
|
2370
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
2371
|
+
args: []
|
|
2372
|
+
});
|
|
2373
|
+
} catch {
|
|
2374
|
+
}
|
|
2375
|
+
try {
|
|
2376
|
+
await client.execute({
|
|
2377
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
2378
|
+
args: []
|
|
2379
|
+
});
|
|
2380
|
+
} catch {
|
|
2381
|
+
}
|
|
2382
|
+
await client.executeMultiple(`
|
|
2383
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
2384
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
2385
|
+
|
|
2386
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
2387
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1677
2388
|
`);
|
|
1678
2389
|
try {
|
|
1679
2390
|
await client.execute({
|
|
@@ -2257,46 +2968,66 @@ async function ensureSchema() {
|
|
|
2257
2968
|
} catch {
|
|
2258
2969
|
}
|
|
2259
2970
|
}
|
|
2971
|
+
try {
|
|
2972
|
+
await client.execute({
|
|
2973
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2974
|
+
args: []
|
|
2975
|
+
});
|
|
2976
|
+
} catch {
|
|
2977
|
+
}
|
|
2260
2978
|
}
|
|
2261
2979
|
async function disposeDatabase() {
|
|
2980
|
+
if (_walCheckpointTimer) {
|
|
2981
|
+
clearInterval(_walCheckpointTimer);
|
|
2982
|
+
_walCheckpointTimer = null;
|
|
2983
|
+
}
|
|
2262
2984
|
if (_daemonClient) {
|
|
2263
2985
|
_daemonClient.close();
|
|
2264
2986
|
_daemonClient = null;
|
|
2265
2987
|
}
|
|
2988
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2989
|
+
_adapterClient.close();
|
|
2990
|
+
}
|
|
2991
|
+
_adapterClient = null;
|
|
2266
2992
|
if (_client) {
|
|
2267
2993
|
_client.close();
|
|
2268
2994
|
_client = null;
|
|
2269
2995
|
_resilientClient = null;
|
|
2270
2996
|
}
|
|
2271
2997
|
}
|
|
2272
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
2998
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2273
2999
|
var init_database = __esm({
|
|
2274
3000
|
"src/lib/database.ts"() {
|
|
2275
3001
|
"use strict";
|
|
2276
3002
|
init_db_retry();
|
|
2277
3003
|
init_employees();
|
|
3004
|
+
init_database_adapter();
|
|
2278
3005
|
_client = null;
|
|
2279
3006
|
_resilientClient = null;
|
|
2280
3007
|
_walCheckpointTimer = null;
|
|
2281
3008
|
_daemonClient = null;
|
|
3009
|
+
_adapterClient = null;
|
|
2282
3010
|
initTurso = initDatabase;
|
|
2283
3011
|
disposeTurso = disposeDatabase;
|
|
2284
3012
|
}
|
|
2285
3013
|
});
|
|
2286
3014
|
|
|
2287
3015
|
// src/lib/license.ts
|
|
2288
|
-
import { readFileSync as
|
|
3016
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
2289
3017
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2290
|
-
import
|
|
3018
|
+
import { createRequire as createRequire2 } from "module";
|
|
3019
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3020
|
+
import os7 from "os";
|
|
3021
|
+
import path9 from "path";
|
|
2291
3022
|
import { jwtVerify, importSPKI } from "jose";
|
|
2292
3023
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
2293
3024
|
var init_license = __esm({
|
|
2294
3025
|
"src/lib/license.ts"() {
|
|
2295
3026
|
"use strict";
|
|
2296
3027
|
init_config();
|
|
2297
|
-
LICENSE_PATH =
|
|
2298
|
-
CACHE_PATH =
|
|
2299
|
-
DEVICE_ID_PATH =
|
|
3028
|
+
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3029
|
+
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3030
|
+
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
2300
3031
|
PLAN_LIMITS = {
|
|
2301
3032
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
2302
3033
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -2308,12 +3039,12 @@ var init_license = __esm({
|
|
|
2308
3039
|
});
|
|
2309
3040
|
|
|
2310
3041
|
// src/lib/plan-limits.ts
|
|
2311
|
-
import { readFileSync as
|
|
2312
|
-
import
|
|
3042
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
3043
|
+
import path10 from "path";
|
|
2313
3044
|
function getLicenseSync() {
|
|
2314
3045
|
try {
|
|
2315
|
-
if (!
|
|
2316
|
-
const raw = JSON.parse(
|
|
3046
|
+
if (!existsSync10(CACHE_PATH2)) return freeLicense();
|
|
3047
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
2317
3048
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2318
3049
|
const parts = raw.token.split(".");
|
|
2319
3050
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2351,8 +3082,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2351
3082
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2352
3083
|
let count = 0;
|
|
2353
3084
|
try {
|
|
2354
|
-
if (
|
|
2355
|
-
const raw =
|
|
3085
|
+
if (existsSync10(filePath)) {
|
|
3086
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
2356
3087
|
const employees = JSON.parse(raw);
|
|
2357
3088
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2358
3089
|
}
|
|
@@ -2381,29 +3112,30 @@ var init_plan_limits = __esm({
|
|
|
2381
3112
|
this.name = "PlanLimitError";
|
|
2382
3113
|
}
|
|
2383
3114
|
};
|
|
2384
|
-
CACHE_PATH2 =
|
|
3115
|
+
CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
2385
3116
|
}
|
|
2386
3117
|
});
|
|
2387
3118
|
|
|
2388
3119
|
// src/lib/notifications.ts
|
|
2389
|
-
import
|
|
2390
|
-
import
|
|
2391
|
-
import
|
|
3120
|
+
import crypto2 from "crypto";
|
|
3121
|
+
import path11 from "path";
|
|
3122
|
+
import os8 from "os";
|
|
2392
3123
|
import {
|
|
2393
|
-
readFileSync as
|
|
3124
|
+
readFileSync as readFileSync10,
|
|
2394
3125
|
readdirSync,
|
|
2395
3126
|
unlinkSync as unlinkSync3,
|
|
2396
|
-
existsSync as
|
|
3127
|
+
existsSync as existsSync11,
|
|
2397
3128
|
rmdirSync
|
|
2398
3129
|
} from "fs";
|
|
2399
3130
|
async function writeNotification(notification) {
|
|
2400
3131
|
try {
|
|
2401
3132
|
const client = getClient();
|
|
2402
|
-
const id =
|
|
3133
|
+
const id = crypto2.randomUUID();
|
|
2403
3134
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3135
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2404
3136
|
await client.execute({
|
|
2405
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2406
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
3137
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
3138
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2407
3139
|
args: [
|
|
2408
3140
|
id,
|
|
2409
3141
|
notification.agentId,
|
|
@@ -2412,6 +3144,7 @@ async function writeNotification(notification) {
|
|
|
2412
3144
|
notification.project,
|
|
2413
3145
|
notification.summary,
|
|
2414
3146
|
notification.taskFile ?? null,
|
|
3147
|
+
sessionScope,
|
|
2415
3148
|
now
|
|
2416
3149
|
]
|
|
2417
3150
|
});
|
|
@@ -2420,12 +3153,14 @@ async function writeNotification(notification) {
|
|
|
2420
3153
|
`);
|
|
2421
3154
|
}
|
|
2422
3155
|
}
|
|
2423
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
3156
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2424
3157
|
try {
|
|
2425
3158
|
const client = getClient();
|
|
3159
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2426
3160
|
await client.execute({
|
|
2427
|
-
sql:
|
|
2428
|
-
|
|
3161
|
+
sql: `UPDATE notifications SET read = 1
|
|
3162
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
3163
|
+
args: [taskFile, ...scope.args]
|
|
2429
3164
|
});
|
|
2430
3165
|
} catch {
|
|
2431
3166
|
}
|
|
@@ -2434,11 +3169,12 @@ var init_notifications = __esm({
|
|
|
2434
3169
|
"src/lib/notifications.ts"() {
|
|
2435
3170
|
"use strict";
|
|
2436
3171
|
init_database();
|
|
3172
|
+
init_task_scope();
|
|
2437
3173
|
}
|
|
2438
3174
|
});
|
|
2439
3175
|
|
|
2440
3176
|
// src/lib/session-kill-telemetry.ts
|
|
2441
|
-
import
|
|
3177
|
+
import crypto3 from "crypto";
|
|
2442
3178
|
async function recordSessionKill(input) {
|
|
2443
3179
|
try {
|
|
2444
3180
|
const client = getClient();
|
|
@@ -2448,7 +3184,7 @@ async function recordSessionKill(input) {
|
|
|
2448
3184
|
ticks_idle, estimated_tokens_saved)
|
|
2449
3185
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2450
3186
|
args: [
|
|
2451
|
-
|
|
3187
|
+
crypto3.randomUUID(),
|
|
2452
3188
|
input.sessionName,
|
|
2453
3189
|
input.agentId,
|
|
2454
3190
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2527,12 +3263,12 @@ var init_state_bus = __esm({
|
|
|
2527
3263
|
});
|
|
2528
3264
|
|
|
2529
3265
|
// src/lib/tasks-crud.ts
|
|
2530
|
-
import
|
|
2531
|
-
import
|
|
2532
|
-
import
|
|
3266
|
+
import crypto4 from "crypto";
|
|
3267
|
+
import path12 from "path";
|
|
3268
|
+
import os9 from "os";
|
|
2533
3269
|
import { execSync as execSync4 } from "child_process";
|
|
2534
3270
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2535
|
-
import { existsSync as
|
|
3271
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
2536
3272
|
async function writeCheckpoint(input) {
|
|
2537
3273
|
const client = getClient();
|
|
2538
3274
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2648,7 +3384,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
2648
3384
|
}
|
|
2649
3385
|
async function createTaskCore(input) {
|
|
2650
3386
|
const client = getClient();
|
|
2651
|
-
const id =
|
|
3387
|
+
const id = crypto4.randomUUID();
|
|
2652
3388
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2653
3389
|
const slug = slugify(input.title);
|
|
2654
3390
|
let earlySessionScope = null;
|
|
@@ -2707,8 +3443,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2707
3443
|
}
|
|
2708
3444
|
if (input.baseDir) {
|
|
2709
3445
|
try {
|
|
2710
|
-
await mkdir3(
|
|
2711
|
-
await mkdir3(
|
|
3446
|
+
await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3447
|
+
await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2712
3448
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2713
3449
|
await ensureGitignoreExe(input.baseDir);
|
|
2714
3450
|
} catch {
|
|
@@ -2744,13 +3480,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2744
3480
|
});
|
|
2745
3481
|
if (input.baseDir) {
|
|
2746
3482
|
try {
|
|
2747
|
-
const EXE_OS_DIR =
|
|
2748
|
-
const mdPath =
|
|
2749
|
-
const mdDir =
|
|
2750
|
-
if (!
|
|
3483
|
+
const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
|
|
3484
|
+
const mdPath = path12.join(EXE_OS_DIR, taskFile);
|
|
3485
|
+
const mdDir = path12.dirname(mdPath);
|
|
3486
|
+
if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2751
3487
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2752
3488
|
const mdContent = `# ${input.title}
|
|
2753
3489
|
|
|
3490
|
+
## MANDATORY: When done
|
|
3491
|
+
|
|
3492
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3493
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3494
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3495
|
+
|
|
2754
3496
|
**ID:** ${id}
|
|
2755
3497
|
**Status:** ${initialStatus}
|
|
2756
3498
|
**Priority:** ${input.priority}
|
|
@@ -2764,12 +3506,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2764
3506
|
## Context
|
|
2765
3507
|
|
|
2766
3508
|
${input.context}
|
|
2767
|
-
|
|
2768
|
-
## MANDATORY: When done
|
|
2769
|
-
|
|
2770
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2771
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2772
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2773
3509
|
`;
|
|
2774
3510
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2775
3511
|
} catch (err) {
|
|
@@ -3018,7 +3754,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3018
3754
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3019
3755
|
} catch {
|
|
3020
3756
|
}
|
|
3021
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3757
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3022
3758
|
try {
|
|
3023
3759
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3024
3760
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -3047,9 +3783,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3047
3783
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3048
3784
|
}
|
|
3049
3785
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3050
|
-
const archPath =
|
|
3786
|
+
const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3051
3787
|
try {
|
|
3052
|
-
if (
|
|
3788
|
+
if (existsSync12(archPath)) return;
|
|
3053
3789
|
const template = [
|
|
3054
3790
|
`# ${projectName} \u2014 System Architecture`,
|
|
3055
3791
|
"",
|
|
@@ -3082,10 +3818,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3082
3818
|
}
|
|
3083
3819
|
}
|
|
3084
3820
|
async function ensureGitignoreExe(baseDir) {
|
|
3085
|
-
const gitignorePath =
|
|
3821
|
+
const gitignorePath = path12.join(baseDir, ".gitignore");
|
|
3086
3822
|
try {
|
|
3087
|
-
if (
|
|
3088
|
-
const content =
|
|
3823
|
+
if (existsSync12(gitignorePath)) {
|
|
3824
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
3089
3825
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3090
3826
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3091
3827
|
} else {
|
|
@@ -3116,58 +3852,42 @@ var init_tasks_crud = __esm({
|
|
|
3116
3852
|
});
|
|
3117
3853
|
|
|
3118
3854
|
// src/lib/tasks-review.ts
|
|
3119
|
-
import
|
|
3120
|
-
import { existsSync as
|
|
3855
|
+
import path13 from "path";
|
|
3856
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
3121
3857
|
async function countPendingReviews(sessionScope) {
|
|
3122
3858
|
const client = getClient();
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
args: [sessionScope]
|
|
3127
|
-
});
|
|
3128
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3129
|
-
}
|
|
3859
|
+
const scope = strictSessionScopeFilter(
|
|
3860
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3861
|
+
);
|
|
3130
3862
|
const result = await client.execute({
|
|
3131
|
-
sql:
|
|
3132
|
-
|
|
3863
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3864
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3865
|
+
args: [...scope.args]
|
|
3133
3866
|
});
|
|
3134
3867
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3135
3868
|
}
|
|
3136
3869
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
3137
3870
|
const client = getClient();
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
3142
|
-
AND session_scope = ?`,
|
|
3143
|
-
args: [sinceIso, sessionScope]
|
|
3144
|
-
});
|
|
3145
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
3146
|
-
}
|
|
3871
|
+
const scope = strictSessionScopeFilter(
|
|
3872
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3873
|
+
);
|
|
3147
3874
|
const result = await client.execute({
|
|
3148
3875
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3149
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
3150
|
-
args: [sinceIso]
|
|
3876
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3877
|
+
args: [sinceIso, ...scope.args]
|
|
3151
3878
|
});
|
|
3152
3879
|
return Number(result.rows[0]?.cnt) || 0;
|
|
3153
3880
|
}
|
|
3154
3881
|
async function listPendingReviews(limit, sessionScope) {
|
|
3155
3882
|
const client = getClient();
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
WHERE status = 'needs_review'
|
|
3160
|
-
AND session_scope = ?
|
|
3161
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
3162
|
-
args: [sessionScope, limit]
|
|
3163
|
-
});
|
|
3164
|
-
return result2.rows;
|
|
3165
|
-
}
|
|
3883
|
+
const scope = strictSessionScopeFilter(
|
|
3884
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3885
|
+
);
|
|
3166
3886
|
const result = await client.execute({
|
|
3167
3887
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
3168
|
-
WHERE status = 'needs_review'
|
|
3888
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
3169
3889
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
3170
|
-
args: [limit]
|
|
3890
|
+
args: [...scope.args, limit]
|
|
3171
3891
|
});
|
|
3172
3892
|
return result.rows;
|
|
3173
3893
|
}
|
|
@@ -3179,7 +3899,7 @@ async function cleanupOrphanedReviews() {
|
|
|
3179
3899
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
3180
3900
|
AND assigned_by = 'system'
|
|
3181
3901
|
AND title LIKE 'Review:%'
|
|
3182
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3902
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
3183
3903
|
args: [now]
|
|
3184
3904
|
});
|
|
3185
3905
|
const r1b = await client.execute({
|
|
@@ -3298,11 +4018,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3298
4018
|
);
|
|
3299
4019
|
}
|
|
3300
4020
|
try {
|
|
3301
|
-
const cacheDir =
|
|
3302
|
-
if (
|
|
4021
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4022
|
+
if (existsSync13(cacheDir)) {
|
|
3303
4023
|
for (const f of readdirSync2(cacheDir)) {
|
|
3304
4024
|
if (f.startsWith("review-notified-")) {
|
|
3305
|
-
unlinkSync4(
|
|
4025
|
+
unlinkSync4(path13.join(cacheDir, f));
|
|
3306
4026
|
}
|
|
3307
4027
|
}
|
|
3308
4028
|
}
|
|
@@ -3319,11 +4039,12 @@ var init_tasks_review = __esm({
|
|
|
3319
4039
|
init_tmux_routing();
|
|
3320
4040
|
init_session_key();
|
|
3321
4041
|
init_state_bus();
|
|
4042
|
+
init_task_scope();
|
|
3322
4043
|
}
|
|
3323
4044
|
});
|
|
3324
4045
|
|
|
3325
4046
|
// src/lib/tasks-chain.ts
|
|
3326
|
-
import
|
|
4047
|
+
import path14 from "path";
|
|
3327
4048
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
3328
4049
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3329
4050
|
const client = getClient();
|
|
@@ -3340,7 +4061,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3340
4061
|
});
|
|
3341
4062
|
for (const ur of unblockedRows.rows) {
|
|
3342
4063
|
try {
|
|
3343
|
-
const ubFile =
|
|
4064
|
+
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
3344
4065
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
3345
4066
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3346
4067
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3375,7 +4096,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
3375
4096
|
const scScope = sessionScopeFilter();
|
|
3376
4097
|
const remaining = await client.execute({
|
|
3377
4098
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3378
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
4099
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
3379
4100
|
args: [parentTaskId, ...scScope.args]
|
|
3380
4101
|
});
|
|
3381
4102
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -3414,7 +4135,7 @@ __export(project_name_exports, {
|
|
|
3414
4135
|
getProjectName: () => getProjectName
|
|
3415
4136
|
});
|
|
3416
4137
|
import { execSync as execSync5 } from "child_process";
|
|
3417
|
-
import
|
|
4138
|
+
import path15 from "path";
|
|
3418
4139
|
function getProjectName(cwd) {
|
|
3419
4140
|
const dir = cwd ?? process.cwd();
|
|
3420
4141
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3427,7 +4148,7 @@ function getProjectName(cwd) {
|
|
|
3427
4148
|
timeout: 2e3,
|
|
3428
4149
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3429
4150
|
}).trim();
|
|
3430
|
-
repoRoot =
|
|
4151
|
+
repoRoot = path15.dirname(gitCommonDir);
|
|
3431
4152
|
} catch {
|
|
3432
4153
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
3433
4154
|
cwd: dir,
|
|
@@ -3436,11 +4157,11 @@ function getProjectName(cwd) {
|
|
|
3436
4157
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3437
4158
|
}).trim();
|
|
3438
4159
|
}
|
|
3439
|
-
_cached2 =
|
|
4160
|
+
_cached2 = path15.basename(repoRoot);
|
|
3440
4161
|
_cachedCwd = dir;
|
|
3441
4162
|
return _cached2;
|
|
3442
4163
|
} catch {
|
|
3443
|
-
_cached2 =
|
|
4164
|
+
_cached2 = path15.basename(dir);
|
|
3444
4165
|
_cachedCwd = dir;
|
|
3445
4166
|
return _cached2;
|
|
3446
4167
|
}
|
|
@@ -3587,10 +4308,10 @@ var init_tasks_notify = __esm({
|
|
|
3587
4308
|
});
|
|
3588
4309
|
|
|
3589
4310
|
// src/lib/behaviors.ts
|
|
3590
|
-
import
|
|
4311
|
+
import crypto5 from "crypto";
|
|
3591
4312
|
async function storeBehavior(opts) {
|
|
3592
4313
|
const client = getClient();
|
|
3593
|
-
const id =
|
|
4314
|
+
const id = crypto5.randomUUID();
|
|
3594
4315
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3595
4316
|
await client.execute({
|
|
3596
4317
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -3619,7 +4340,7 @@ __export(skill_learning_exports, {
|
|
|
3619
4340
|
storeTrajectory: () => storeTrajectory,
|
|
3620
4341
|
sweepTrajectories: () => sweepTrajectories
|
|
3621
4342
|
});
|
|
3622
|
-
import
|
|
4343
|
+
import crypto6 from "crypto";
|
|
3623
4344
|
async function extractTrajectory(taskId, agentId) {
|
|
3624
4345
|
const client = getClient();
|
|
3625
4346
|
const result = await client.execute({
|
|
@@ -3648,11 +4369,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
3648
4369
|
return signature;
|
|
3649
4370
|
}
|
|
3650
4371
|
function hashSignature(signature) {
|
|
3651
|
-
return
|
|
4372
|
+
return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
3652
4373
|
}
|
|
3653
4374
|
async function storeTrajectory(opts) {
|
|
3654
4375
|
const client = getClient();
|
|
3655
|
-
const id =
|
|
4376
|
+
const id = crypto6.randomUUID();
|
|
3656
4377
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3657
4378
|
const signatureHash = hashSignature(opts.signature);
|
|
3658
4379
|
await client.execute({
|
|
@@ -3917,8 +4638,8 @@ __export(tasks_exports, {
|
|
|
3917
4638
|
updateTaskStatus: () => updateTaskStatus,
|
|
3918
4639
|
writeCheckpoint: () => writeCheckpoint
|
|
3919
4640
|
});
|
|
3920
|
-
import
|
|
3921
|
-
import { writeFileSync as
|
|
4641
|
+
import path16 from "path";
|
|
4642
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3922
4643
|
async function createTask(input) {
|
|
3923
4644
|
const result = await createTaskCore(input);
|
|
3924
4645
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3937,12 +4658,12 @@ async function updateTask(input) {
|
|
|
3937
4658
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3938
4659
|
try {
|
|
3939
4660
|
const agent = String(row.assigned_to);
|
|
3940
|
-
const cacheDir =
|
|
3941
|
-
const cachePath =
|
|
4661
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
4662
|
+
const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
|
|
3942
4663
|
if (input.status === "in_progress") {
|
|
3943
4664
|
mkdirSync5(cacheDir, { recursive: true });
|
|
3944
|
-
|
|
3945
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4665
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4666
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3946
4667
|
try {
|
|
3947
4668
|
unlinkSync5(cachePath);
|
|
3948
4669
|
} catch {
|
|
@@ -3950,10 +4671,10 @@ async function updateTask(input) {
|
|
|
3950
4671
|
}
|
|
3951
4672
|
} catch {
|
|
3952
4673
|
}
|
|
3953
|
-
if (input.status === "done") {
|
|
4674
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3954
4675
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3955
4676
|
}
|
|
3956
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4677
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3957
4678
|
try {
|
|
3958
4679
|
const client = getClient();
|
|
3959
4680
|
const taskTitle = String(row.title);
|
|
@@ -3969,7 +4690,7 @@ async function updateTask(input) {
|
|
|
3969
4690
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3970
4691
|
try {
|
|
3971
4692
|
const draftClient = getClient();
|
|
3972
|
-
if (input.status === "done") {
|
|
4693
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3973
4694
|
await draftClient.execute({
|
|
3974
4695
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3975
4696
|
args: [assignedAgent]
|
|
@@ -3986,7 +4707,7 @@ async function updateTask(input) {
|
|
|
3986
4707
|
try {
|
|
3987
4708
|
const client = getClient();
|
|
3988
4709
|
const cascaded = await client.execute({
|
|
3989
|
-
sql: `UPDATE tasks SET status = '
|
|
4710
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3990
4711
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3991
4712
|
args: [now, taskId]
|
|
3992
4713
|
});
|
|
@@ -3999,14 +4720,14 @@ async function updateTask(input) {
|
|
|
3999
4720
|
} catch {
|
|
4000
4721
|
}
|
|
4001
4722
|
}
|
|
4002
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4723
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
4003
4724
|
if (isTerminal) {
|
|
4004
4725
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4005
4726
|
if (!isCoordinator) {
|
|
4006
4727
|
notifyTaskDone();
|
|
4007
4728
|
}
|
|
4008
4729
|
await markTaskNotificationsRead(taskFile);
|
|
4009
|
-
if (input.status === "done") {
|
|
4730
|
+
if (input.status === "done" || input.status === "closed") {
|
|
4010
4731
|
try {
|
|
4011
4732
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4012
4733
|
} catch {
|
|
@@ -4026,7 +4747,7 @@ async function updateTask(input) {
|
|
|
4026
4747
|
}
|
|
4027
4748
|
}
|
|
4028
4749
|
}
|
|
4029
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4750
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4030
4751
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4031
4752
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4032
4753
|
taskId,
|
|
@@ -4398,6 +5119,7 @@ __export(tmux_routing_exports, {
|
|
|
4398
5119
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
4399
5120
|
isExeSession: () => isExeSession,
|
|
4400
5121
|
isSessionBusy: () => isSessionBusy,
|
|
5122
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
4401
5123
|
notifyParentExe: () => notifyParentExe,
|
|
4402
5124
|
parseParentExe: () => parseParentExe,
|
|
4403
5125
|
registerParentExe: () => registerParentExe,
|
|
@@ -4408,13 +5130,13 @@ __export(tmux_routing_exports, {
|
|
|
4408
5130
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4409
5131
|
});
|
|
4410
5132
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
4411
|
-
import { readFileSync as
|
|
4412
|
-
import
|
|
4413
|
-
import
|
|
5133
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
5134
|
+
import path17 from "path";
|
|
5135
|
+
import os10 from "os";
|
|
4414
5136
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4415
5137
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4416
5138
|
function spawnLockPath(sessionName) {
|
|
4417
|
-
return
|
|
5139
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4418
5140
|
}
|
|
4419
5141
|
function isProcessAlive(pid) {
|
|
4420
5142
|
try {
|
|
@@ -4425,13 +5147,13 @@ function isProcessAlive(pid) {
|
|
|
4425
5147
|
}
|
|
4426
5148
|
}
|
|
4427
5149
|
function acquireSpawnLock2(sessionName) {
|
|
4428
|
-
if (!
|
|
5150
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4429
5151
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4430
5152
|
}
|
|
4431
5153
|
const lockFile = spawnLockPath(sessionName);
|
|
4432
|
-
if (
|
|
5154
|
+
if (existsSync14(lockFile)) {
|
|
4433
5155
|
try {
|
|
4434
|
-
const lock = JSON.parse(
|
|
5156
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4435
5157
|
const age = Date.now() - lock.timestamp;
|
|
4436
5158
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4437
5159
|
return false;
|
|
@@ -4439,7 +5161,7 @@ function acquireSpawnLock2(sessionName) {
|
|
|
4439
5161
|
} catch {
|
|
4440
5162
|
}
|
|
4441
5163
|
}
|
|
4442
|
-
|
|
5164
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4443
5165
|
return true;
|
|
4444
5166
|
}
|
|
4445
5167
|
function releaseSpawnLock2(sessionName) {
|
|
@@ -4451,13 +5173,13 @@ function releaseSpawnLock2(sessionName) {
|
|
|
4451
5173
|
function resolveBehaviorsExporterScript() {
|
|
4452
5174
|
try {
|
|
4453
5175
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
4454
|
-
const scriptPath =
|
|
4455
|
-
|
|
5176
|
+
const scriptPath = path17.join(
|
|
5177
|
+
path17.dirname(thisFile),
|
|
4456
5178
|
"..",
|
|
4457
5179
|
"bin",
|
|
4458
5180
|
"exe-export-behaviors.js"
|
|
4459
5181
|
);
|
|
4460
|
-
return
|
|
5182
|
+
return existsSync14(scriptPath) ? scriptPath : null;
|
|
4461
5183
|
} catch {
|
|
4462
5184
|
return null;
|
|
4463
5185
|
}
|
|
@@ -4523,12 +5245,12 @@ function extractRootExe(name) {
|
|
|
4523
5245
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4524
5246
|
}
|
|
4525
5247
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4526
|
-
if (!
|
|
5248
|
+
if (!existsSync14(SESSION_CACHE)) {
|
|
4527
5249
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4528
5250
|
}
|
|
4529
5251
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4530
|
-
const filePath =
|
|
4531
|
-
|
|
5252
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5253
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4532
5254
|
parentExe: rootExe,
|
|
4533
5255
|
dispatchedBy: dispatchedBy || rootExe,
|
|
4534
5256
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4536,7 +5258,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4536
5258
|
}
|
|
4537
5259
|
function getParentExe(sessionKey) {
|
|
4538
5260
|
try {
|
|
4539
|
-
const data = JSON.parse(
|
|
5261
|
+
const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4540
5262
|
return data.parentExe || null;
|
|
4541
5263
|
} catch {
|
|
4542
5264
|
return null;
|
|
@@ -4544,8 +5266,8 @@ function getParentExe(sessionKey) {
|
|
|
4544
5266
|
}
|
|
4545
5267
|
function getDispatchedBy(sessionKey) {
|
|
4546
5268
|
try {
|
|
4547
|
-
const data = JSON.parse(
|
|
4548
|
-
|
|
5269
|
+
const data = JSON.parse(readFileSync12(
|
|
5270
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4549
5271
|
"utf8"
|
|
4550
5272
|
));
|
|
4551
5273
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4615,8 +5337,8 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4615
5337
|
}
|
|
4616
5338
|
function readDebounceState() {
|
|
4617
5339
|
try {
|
|
4618
|
-
if (!
|
|
4619
|
-
const raw = JSON.parse(
|
|
5340
|
+
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
5341
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
4620
5342
|
const state = {};
|
|
4621
5343
|
for (const [key, val] of Object.entries(raw)) {
|
|
4622
5344
|
if (typeof val === "number") {
|
|
@@ -4632,8 +5354,8 @@ function readDebounceState() {
|
|
|
4632
5354
|
}
|
|
4633
5355
|
function writeDebounceState(state) {
|
|
4634
5356
|
try {
|
|
4635
|
-
if (!
|
|
4636
|
-
|
|
5357
|
+
if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5358
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4637
5359
|
} catch {
|
|
4638
5360
|
}
|
|
4639
5361
|
}
|
|
@@ -4731,8 +5453,8 @@ function sendIntercom(targetSession) {
|
|
|
4731
5453
|
try {
|
|
4732
5454
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4733
5455
|
const agent = baseAgentName(rawAgent);
|
|
4734
|
-
const markerPath =
|
|
4735
|
-
if (
|
|
5456
|
+
const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
5457
|
+
if (existsSync14(markerPath)) {
|
|
4736
5458
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4737
5459
|
return "debounced";
|
|
4738
5460
|
}
|
|
@@ -4741,8 +5463,8 @@ function sendIntercom(targetSession) {
|
|
|
4741
5463
|
try {
|
|
4742
5464
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4743
5465
|
const agent = baseAgentName(rawAgent);
|
|
4744
|
-
const taskDir =
|
|
4745
|
-
if (
|
|
5466
|
+
const taskDir = path17.join(process.cwd(), "exe", agent);
|
|
5467
|
+
if (existsSync14(taskDir)) {
|
|
4746
5468
|
const files = readdirSync3(taskDir).filter(
|
|
4747
5469
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4748
5470
|
);
|
|
@@ -4802,6 +5524,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4802
5524
|
}
|
|
4803
5525
|
return true;
|
|
4804
5526
|
}
|
|
5527
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
5528
|
+
const transport = getTransport();
|
|
5529
|
+
try {
|
|
5530
|
+
const sessions = transport.listSessions();
|
|
5531
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
5532
|
+
execSync6(
|
|
5533
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
5534
|
+
{ timeout: 3e3 }
|
|
5535
|
+
);
|
|
5536
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
5537
|
+
return true;
|
|
5538
|
+
} catch {
|
|
5539
|
+
return false;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
4805
5542
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4806
5543
|
if (isCoordinatorName(employeeName)) {
|
|
4807
5544
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4875,26 +5612,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4875
5612
|
const transport = getTransport();
|
|
4876
5613
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4877
5614
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4878
|
-
const logDir =
|
|
4879
|
-
const logFile =
|
|
4880
|
-
if (!
|
|
5615
|
+
const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
|
|
5616
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5617
|
+
if (!existsSync14(logDir)) {
|
|
4881
5618
|
mkdirSync6(logDir, { recursive: true });
|
|
4882
5619
|
}
|
|
4883
5620
|
transport.kill(sessionName);
|
|
4884
5621
|
let cleanupSuffix = "";
|
|
4885
5622
|
try {
|
|
4886
5623
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
4887
|
-
const cleanupScript =
|
|
4888
|
-
if (
|
|
5624
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5625
|
+
if (existsSync14(cleanupScript)) {
|
|
4889
5626
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4890
5627
|
}
|
|
4891
5628
|
} catch {
|
|
4892
5629
|
}
|
|
4893
5630
|
try {
|
|
4894
|
-
const claudeJsonPath =
|
|
5631
|
+
const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
|
|
4895
5632
|
let claudeJson = {};
|
|
4896
5633
|
try {
|
|
4897
|
-
claudeJson = JSON.parse(
|
|
5634
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
4898
5635
|
} catch {
|
|
4899
5636
|
}
|
|
4900
5637
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4902,17 +5639,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4902
5639
|
const trustDir = opts?.cwd ?? projectDir;
|
|
4903
5640
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4904
5641
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4905
|
-
|
|
5642
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4906
5643
|
} catch {
|
|
4907
5644
|
}
|
|
4908
5645
|
try {
|
|
4909
|
-
const settingsDir =
|
|
5646
|
+
const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
|
|
4910
5647
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4911
|
-
const projSettingsDir =
|
|
4912
|
-
const settingsPath =
|
|
5648
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
5649
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
4913
5650
|
let settings = {};
|
|
4914
5651
|
try {
|
|
4915
|
-
settings = JSON.parse(
|
|
5652
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
4916
5653
|
} catch {
|
|
4917
5654
|
}
|
|
4918
5655
|
const perms = settings.permissions ?? {};
|
|
@@ -4941,7 +5678,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4941
5678
|
perms.allow = allow;
|
|
4942
5679
|
settings.permissions = perms;
|
|
4943
5680
|
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4944
|
-
|
|
5681
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4945
5682
|
}
|
|
4946
5683
|
} catch {
|
|
4947
5684
|
}
|
|
@@ -4956,8 +5693,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4956
5693
|
let behaviorsFlag = "";
|
|
4957
5694
|
let legacyFallbackWarned = false;
|
|
4958
5695
|
if (!useExeAgent && !useBinSymlink) {
|
|
4959
|
-
const identityPath =
|
|
4960
|
-
|
|
5696
|
+
const identityPath = path17.join(
|
|
5697
|
+
os10.homedir(),
|
|
4961
5698
|
".exe-os",
|
|
4962
5699
|
"identity",
|
|
4963
5700
|
`${employeeName}.md`
|
|
@@ -4966,13 +5703,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4966
5703
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4967
5704
|
if (hasAgentFlag) {
|
|
4968
5705
|
identityFlag = ` --agent ${employeeName}`;
|
|
4969
|
-
} else if (
|
|
5706
|
+
} else if (existsSync14(identityPath)) {
|
|
4970
5707
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4971
5708
|
legacyFallbackWarned = true;
|
|
4972
5709
|
}
|
|
4973
5710
|
const behaviorsFile = exportBehaviorsSync(
|
|
4974
5711
|
employeeName,
|
|
4975
|
-
|
|
5712
|
+
path17.basename(spawnCwd),
|
|
4976
5713
|
sessionName
|
|
4977
5714
|
);
|
|
4978
5715
|
if (behaviorsFile) {
|
|
@@ -4987,16 +5724,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4987
5724
|
}
|
|
4988
5725
|
let sessionContextFlag = "";
|
|
4989
5726
|
try {
|
|
4990
|
-
const ctxDir =
|
|
5727
|
+
const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
4991
5728
|
mkdirSync6(ctxDir, { recursive: true });
|
|
4992
|
-
const ctxFile =
|
|
5729
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4993
5730
|
const ctxContent = [
|
|
4994
5731
|
`## Session Context`,
|
|
4995
5732
|
`You are running in tmux session: ${sessionName}.`,
|
|
4996
5733
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4997
5734
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4998
5735
|
].join("\n");
|
|
4999
|
-
|
|
5736
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
5000
5737
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5001
5738
|
} catch {
|
|
5002
5739
|
}
|
|
@@ -5073,8 +5810,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5073
5810
|
transport.pipeLog(sessionName, logFile);
|
|
5074
5811
|
try {
|
|
5075
5812
|
const mySession = getMySession();
|
|
5076
|
-
const dispatchInfo =
|
|
5077
|
-
|
|
5813
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5814
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
5078
5815
|
dispatchedBy: mySession,
|
|
5079
5816
|
rootExe: exeSession,
|
|
5080
5817
|
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
@@ -5148,15 +5885,15 @@ var init_tmux_routing = __esm({
|
|
|
5148
5885
|
init_intercom_queue();
|
|
5149
5886
|
init_plan_limits();
|
|
5150
5887
|
init_employees();
|
|
5151
|
-
SPAWN_LOCK_DIR =
|
|
5152
|
-
SESSION_CACHE =
|
|
5888
|
+
SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
|
|
5889
|
+
SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
|
|
5153
5890
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5154
5891
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5155
5892
|
VERIFY_PANE_LINES = 200;
|
|
5156
5893
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5157
5894
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
5158
|
-
INTERCOM_LOG2 =
|
|
5159
|
-
DEBOUNCE_FILE =
|
|
5895
|
+
INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
|
|
5896
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5160
5897
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5161
5898
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
5162
5899
|
}
|
|
@@ -5179,6 +5916,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
5179
5916
|
args: [scope]
|
|
5180
5917
|
};
|
|
5181
5918
|
}
|
|
5919
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
5920
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
5921
|
+
if (!scope) return { sql: "", args: [] };
|
|
5922
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
5923
|
+
return {
|
|
5924
|
+
sql: ` AND ${col} = ?`,
|
|
5925
|
+
args: [scope]
|
|
5926
|
+
};
|
|
5927
|
+
}
|
|
5182
5928
|
var init_task_scope = __esm({
|
|
5183
5929
|
"src/lib/task-scope.ts"() {
|
|
5184
5930
|
"use strict";
|
|
@@ -5197,14 +5943,14 @@ var init_memory = __esm({
|
|
|
5197
5943
|
|
|
5198
5944
|
// src/lib/keychain.ts
|
|
5199
5945
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5200
|
-
import { existsSync as
|
|
5201
|
-
import
|
|
5202
|
-
import
|
|
5946
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5947
|
+
import path18 from "path";
|
|
5948
|
+
import os11 from "os";
|
|
5203
5949
|
function getKeyDir() {
|
|
5204
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5950
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
|
|
5205
5951
|
}
|
|
5206
5952
|
function getKeyPath() {
|
|
5207
|
-
return
|
|
5953
|
+
return path18.join(getKeyDir(), "master.key");
|
|
5208
5954
|
}
|
|
5209
5955
|
async function tryKeytar() {
|
|
5210
5956
|
try {
|
|
@@ -5225,9 +5971,9 @@ async function getMasterKey() {
|
|
|
5225
5971
|
}
|
|
5226
5972
|
}
|
|
5227
5973
|
const keyPath = getKeyPath();
|
|
5228
|
-
if (!
|
|
5974
|
+
if (!existsSync15(keyPath)) {
|
|
5229
5975
|
process.stderr.write(
|
|
5230
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5976
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5231
5977
|
`
|
|
5232
5978
|
);
|
|
5233
5979
|
return null;
|
|
@@ -5257,6 +6003,7 @@ var shard_manager_exports = {};
|
|
|
5257
6003
|
__export(shard_manager_exports, {
|
|
5258
6004
|
disposeShards: () => disposeShards,
|
|
5259
6005
|
ensureShardSchema: () => ensureShardSchema,
|
|
6006
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
5260
6007
|
getReadyShardClient: () => getReadyShardClient,
|
|
5261
6008
|
getShardClient: () => getShardClient,
|
|
5262
6009
|
getShardsDir: () => getShardsDir,
|
|
@@ -5265,15 +6012,18 @@ __export(shard_manager_exports, {
|
|
|
5265
6012
|
listShards: () => listShards,
|
|
5266
6013
|
shardExists: () => shardExists
|
|
5267
6014
|
});
|
|
5268
|
-
import
|
|
5269
|
-
import { existsSync as
|
|
6015
|
+
import path19 from "path";
|
|
6016
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
5270
6017
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5271
6018
|
function initShardManager(encryptionKey) {
|
|
5272
6019
|
_encryptionKey = encryptionKey;
|
|
5273
|
-
if (!
|
|
6020
|
+
if (!existsSync16(SHARDS_DIR)) {
|
|
5274
6021
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
5275
6022
|
}
|
|
5276
6023
|
_shardingEnabled = true;
|
|
6024
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
6025
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
6026
|
+
_evictionTimer.unref();
|
|
5277
6027
|
}
|
|
5278
6028
|
function isShardingEnabled() {
|
|
5279
6029
|
return _shardingEnabled;
|
|
@@ -5290,21 +6040,28 @@ function getShardClient(projectName) {
|
|
|
5290
6040
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
5291
6041
|
}
|
|
5292
6042
|
const cached = _shards.get(safeName);
|
|
5293
|
-
if (cached)
|
|
5294
|
-
|
|
6043
|
+
if (cached) {
|
|
6044
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
6045
|
+
return cached;
|
|
6046
|
+
}
|
|
6047
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
6048
|
+
evictLRU();
|
|
6049
|
+
}
|
|
6050
|
+
const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
|
|
5295
6051
|
const client = createClient2({
|
|
5296
6052
|
url: `file:${dbPath}`,
|
|
5297
6053
|
encryptionKey: _encryptionKey
|
|
5298
6054
|
});
|
|
5299
6055
|
_shards.set(safeName, client);
|
|
6056
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5300
6057
|
return client;
|
|
5301
6058
|
}
|
|
5302
6059
|
function shardExists(projectName) {
|
|
5303
6060
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5304
|
-
return
|
|
6061
|
+
return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
|
|
5305
6062
|
}
|
|
5306
6063
|
function listShards() {
|
|
5307
|
-
if (!
|
|
6064
|
+
if (!existsSync16(SHARDS_DIR)) return [];
|
|
5308
6065
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5309
6066
|
}
|
|
5310
6067
|
async function ensureShardSchema(client) {
|
|
@@ -5356,6 +6113,8 @@ async function ensureShardSchema(client) {
|
|
|
5356
6113
|
for (const col of [
|
|
5357
6114
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
5358
6115
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
6116
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
6117
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
5359
6118
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
5360
6119
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
5361
6120
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -5378,7 +6137,23 @@ async function ensureShardSchema(client) {
|
|
|
5378
6137
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5379
6138
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5380
6139
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5381
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
6140
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
6141
|
+
// Metadata enrichment columns (must match database.ts)
|
|
6142
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
6143
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
6144
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
6145
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
6146
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
6147
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
6148
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
6149
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
6150
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
6151
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
6152
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
6153
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
6154
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
6155
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
6156
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
5382
6157
|
]) {
|
|
5383
6158
|
try {
|
|
5384
6159
|
await client.execute(col);
|
|
@@ -5477,21 +6252,69 @@ async function getReadyShardClient(projectName) {
|
|
|
5477
6252
|
await ensureShardSchema(client);
|
|
5478
6253
|
return client;
|
|
5479
6254
|
}
|
|
6255
|
+
function evictLRU() {
|
|
6256
|
+
let oldest = null;
|
|
6257
|
+
let oldestTime = Infinity;
|
|
6258
|
+
for (const [name, time] of _shardLastAccess) {
|
|
6259
|
+
if (time < oldestTime) {
|
|
6260
|
+
oldestTime = time;
|
|
6261
|
+
oldest = name;
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
if (oldest) {
|
|
6265
|
+
const client = _shards.get(oldest);
|
|
6266
|
+
if (client) {
|
|
6267
|
+
client.close();
|
|
6268
|
+
}
|
|
6269
|
+
_shards.delete(oldest);
|
|
6270
|
+
_shardLastAccess.delete(oldest);
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
function evictIdleShards() {
|
|
6274
|
+
const now = Date.now();
|
|
6275
|
+
const toEvict = [];
|
|
6276
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
6277
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
6278
|
+
toEvict.push(name);
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
for (const name of toEvict) {
|
|
6282
|
+
const client = _shards.get(name);
|
|
6283
|
+
if (client) {
|
|
6284
|
+
client.close();
|
|
6285
|
+
}
|
|
6286
|
+
_shards.delete(name);
|
|
6287
|
+
_shardLastAccess.delete(name);
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
function getOpenShardCount() {
|
|
6291
|
+
return _shards.size;
|
|
6292
|
+
}
|
|
5480
6293
|
function disposeShards() {
|
|
6294
|
+
if (_evictionTimer) {
|
|
6295
|
+
clearInterval(_evictionTimer);
|
|
6296
|
+
_evictionTimer = null;
|
|
6297
|
+
}
|
|
5481
6298
|
for (const [, client] of _shards) {
|
|
5482
6299
|
client.close();
|
|
5483
6300
|
}
|
|
5484
6301
|
_shards.clear();
|
|
6302
|
+
_shardLastAccess.clear();
|
|
5485
6303
|
_shardingEnabled = false;
|
|
5486
6304
|
_encryptionKey = null;
|
|
5487
6305
|
}
|
|
5488
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
6306
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
5489
6307
|
var init_shard_manager = __esm({
|
|
5490
6308
|
"src/lib/shard-manager.ts"() {
|
|
5491
6309
|
"use strict";
|
|
5492
6310
|
init_config();
|
|
5493
|
-
SHARDS_DIR =
|
|
6311
|
+
SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
|
|
6312
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
6313
|
+
MAX_OPEN_SHARDS = 10;
|
|
6314
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
5494
6315
|
_shards = /* @__PURE__ */ new Map();
|
|
6316
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
6317
|
+
_evictionTimer = null;
|
|
5495
6318
|
_encryptionKey = null;
|
|
5496
6319
|
_shardingEnabled = false;
|
|
5497
6320
|
}
|
|
@@ -6255,9 +7078,9 @@ var init_store = __esm({
|
|
|
6255
7078
|
});
|
|
6256
7079
|
|
|
6257
7080
|
// src/bin/scan-tasks.ts
|
|
6258
|
-
import { existsSync as
|
|
6259
|
-
import
|
|
6260
|
-
import
|
|
7081
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
7082
|
+
import path20 from "path";
|
|
7083
|
+
import os12 from "os";
|
|
6261
7084
|
|
|
6262
7085
|
// src/lib/is-main.ts
|
|
6263
7086
|
import { realpathSync } from "fs";
|
|
@@ -6277,27 +7100,27 @@ function isMainModule(importMetaUrl) {
|
|
|
6277
7100
|
// src/bin/scan-tasks.ts
|
|
6278
7101
|
init_session_key();
|
|
6279
7102
|
init_task_scope();
|
|
6280
|
-
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir =
|
|
7103
|
+
function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os12.homedir()) {
|
|
6281
7104
|
if (runtime === "codex") {
|
|
6282
7105
|
return null;
|
|
6283
7106
|
}
|
|
6284
7107
|
try {
|
|
6285
7108
|
if (runtime === "opencode") {
|
|
6286
|
-
const opencodeJson =
|
|
6287
|
-
if (!
|
|
7109
|
+
const opencodeJson = path20.join(homeDir, ".config", "opencode", "opencode.json");
|
|
7110
|
+
if (!existsSync17(opencodeJson)) {
|
|
6288
7111
|
return "\u26A0\uFE0F MCP config missing (~/.config/opencode/opencode.json not found) \u2014 exe-os task tools may be unavailable. Run `exe-os opencode`.\n";
|
|
6289
7112
|
}
|
|
6290
|
-
const config2 = JSON.parse(
|
|
7113
|
+
const config2 = JSON.parse(readFileSync13(opencodeJson, "utf8"));
|
|
6291
7114
|
if (!config2.mcp?.["exe-os"]?.enabled) {
|
|
6292
7115
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server is not enabled in ~/.config/opencode/opencode.json.\n";
|
|
6293
7116
|
}
|
|
6294
7117
|
return null;
|
|
6295
7118
|
}
|
|
6296
|
-
const claudeJson =
|
|
6297
|
-
if (!
|
|
7119
|
+
const claudeJson = path20.join(homeDir, ".claude.json");
|
|
7120
|
+
if (!existsSync17(claudeJson)) {
|
|
6298
7121
|
return "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n";
|
|
6299
7122
|
}
|
|
6300
|
-
const config = JSON.parse(
|
|
7123
|
+
const config = JSON.parse(readFileSync13(claudeJson, "utf8"));
|
|
6301
7124
|
const servers = config.mcpServers;
|
|
6302
7125
|
if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
|
|
6303
7126
|
return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server not configured in ~/.claude.json. close_task won't work.\n";
|
|
@@ -6462,7 +7285,7 @@ async function main() {
|
|
|
6462
7285
|
if (out) tmuxSession = out;
|
|
6463
7286
|
} catch {
|
|
6464
7287
|
}
|
|
6465
|
-
const result = await scanFromDb(agentId, tmuxSession);
|
|
7288
|
+
const result = await scanFromDb(agentId, tmuxSession, null);
|
|
6466
7289
|
switch (format) {
|
|
6467
7290
|
case "json":
|
|
6468
7291
|
console.log(formatJson(result));
|