@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -308,9 +308,34 @@ var init_provider_table = __esm({
|
|
|
308
308
|
}
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
+
// src/lib/secure-files.ts
|
|
312
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
313
|
+
import { chmod, mkdir } from "fs/promises";
|
|
314
|
+
async function ensurePrivateDir(dirPath) {
|
|
315
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
316
|
+
try {
|
|
317
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function enforcePrivateFile(filePath) {
|
|
322
|
+
try {
|
|
323
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
328
|
+
var init_secure_files = __esm({
|
|
329
|
+
"src/lib/secure-files.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
PRIVATE_DIR_MODE = 448;
|
|
332
|
+
PRIVATE_FILE_MODE = 384;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
311
336
|
// src/lib/config.ts
|
|
312
|
-
import { readFile, writeFile
|
|
313
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
337
|
+
import { readFile, writeFile } from "fs/promises";
|
|
338
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
|
|
314
339
|
import path2 from "path";
|
|
315
340
|
import os2 from "os";
|
|
316
341
|
function resolveDataDir() {
|
|
@@ -318,7 +343,7 @@ function resolveDataDir() {
|
|
|
318
343
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
319
344
|
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
320
345
|
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
321
|
-
if (!
|
|
346
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
322
347
|
try {
|
|
323
348
|
renameSync(legacyDir, newDir);
|
|
324
349
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -381,9 +406,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
381
406
|
}
|
|
382
407
|
async function loadConfig() {
|
|
383
408
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
384
|
-
await
|
|
409
|
+
await ensurePrivateDir(dir);
|
|
385
410
|
const configPath = path2.join(dir, "config.json");
|
|
386
|
-
if (!
|
|
411
|
+
if (!existsSync3(configPath)) {
|
|
387
412
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
388
413
|
}
|
|
389
414
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -396,6 +421,7 @@ async function loadConfig() {
|
|
|
396
421
|
`);
|
|
397
422
|
try {
|
|
398
423
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
424
|
+
await enforcePrivateFile(configPath);
|
|
399
425
|
} catch {
|
|
400
426
|
}
|
|
401
427
|
}
|
|
@@ -415,6 +441,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
415
441
|
var init_config = __esm({
|
|
416
442
|
"src/lib/config.ts"() {
|
|
417
443
|
"use strict";
|
|
444
|
+
init_secure_files();
|
|
418
445
|
EXE_AI_DIR = resolveDataDir();
|
|
419
446
|
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
420
447
|
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
@@ -519,10 +546,10 @@ var init_runtime_table = __esm({
|
|
|
519
546
|
});
|
|
520
547
|
|
|
521
548
|
// src/lib/agent-config.ts
|
|
522
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
549
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
523
550
|
import path3 from "path";
|
|
524
551
|
function loadAgentConfig() {
|
|
525
|
-
if (!
|
|
552
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
526
553
|
try {
|
|
527
554
|
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
528
555
|
} catch {
|
|
@@ -543,6 +570,7 @@ var init_agent_config = __esm({
|
|
|
543
570
|
"use strict";
|
|
544
571
|
init_config();
|
|
545
572
|
init_runtime_table();
|
|
573
|
+
init_secure_files();
|
|
546
574
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
547
575
|
DEFAULT_MODELS = {
|
|
548
576
|
claude: "claude-opus-4",
|
|
@@ -561,16 +589,16 @@ __export(intercom_queue_exports, {
|
|
|
561
589
|
queueIntercom: () => queueIntercom,
|
|
562
590
|
readQueue: () => readQueue
|
|
563
591
|
});
|
|
564
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as
|
|
592
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
565
593
|
import path4 from "path";
|
|
566
594
|
import os3 from "os";
|
|
567
595
|
function ensureDir() {
|
|
568
596
|
const dir = path4.dirname(QUEUE_PATH);
|
|
569
|
-
if (!
|
|
597
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
570
598
|
}
|
|
571
599
|
function readQueue() {
|
|
572
600
|
try {
|
|
573
|
-
if (!
|
|
601
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
574
602
|
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
575
603
|
} catch {
|
|
576
604
|
return [];
|
|
@@ -735,7 +763,7 @@ var init_db_retry = __esm({
|
|
|
735
763
|
|
|
736
764
|
// src/lib/employees.ts
|
|
737
765
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
738
|
-
import { existsSync as
|
|
766
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
739
767
|
import { execSync as execSync3 } from "child_process";
|
|
740
768
|
import path5 from "path";
|
|
741
769
|
import os4 from "os";
|
|
@@ -756,7 +784,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
756
784
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
757
785
|
}
|
|
758
786
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
759
|
-
if (!
|
|
787
|
+
if (!existsSync6(employeesPath)) return [];
|
|
760
788
|
try {
|
|
761
789
|
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
762
790
|
} catch {
|
|
@@ -780,7 +808,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
780
808
|
if (!emp) return false;
|
|
781
809
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
782
810
|
}
|
|
783
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
811
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
784
812
|
var init_employees = __esm({
|
|
785
813
|
"src/lib/employees.ts"() {
|
|
786
814
|
"use strict";
|
|
@@ -789,12 +817,609 @@ var init_employees = __esm({
|
|
|
789
817
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
790
818
|
COORDINATOR_ROLE = "COO";
|
|
791
819
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
820
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// src/lib/database-adapter.ts
|
|
825
|
+
import os5 from "os";
|
|
826
|
+
import path6 from "path";
|
|
827
|
+
import { createRequire } from "module";
|
|
828
|
+
import { pathToFileURL } from "url";
|
|
829
|
+
function quotedIdentifier(identifier) {
|
|
830
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
831
|
+
}
|
|
832
|
+
function unqualifiedTableName(name) {
|
|
833
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
834
|
+
const parts = raw.split(".");
|
|
835
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
836
|
+
}
|
|
837
|
+
function stripTrailingSemicolon(sql) {
|
|
838
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
839
|
+
}
|
|
840
|
+
function appendClause(sql, clause) {
|
|
841
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
842
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
843
|
+
if (!returningMatch) {
|
|
844
|
+
return `${trimmed}${clause}`;
|
|
845
|
+
}
|
|
846
|
+
const idx = returningMatch.index;
|
|
847
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
848
|
+
}
|
|
849
|
+
function normalizeStatement(stmt) {
|
|
850
|
+
if (typeof stmt === "string") {
|
|
851
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
852
|
+
}
|
|
853
|
+
const sql = stmt.sql;
|
|
854
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
855
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
856
|
+
}
|
|
857
|
+
return { kind: "named", sql, args: stmt.args };
|
|
858
|
+
}
|
|
859
|
+
function rewriteBooleanLiterals(sql) {
|
|
860
|
+
let out = sql;
|
|
861
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
862
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
863
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
864
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
865
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
866
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
867
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
868
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
869
|
+
}
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
function rewriteInsertOrIgnore(sql) {
|
|
873
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
874
|
+
return sql;
|
|
875
|
+
}
|
|
876
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
877
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
878
|
+
}
|
|
879
|
+
function rewriteInsertOrReplace(sql) {
|
|
880
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
881
|
+
if (!match) {
|
|
882
|
+
return sql;
|
|
883
|
+
}
|
|
884
|
+
const rawTable = match[1];
|
|
885
|
+
const rawColumns = match[2];
|
|
886
|
+
const remainder = match[3];
|
|
887
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
888
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
889
|
+
if (!conflictKeys?.length) {
|
|
890
|
+
return sql;
|
|
891
|
+
}
|
|
892
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
893
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
894
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
895
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
896
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
897
|
+
}
|
|
898
|
+
function rewriteSql(sql) {
|
|
899
|
+
let out = sql;
|
|
900
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
901
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
902
|
+
out = rewriteBooleanLiterals(out);
|
|
903
|
+
out = rewriteInsertOrReplace(out);
|
|
904
|
+
out = rewriteInsertOrIgnore(out);
|
|
905
|
+
return stripTrailingSemicolon(out);
|
|
906
|
+
}
|
|
907
|
+
function toBoolean(value) {
|
|
908
|
+
if (value === null || value === void 0) return value;
|
|
909
|
+
if (typeof value === "boolean") return value;
|
|
910
|
+
if (typeof value === "number") return value !== 0;
|
|
911
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
912
|
+
if (typeof value === "string") {
|
|
913
|
+
const normalized = value.trim().toLowerCase();
|
|
914
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
915
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
916
|
+
}
|
|
917
|
+
return Boolean(value);
|
|
918
|
+
}
|
|
919
|
+
function countQuestionMarks(sql, end) {
|
|
920
|
+
let count = 0;
|
|
921
|
+
let inSingle = false;
|
|
922
|
+
let inDouble = false;
|
|
923
|
+
let inLineComment = false;
|
|
924
|
+
let inBlockComment = false;
|
|
925
|
+
for (let i = 0; i < end; i++) {
|
|
926
|
+
const ch = sql[i];
|
|
927
|
+
const next = sql[i + 1];
|
|
928
|
+
if (inLineComment) {
|
|
929
|
+
if (ch === "\n") inLineComment = false;
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
if (inBlockComment) {
|
|
933
|
+
if (ch === "*" && next === "/") {
|
|
934
|
+
inBlockComment = false;
|
|
935
|
+
i += 1;
|
|
936
|
+
}
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
940
|
+
inLineComment = true;
|
|
941
|
+
i += 1;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
945
|
+
inBlockComment = true;
|
|
946
|
+
i += 1;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
950
|
+
inSingle = !inSingle;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
954
|
+
inDouble = !inDouble;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
958
|
+
count += 1;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return count;
|
|
962
|
+
}
|
|
963
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
964
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
965
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
966
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
967
|
+
for (const match of sql.matchAll(pattern)) {
|
|
968
|
+
const matchText = match[0];
|
|
969
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
970
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return indexes;
|
|
974
|
+
}
|
|
975
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
976
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
977
|
+
if (!match) return;
|
|
978
|
+
const rawTable = match[1];
|
|
979
|
+
const rawColumns = match[2];
|
|
980
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
981
|
+
if (!boolColumns?.size) return;
|
|
982
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
983
|
+
for (const [index, column] of columns.entries()) {
|
|
984
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
985
|
+
args[index] = toBoolean(args[index]);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
990
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
991
|
+
if (!match) return;
|
|
992
|
+
const rawTable = match[1];
|
|
993
|
+
const setClause = match[2];
|
|
994
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
995
|
+
if (!boolColumns?.size) return;
|
|
996
|
+
const assignments = setClause.split(",");
|
|
997
|
+
let placeholderIndex = 0;
|
|
998
|
+
for (const assignment of assignments) {
|
|
999
|
+
if (!assignment.includes("?")) continue;
|
|
1000
|
+
placeholderIndex += 1;
|
|
1001
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1002
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1003
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function coerceBooleanArgs(sql, args) {
|
|
1008
|
+
const nextArgs = [...args];
|
|
1009
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1010
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1011
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1012
|
+
for (const index of placeholderIndexes) {
|
|
1013
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1014
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return nextArgs;
|
|
1018
|
+
}
|
|
1019
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1020
|
+
let out = "";
|
|
1021
|
+
let placeholder = 0;
|
|
1022
|
+
let inSingle = false;
|
|
1023
|
+
let inDouble = false;
|
|
1024
|
+
let inLineComment = false;
|
|
1025
|
+
let inBlockComment = false;
|
|
1026
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1027
|
+
const ch = sql[i];
|
|
1028
|
+
const next = sql[i + 1];
|
|
1029
|
+
if (inLineComment) {
|
|
1030
|
+
out += ch;
|
|
1031
|
+
if (ch === "\n") inLineComment = false;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (inBlockComment) {
|
|
1035
|
+
out += ch;
|
|
1036
|
+
if (ch === "*" && next === "/") {
|
|
1037
|
+
out += next;
|
|
1038
|
+
inBlockComment = false;
|
|
1039
|
+
i += 1;
|
|
1040
|
+
}
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1044
|
+
out += ch + next;
|
|
1045
|
+
inLineComment = true;
|
|
1046
|
+
i += 1;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1050
|
+
out += ch + next;
|
|
1051
|
+
inBlockComment = true;
|
|
1052
|
+
i += 1;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1056
|
+
inSingle = !inSingle;
|
|
1057
|
+
out += ch;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1061
|
+
inDouble = !inDouble;
|
|
1062
|
+
out += ch;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1066
|
+
placeholder += 1;
|
|
1067
|
+
out += `$${placeholder}`;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
out += ch;
|
|
1071
|
+
}
|
|
1072
|
+
return out;
|
|
1073
|
+
}
|
|
1074
|
+
function translateStatementForPostgres(stmt) {
|
|
1075
|
+
const normalized = normalizeStatement(stmt);
|
|
1076
|
+
if (normalized.kind === "named") {
|
|
1077
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1078
|
+
}
|
|
1079
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1080
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1081
|
+
return {
|
|
1082
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1083
|
+
args: coercedArgs
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
function shouldBypassPostgres(stmt) {
|
|
1087
|
+
const normalized = normalizeStatement(stmt);
|
|
1088
|
+
if (normalized.kind === "named") {
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
1092
|
+
}
|
|
1093
|
+
function shouldFallbackOnError(error) {
|
|
1094
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1095
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
1096
|
+
}
|
|
1097
|
+
function isReadQuery(sql) {
|
|
1098
|
+
const trimmed = sql.trimStart();
|
|
1099
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
1100
|
+
}
|
|
1101
|
+
function buildRow(row, columns) {
|
|
1102
|
+
const values = columns.map((column) => row[column]);
|
|
1103
|
+
return Object.assign(values, row);
|
|
1104
|
+
}
|
|
1105
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
1106
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
1107
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
1108
|
+
return {
|
|
1109
|
+
columns,
|
|
1110
|
+
columnTypes: columns.map(() => ""),
|
|
1111
|
+
rows: resultRows,
|
|
1112
|
+
rowsAffected,
|
|
1113
|
+
lastInsertRowid: void 0,
|
|
1114
|
+
toJSON() {
|
|
1115
|
+
return {
|
|
1116
|
+
columns,
|
|
1117
|
+
columnTypes: columns.map(() => ""),
|
|
1118
|
+
rows,
|
|
1119
|
+
rowsAffected,
|
|
1120
|
+
lastInsertRowid: void 0
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
async function loadPrismaClient() {
|
|
1126
|
+
if (!prismaClientPromise) {
|
|
1127
|
+
prismaClientPromise = (async () => {
|
|
1128
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1129
|
+
if (explicitPath) {
|
|
1130
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1131
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1132
|
+
if (!PrismaClient2) {
|
|
1133
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1134
|
+
}
|
|
1135
|
+
return new PrismaClient2();
|
|
1136
|
+
}
|
|
1137
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
|
|
1138
|
+
const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1139
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1140
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1141
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1142
|
+
if (!PrismaClient) {
|
|
1143
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
1144
|
+
}
|
|
1145
|
+
return new PrismaClient();
|
|
1146
|
+
})();
|
|
1147
|
+
}
|
|
1148
|
+
return prismaClientPromise;
|
|
1149
|
+
}
|
|
1150
|
+
async function ensureCompatibilityViews(prisma) {
|
|
1151
|
+
if (!compatibilityBootstrapPromise) {
|
|
1152
|
+
compatibilityBootstrapPromise = (async () => {
|
|
1153
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
1154
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
1155
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1156
|
+
"SELECT to_regclass($1) AS regclass",
|
|
1157
|
+
relation
|
|
1158
|
+
);
|
|
1159
|
+
if (!rows[0]?.regclass) {
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
await prisma.$executeRawUnsafe(
|
|
1163
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
})();
|
|
1167
|
+
}
|
|
1168
|
+
return compatibilityBootstrapPromise;
|
|
1169
|
+
}
|
|
1170
|
+
async function executeOnPrisma(executor, stmt) {
|
|
1171
|
+
const translated = translateStatementForPostgres(stmt);
|
|
1172
|
+
if (isReadQuery(translated.sql)) {
|
|
1173
|
+
const rows = await executor.$queryRawUnsafe(
|
|
1174
|
+
translated.sql,
|
|
1175
|
+
...translated.args
|
|
1176
|
+
);
|
|
1177
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
1178
|
+
}
|
|
1179
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
1180
|
+
return buildResultSet([], rowsAffected);
|
|
1181
|
+
}
|
|
1182
|
+
function splitSqlStatements(sql) {
|
|
1183
|
+
const parts = [];
|
|
1184
|
+
let current = "";
|
|
1185
|
+
let inSingle = false;
|
|
1186
|
+
let inDouble = false;
|
|
1187
|
+
let inLineComment = false;
|
|
1188
|
+
let inBlockComment = false;
|
|
1189
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1190
|
+
const ch = sql[i];
|
|
1191
|
+
const next = sql[i + 1];
|
|
1192
|
+
if (inLineComment) {
|
|
1193
|
+
current += ch;
|
|
1194
|
+
if (ch === "\n") inLineComment = false;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (inBlockComment) {
|
|
1198
|
+
current += ch;
|
|
1199
|
+
if (ch === "*" && next === "/") {
|
|
1200
|
+
current += next;
|
|
1201
|
+
inBlockComment = false;
|
|
1202
|
+
i += 1;
|
|
1203
|
+
}
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1207
|
+
current += ch + next;
|
|
1208
|
+
inLineComment = true;
|
|
1209
|
+
i += 1;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1213
|
+
current += ch + next;
|
|
1214
|
+
inBlockComment = true;
|
|
1215
|
+
i += 1;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1219
|
+
inSingle = !inSingle;
|
|
1220
|
+
current += ch;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1224
|
+
inDouble = !inDouble;
|
|
1225
|
+
current += ch;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
1229
|
+
if (current.trim()) {
|
|
1230
|
+
parts.push(current.trim());
|
|
1231
|
+
}
|
|
1232
|
+
current = "";
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
current += ch;
|
|
1236
|
+
}
|
|
1237
|
+
if (current.trim()) {
|
|
1238
|
+
parts.push(current.trim());
|
|
1239
|
+
}
|
|
1240
|
+
return parts;
|
|
1241
|
+
}
|
|
1242
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
1243
|
+
const prisma = await loadPrismaClient();
|
|
1244
|
+
await ensureCompatibilityViews(prisma);
|
|
1245
|
+
let closed = false;
|
|
1246
|
+
let adapter;
|
|
1247
|
+
const fallbackExecute = async (stmt, error) => {
|
|
1248
|
+
if (!fallbackClient) {
|
|
1249
|
+
if (error) throw error;
|
|
1250
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
1251
|
+
}
|
|
1252
|
+
if (error) {
|
|
1253
|
+
process.stderr.write(
|
|
1254
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1255
|
+
`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
return fallbackClient.execute(stmt);
|
|
1259
|
+
};
|
|
1260
|
+
adapter = {
|
|
1261
|
+
async execute(stmt) {
|
|
1262
|
+
if (shouldBypassPostgres(stmt)) {
|
|
1263
|
+
return fallbackExecute(stmt);
|
|
1264
|
+
}
|
|
1265
|
+
try {
|
|
1266
|
+
return await executeOnPrisma(prisma, stmt);
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
if (shouldFallbackOnError(error)) {
|
|
1269
|
+
return fallbackExecute(stmt, error);
|
|
1270
|
+
}
|
|
1271
|
+
throw error;
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
async batch(stmts, mode) {
|
|
1275
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
1276
|
+
if (!fallbackClient) {
|
|
1277
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
1278
|
+
}
|
|
1279
|
+
return fallbackClient.batch(stmts, mode);
|
|
1280
|
+
}
|
|
1281
|
+
try {
|
|
1282
|
+
if (prisma.$transaction) {
|
|
1283
|
+
return await prisma.$transaction(async (tx) => {
|
|
1284
|
+
const results2 = [];
|
|
1285
|
+
for (const stmt of stmts) {
|
|
1286
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
1287
|
+
}
|
|
1288
|
+
return results2;
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
const results = [];
|
|
1292
|
+
for (const stmt of stmts) {
|
|
1293
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
1294
|
+
}
|
|
1295
|
+
return results;
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
1298
|
+
process.stderr.write(
|
|
1299
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
1300
|
+
`
|
|
1301
|
+
);
|
|
1302
|
+
return fallbackClient.batch(stmts, mode);
|
|
1303
|
+
}
|
|
1304
|
+
throw error;
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
async migrate(stmts) {
|
|
1308
|
+
if (fallbackClient) {
|
|
1309
|
+
return fallbackClient.migrate(stmts);
|
|
1310
|
+
}
|
|
1311
|
+
return adapter.batch(stmts, "deferred");
|
|
1312
|
+
},
|
|
1313
|
+
async transaction(mode) {
|
|
1314
|
+
if (!fallbackClient) {
|
|
1315
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
1316
|
+
}
|
|
1317
|
+
return fallbackClient.transaction(mode);
|
|
1318
|
+
},
|
|
1319
|
+
async executeMultiple(sql) {
|
|
1320
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
1321
|
+
return fallbackClient.executeMultiple(sql);
|
|
1322
|
+
}
|
|
1323
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
1324
|
+
await adapter.execute(statement);
|
|
1325
|
+
}
|
|
1326
|
+
},
|
|
1327
|
+
async sync() {
|
|
1328
|
+
if (fallbackClient) {
|
|
1329
|
+
return fallbackClient.sync();
|
|
1330
|
+
}
|
|
1331
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
1332
|
+
},
|
|
1333
|
+
close() {
|
|
1334
|
+
closed = true;
|
|
1335
|
+
prismaClientPromise = null;
|
|
1336
|
+
compatibilityBootstrapPromise = null;
|
|
1337
|
+
void prisma.$disconnect?.();
|
|
1338
|
+
},
|
|
1339
|
+
get closed() {
|
|
1340
|
+
return closed;
|
|
1341
|
+
},
|
|
1342
|
+
get protocol() {
|
|
1343
|
+
return "prisma-postgres";
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
return adapter;
|
|
1347
|
+
}
|
|
1348
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
1349
|
+
var init_database_adapter = __esm({
|
|
1350
|
+
"src/lib/database-adapter.ts"() {
|
|
1351
|
+
"use strict";
|
|
1352
|
+
VIEW_MAPPINGS = [
|
|
1353
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
1354
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
1355
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
1356
|
+
{ view: "entities", source: "memory.entities" },
|
|
1357
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
1358
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
1359
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
1360
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
1361
|
+
{ view: "messages", source: "memory.messages" },
|
|
1362
|
+
{ view: "users", source: "wiki.users" },
|
|
1363
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
1364
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
1365
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
1366
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
1367
|
+
];
|
|
1368
|
+
UPSERT_KEYS = {
|
|
1369
|
+
memories: ["id"],
|
|
1370
|
+
tasks: ["id"],
|
|
1371
|
+
behaviors: ["id"],
|
|
1372
|
+
entities: ["id"],
|
|
1373
|
+
relationships: ["id"],
|
|
1374
|
+
entity_aliases: ["alias"],
|
|
1375
|
+
notifications: ["id"],
|
|
1376
|
+
messages: ["id"],
|
|
1377
|
+
users: ["id"],
|
|
1378
|
+
workspaces: ["id"],
|
|
1379
|
+
workspace_users: ["id"],
|
|
1380
|
+
documents: ["id"],
|
|
1381
|
+
chats: ["id"]
|
|
1382
|
+
};
|
|
1383
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
1384
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
1385
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
1386
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
1387
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
1388
|
+
};
|
|
1389
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
1390
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
1391
|
+
);
|
|
1392
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
1393
|
+
/\bPRAGMA\b/i,
|
|
1394
|
+
/\bsqlite_master\b/i,
|
|
1395
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
1396
|
+
/\bMATCH\b/i,
|
|
1397
|
+
/\bvector_distance_cos\s*\(/i,
|
|
1398
|
+
/\bjson_extract\s*\(/i,
|
|
1399
|
+
/\bjulianday\s*\(/i,
|
|
1400
|
+
/\bstrftime\s*\(/i,
|
|
1401
|
+
/\blast_insert_rowid\s*\(/i
|
|
1402
|
+
];
|
|
1403
|
+
prismaClientPromise = null;
|
|
1404
|
+
compatibilityBootstrapPromise = null;
|
|
792
1405
|
}
|
|
793
1406
|
});
|
|
794
1407
|
|
|
795
1408
|
// src/lib/database.ts
|
|
796
1409
|
import { createClient } from "@libsql/client";
|
|
797
1410
|
async function initDatabase(config) {
|
|
1411
|
+
if (_walCheckpointTimer) {
|
|
1412
|
+
clearInterval(_walCheckpointTimer);
|
|
1413
|
+
_walCheckpointTimer = null;
|
|
1414
|
+
}
|
|
1415
|
+
if (_daemonClient) {
|
|
1416
|
+
_daemonClient.close();
|
|
1417
|
+
_daemonClient = null;
|
|
1418
|
+
}
|
|
1419
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
1420
|
+
_adapterClient.close();
|
|
1421
|
+
}
|
|
1422
|
+
_adapterClient = null;
|
|
798
1423
|
if (_client) {
|
|
799
1424
|
_client.close();
|
|
800
1425
|
_client = null;
|
|
@@ -808,6 +1433,7 @@ async function initDatabase(config) {
|
|
|
808
1433
|
}
|
|
809
1434
|
_client = createClient(opts);
|
|
810
1435
|
_resilientClient = wrapWithRetry(_client);
|
|
1436
|
+
_adapterClient = _resilientClient;
|
|
811
1437
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
812
1438
|
});
|
|
813
1439
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -818,11 +1444,17 @@ async function initDatabase(config) {
|
|
|
818
1444
|
});
|
|
819
1445
|
}, 3e4);
|
|
820
1446
|
_walCheckpointTimer.unref();
|
|
1447
|
+
if (process.env.DATABASE_URL) {
|
|
1448
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1449
|
+
}
|
|
821
1450
|
}
|
|
822
1451
|
function getClient() {
|
|
823
|
-
if (!
|
|
1452
|
+
if (!_adapterClient) {
|
|
824
1453
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
825
1454
|
}
|
|
1455
|
+
if (process.env.DATABASE_URL) {
|
|
1456
|
+
return _adapterClient;
|
|
1457
|
+
}
|
|
826
1458
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
827
1459
|
return _resilientClient;
|
|
828
1460
|
}
|
|
@@ -1115,6 +1747,7 @@ async function ensureSchema() {
|
|
|
1115
1747
|
project TEXT NOT NULL,
|
|
1116
1748
|
summary TEXT NOT NULL,
|
|
1117
1749
|
task_file TEXT,
|
|
1750
|
+
session_scope TEXT,
|
|
1118
1751
|
read INTEGER NOT NULL DEFAULT 0,
|
|
1119
1752
|
created_at TEXT NOT NULL
|
|
1120
1753
|
);
|
|
@@ -1123,7 +1756,7 @@ async function ensureSchema() {
|
|
|
1123
1756
|
ON notifications(read);
|
|
1124
1757
|
|
|
1125
1758
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1126
|
-
ON notifications(agent_id);
|
|
1759
|
+
ON notifications(agent_id, session_scope);
|
|
1127
1760
|
|
|
1128
1761
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1129
1762
|
ON notifications(task_file);
|
|
@@ -1161,6 +1794,7 @@ async function ensureSchema() {
|
|
|
1161
1794
|
target_agent TEXT NOT NULL,
|
|
1162
1795
|
target_project TEXT,
|
|
1163
1796
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1797
|
+
session_scope TEXT,
|
|
1164
1798
|
content TEXT NOT NULL,
|
|
1165
1799
|
priority TEXT DEFAULT 'normal',
|
|
1166
1800
|
status TEXT DEFAULT 'pending',
|
|
@@ -1174,10 +1808,31 @@ async function ensureSchema() {
|
|
|
1174
1808
|
);
|
|
1175
1809
|
|
|
1176
1810
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1177
|
-
ON messages(target_agent, status);
|
|
1811
|
+
ON messages(target_agent, session_scope, status);
|
|
1178
1812
|
|
|
1179
1813
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1180
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
1814
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
1815
|
+
`);
|
|
1816
|
+
try {
|
|
1817
|
+
await client.execute({
|
|
1818
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
1819
|
+
args: []
|
|
1820
|
+
});
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
await client.execute({
|
|
1825
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
1826
|
+
args: []
|
|
1827
|
+
});
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
await client.executeMultiple(`
|
|
1831
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
1832
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
1833
|
+
|
|
1834
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
1835
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
1181
1836
|
`);
|
|
1182
1837
|
try {
|
|
1183
1838
|
await client.execute({
|
|
@@ -1761,34 +2416,46 @@ async function ensureSchema() {
|
|
|
1761
2416
|
} catch {
|
|
1762
2417
|
}
|
|
1763
2418
|
}
|
|
2419
|
+
try {
|
|
2420
|
+
await client.execute({
|
|
2421
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
2422
|
+
args: []
|
|
2423
|
+
});
|
|
2424
|
+
} catch {
|
|
2425
|
+
}
|
|
1764
2426
|
}
|
|
1765
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
|
|
2427
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
|
|
1766
2428
|
var init_database = __esm({
|
|
1767
2429
|
"src/lib/database.ts"() {
|
|
1768
2430
|
"use strict";
|
|
1769
2431
|
init_db_retry();
|
|
1770
2432
|
init_employees();
|
|
2433
|
+
init_database_adapter();
|
|
1771
2434
|
_client = null;
|
|
1772
2435
|
_resilientClient = null;
|
|
1773
2436
|
_walCheckpointTimer = null;
|
|
1774
2437
|
_daemonClient = null;
|
|
2438
|
+
_adapterClient = null;
|
|
1775
2439
|
initTurso = initDatabase;
|
|
1776
2440
|
}
|
|
1777
2441
|
});
|
|
1778
2442
|
|
|
1779
2443
|
// src/lib/license.ts
|
|
1780
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as
|
|
2444
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
1781
2445
|
import { randomUUID } from "crypto";
|
|
1782
|
-
import
|
|
2446
|
+
import { createRequire as createRequire2 } from "module";
|
|
2447
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2448
|
+
import os6 from "os";
|
|
2449
|
+
import path7 from "path";
|
|
1783
2450
|
import { jwtVerify, importSPKI } from "jose";
|
|
1784
2451
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1785
2452
|
var init_license = __esm({
|
|
1786
2453
|
"src/lib/license.ts"() {
|
|
1787
2454
|
"use strict";
|
|
1788
2455
|
init_config();
|
|
1789
|
-
LICENSE_PATH =
|
|
1790
|
-
CACHE_PATH =
|
|
1791
|
-
DEVICE_ID_PATH =
|
|
2456
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2457
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2458
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1792
2459
|
PLAN_LIMITS = {
|
|
1793
2460
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1794
2461
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1800,11 +2467,11 @@ var init_license = __esm({
|
|
|
1800
2467
|
});
|
|
1801
2468
|
|
|
1802
2469
|
// src/lib/plan-limits.ts
|
|
1803
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
1804
|
-
import
|
|
2470
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
2471
|
+
import path8 from "path";
|
|
1805
2472
|
function getLicenseSync() {
|
|
1806
2473
|
try {
|
|
1807
|
-
if (!
|
|
2474
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
1808
2475
|
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1809
2476
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1810
2477
|
const parts = raw.token.split(".");
|
|
@@ -1843,7 +2510,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1843
2510
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1844
2511
|
let count = 0;
|
|
1845
2512
|
try {
|
|
1846
|
-
if (
|
|
2513
|
+
if (existsSync8(filePath)) {
|
|
1847
2514
|
const raw = readFileSync7(filePath, "utf8");
|
|
1848
2515
|
const employees = JSON.parse(raw);
|
|
1849
2516
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
@@ -1873,19 +2540,52 @@ var init_plan_limits = __esm({
|
|
|
1873
2540
|
this.name = "PlanLimitError";
|
|
1874
2541
|
}
|
|
1875
2542
|
};
|
|
1876
|
-
CACHE_PATH2 =
|
|
2543
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
|
|
2547
|
+
// src/lib/task-scope.ts
|
|
2548
|
+
function getCurrentSessionScope() {
|
|
2549
|
+
try {
|
|
2550
|
+
return resolveExeSession();
|
|
2551
|
+
} catch {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
2556
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2557
|
+
if (!scope) return { sql: "", args: [] };
|
|
2558
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2559
|
+
return {
|
|
2560
|
+
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
2561
|
+
args: [scope]
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2565
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2566
|
+
if (!scope) return { sql: "", args: [] };
|
|
2567
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2568
|
+
return {
|
|
2569
|
+
sql: ` AND ${col} = ?`,
|
|
2570
|
+
args: [scope]
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
var init_task_scope = __esm({
|
|
2574
|
+
"src/lib/task-scope.ts"() {
|
|
2575
|
+
"use strict";
|
|
2576
|
+
init_tmux_routing();
|
|
1877
2577
|
}
|
|
1878
2578
|
});
|
|
1879
2579
|
|
|
1880
2580
|
// src/lib/notifications.ts
|
|
1881
2581
|
import crypto from "crypto";
|
|
1882
|
-
import
|
|
1883
|
-
import
|
|
2582
|
+
import path9 from "path";
|
|
2583
|
+
import os7 from "os";
|
|
1884
2584
|
import {
|
|
1885
2585
|
readFileSync as readFileSync8,
|
|
1886
2586
|
readdirSync,
|
|
1887
2587
|
unlinkSync as unlinkSync2,
|
|
1888
|
-
existsSync as
|
|
2588
|
+
existsSync as existsSync9,
|
|
1889
2589
|
rmdirSync
|
|
1890
2590
|
} from "fs";
|
|
1891
2591
|
async function writeNotification(notification) {
|
|
@@ -1893,9 +2593,10 @@ async function writeNotification(notification) {
|
|
|
1893
2593
|
const client = getClient();
|
|
1894
2594
|
const id = crypto.randomUUID();
|
|
1895
2595
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2596
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
1896
2597
|
await client.execute({
|
|
1897
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
1898
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2598
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
2599
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
1899
2600
|
args: [
|
|
1900
2601
|
id,
|
|
1901
2602
|
notification.agentId,
|
|
@@ -1904,6 +2605,7 @@ async function writeNotification(notification) {
|
|
|
1904
2605
|
notification.project,
|
|
1905
2606
|
notification.summary,
|
|
1906
2607
|
notification.taskFile ?? null,
|
|
2608
|
+
sessionScope,
|
|
1907
2609
|
now
|
|
1908
2610
|
]
|
|
1909
2611
|
});
|
|
@@ -1912,12 +2614,14 @@ async function writeNotification(notification) {
|
|
|
1912
2614
|
`);
|
|
1913
2615
|
}
|
|
1914
2616
|
}
|
|
1915
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
2617
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
1916
2618
|
try {
|
|
1917
2619
|
const client = getClient();
|
|
2620
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
1918
2621
|
await client.execute({
|
|
1919
|
-
sql:
|
|
1920
|
-
|
|
2622
|
+
sql: `UPDATE notifications SET read = 1
|
|
2623
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
2624
|
+
args: [taskFile, ...scope.args]
|
|
1921
2625
|
});
|
|
1922
2626
|
} catch {
|
|
1923
2627
|
}
|
|
@@ -1926,6 +2630,7 @@ var init_notifications = __esm({
|
|
|
1926
2630
|
"src/lib/notifications.ts"() {
|
|
1927
2631
|
"use strict";
|
|
1928
2632
|
init_database();
|
|
2633
|
+
init_task_scope();
|
|
1929
2634
|
}
|
|
1930
2635
|
});
|
|
1931
2636
|
|
|
@@ -1963,30 +2668,6 @@ var init_session_kill_telemetry = __esm({
|
|
|
1963
2668
|
}
|
|
1964
2669
|
});
|
|
1965
2670
|
|
|
1966
|
-
// src/lib/task-scope.ts
|
|
1967
|
-
function getCurrentSessionScope() {
|
|
1968
|
-
try {
|
|
1969
|
-
return resolveExeSession();
|
|
1970
|
-
} catch {
|
|
1971
|
-
return null;
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
function sessionScopeFilter(sessionScope, tableAlias) {
|
|
1975
|
-
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
1976
|
-
if (!scope) return { sql: "", args: [] };
|
|
1977
|
-
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
1978
|
-
return {
|
|
1979
|
-
sql: ` AND (${col} IS NULL OR ${col} = ?)`,
|
|
1980
|
-
args: [scope]
|
|
1981
|
-
};
|
|
1982
|
-
}
|
|
1983
|
-
var init_task_scope = __esm({
|
|
1984
|
-
"src/lib/task-scope.ts"() {
|
|
1985
|
-
"use strict";
|
|
1986
|
-
init_tmux_routing();
|
|
1987
|
-
}
|
|
1988
|
-
});
|
|
1989
|
-
|
|
1990
2671
|
// src/lib/state-bus.ts
|
|
1991
2672
|
var StateBus, orgBus;
|
|
1992
2673
|
var init_state_bus = __esm({
|
|
@@ -2044,11 +2725,11 @@ var init_state_bus = __esm({
|
|
|
2044
2725
|
|
|
2045
2726
|
// src/lib/tasks-crud.ts
|
|
2046
2727
|
import crypto3 from "crypto";
|
|
2047
|
-
import
|
|
2048
|
-
import
|
|
2728
|
+
import path10 from "path";
|
|
2729
|
+
import os8 from "os";
|
|
2049
2730
|
import { execSync as execSync4 } from "child_process";
|
|
2050
2731
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2051
|
-
import { existsSync as
|
|
2732
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
2052
2733
|
async function writeCheckpoint(input) {
|
|
2053
2734
|
const client = getClient();
|
|
2054
2735
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2223,8 +2904,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2223
2904
|
}
|
|
2224
2905
|
if (input.baseDir) {
|
|
2225
2906
|
try {
|
|
2226
|
-
await mkdir3(
|
|
2227
|
-
await mkdir3(
|
|
2907
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2908
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2228
2909
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2229
2910
|
await ensureGitignoreExe(input.baseDir);
|
|
2230
2911
|
} catch {
|
|
@@ -2260,13 +2941,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2260
2941
|
});
|
|
2261
2942
|
if (input.baseDir) {
|
|
2262
2943
|
try {
|
|
2263
|
-
const EXE_OS_DIR =
|
|
2264
|
-
const mdPath =
|
|
2265
|
-
const mdDir =
|
|
2266
|
-
if (!
|
|
2944
|
+
const EXE_OS_DIR = path10.join(os8.homedir(), ".exe-os");
|
|
2945
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2946
|
+
const mdDir = path10.dirname(mdPath);
|
|
2947
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2267
2948
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2268
2949
|
const mdContent = `# ${input.title}
|
|
2269
2950
|
|
|
2951
|
+
## MANDATORY: When done
|
|
2952
|
+
|
|
2953
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2954
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2955
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2956
|
+
|
|
2270
2957
|
**ID:** ${id}
|
|
2271
2958
|
**Status:** ${initialStatus}
|
|
2272
2959
|
**Priority:** ${input.priority}
|
|
@@ -2280,12 +2967,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2280
2967
|
## Context
|
|
2281
2968
|
|
|
2282
2969
|
${input.context}
|
|
2283
|
-
|
|
2284
|
-
## MANDATORY: When done
|
|
2285
|
-
|
|
2286
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2287
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2288
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2289
2970
|
`;
|
|
2290
2971
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2291
2972
|
} catch (err) {
|
|
@@ -2534,7 +3215,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2534
3215
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2535
3216
|
} catch {
|
|
2536
3217
|
}
|
|
2537
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3218
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2538
3219
|
try {
|
|
2539
3220
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2540
3221
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2563,9 +3244,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2563
3244
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2564
3245
|
}
|
|
2565
3246
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2566
|
-
const archPath =
|
|
3247
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2567
3248
|
try {
|
|
2568
|
-
if (
|
|
3249
|
+
if (existsSync10(archPath)) return;
|
|
2569
3250
|
const template = [
|
|
2570
3251
|
`# ${projectName} \u2014 System Architecture`,
|
|
2571
3252
|
"",
|
|
@@ -2598,9 +3279,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2598
3279
|
}
|
|
2599
3280
|
}
|
|
2600
3281
|
async function ensureGitignoreExe(baseDir) {
|
|
2601
|
-
const gitignorePath =
|
|
3282
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2602
3283
|
try {
|
|
2603
|
-
if (
|
|
3284
|
+
if (existsSync10(gitignorePath)) {
|
|
2604
3285
|
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2605
3286
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2606
3287
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -2632,58 +3313,42 @@ var init_tasks_crud = __esm({
|
|
|
2632
3313
|
});
|
|
2633
3314
|
|
|
2634
3315
|
// src/lib/tasks-review.ts
|
|
2635
|
-
import
|
|
2636
|
-
import { existsSync as
|
|
3316
|
+
import path11 from "path";
|
|
3317
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
2637
3318
|
async function countPendingReviews(sessionScope) {
|
|
2638
3319
|
const client = getClient();
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
args: [sessionScope]
|
|
2643
|
-
});
|
|
2644
|
-
return Number(result3.rows[0]?.cnt) || 0;
|
|
2645
|
-
}
|
|
3320
|
+
const scope = strictSessionScopeFilter(
|
|
3321
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3322
|
+
);
|
|
2646
3323
|
const result2 = await client.execute({
|
|
2647
|
-
sql:
|
|
2648
|
-
|
|
3324
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3325
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
3326
|
+
args: [...scope.args]
|
|
2649
3327
|
});
|
|
2650
3328
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
2651
3329
|
}
|
|
2652
3330
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2653
3331
|
const client = getClient();
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
2658
|
-
AND session_scope = ?`,
|
|
2659
|
-
args: [sinceIso, sessionScope]
|
|
2660
|
-
});
|
|
2661
|
-
return Number(result3.rows[0]?.cnt) || 0;
|
|
2662
|
-
}
|
|
3332
|
+
const scope = strictSessionScopeFilter(
|
|
3333
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3334
|
+
);
|
|
2663
3335
|
const result2 = await client.execute({
|
|
2664
3336
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2665
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
2666
|
-
args: [sinceIso]
|
|
3337
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
3338
|
+
args: [sinceIso, ...scope.args]
|
|
2667
3339
|
});
|
|
2668
3340
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
2669
3341
|
}
|
|
2670
3342
|
async function listPendingReviews(limit, sessionScope) {
|
|
2671
3343
|
const client = getClient();
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
WHERE status = 'needs_review'
|
|
2676
|
-
AND session_scope = ?
|
|
2677
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
2678
|
-
args: [sessionScope, limit]
|
|
2679
|
-
});
|
|
2680
|
-
return result3.rows;
|
|
2681
|
-
}
|
|
3344
|
+
const scope = strictSessionScopeFilter(
|
|
3345
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
3346
|
+
);
|
|
2682
3347
|
const result2 = await client.execute({
|
|
2683
3348
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
2684
|
-
WHERE status = 'needs_review'
|
|
3349
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
2685
3350
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
2686
|
-
args: [limit]
|
|
3351
|
+
args: [...scope.args, limit]
|
|
2687
3352
|
});
|
|
2688
3353
|
return result2.rows;
|
|
2689
3354
|
}
|
|
@@ -2695,7 +3360,7 @@ async function cleanupOrphanedReviews() {
|
|
|
2695
3360
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
2696
3361
|
AND assigned_by = 'system'
|
|
2697
3362
|
AND title LIKE 'Review:%'
|
|
2698
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
3363
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
2699
3364
|
args: [now]
|
|
2700
3365
|
});
|
|
2701
3366
|
const r1b = await client.execute({
|
|
@@ -2814,11 +3479,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2814
3479
|
);
|
|
2815
3480
|
}
|
|
2816
3481
|
try {
|
|
2817
|
-
const cacheDir =
|
|
2818
|
-
if (
|
|
3482
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3483
|
+
if (existsSync11(cacheDir)) {
|
|
2819
3484
|
for (const f of readdirSync2(cacheDir)) {
|
|
2820
3485
|
if (f.startsWith("review-notified-")) {
|
|
2821
|
-
unlinkSync3(
|
|
3486
|
+
unlinkSync3(path11.join(cacheDir, f));
|
|
2822
3487
|
}
|
|
2823
3488
|
}
|
|
2824
3489
|
}
|
|
@@ -2835,11 +3500,12 @@ var init_tasks_review = __esm({
|
|
|
2835
3500
|
init_tmux_routing();
|
|
2836
3501
|
init_session_key();
|
|
2837
3502
|
init_state_bus();
|
|
3503
|
+
init_task_scope();
|
|
2838
3504
|
}
|
|
2839
3505
|
});
|
|
2840
3506
|
|
|
2841
3507
|
// src/lib/tasks-chain.ts
|
|
2842
|
-
import
|
|
3508
|
+
import path12 from "path";
|
|
2843
3509
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2844
3510
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2845
3511
|
const client = getClient();
|
|
@@ -2856,7 +3522,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2856
3522
|
});
|
|
2857
3523
|
for (const ur of unblockedRows.rows) {
|
|
2858
3524
|
try {
|
|
2859
|
-
const ubFile =
|
|
3525
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2860
3526
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2861
3527
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2862
3528
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2891,7 +3557,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
2891
3557
|
const scScope = sessionScopeFilter();
|
|
2892
3558
|
const remaining = await client.execute({
|
|
2893
3559
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2894
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3560
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
2895
3561
|
args: [parentTaskId, ...scScope.args]
|
|
2896
3562
|
});
|
|
2897
3563
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -2925,7 +3591,7 @@ var init_tasks_chain = __esm({
|
|
|
2925
3591
|
|
|
2926
3592
|
// src/lib/project-name.ts
|
|
2927
3593
|
import { execSync as execSync5 } from "child_process";
|
|
2928
|
-
import
|
|
3594
|
+
import path13 from "path";
|
|
2929
3595
|
function getProjectName(cwd) {
|
|
2930
3596
|
const dir = cwd ?? process.cwd();
|
|
2931
3597
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2938,7 +3604,7 @@ function getProjectName(cwd) {
|
|
|
2938
3604
|
timeout: 2e3,
|
|
2939
3605
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2940
3606
|
}).trim();
|
|
2941
|
-
repoRoot =
|
|
3607
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2942
3608
|
} catch {
|
|
2943
3609
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2944
3610
|
cwd: dir,
|
|
@@ -2947,11 +3613,11 @@ function getProjectName(cwd) {
|
|
|
2947
3613
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2948
3614
|
}).trim();
|
|
2949
3615
|
}
|
|
2950
|
-
_cached2 =
|
|
3616
|
+
_cached2 = path13.basename(repoRoot);
|
|
2951
3617
|
_cachedCwd = dir;
|
|
2952
3618
|
return _cached2;
|
|
2953
3619
|
} catch {
|
|
2954
|
-
_cached2 =
|
|
3620
|
+
_cached2 = path13.basename(dir);
|
|
2955
3621
|
_cachedCwd = dir;
|
|
2956
3622
|
return _cached2;
|
|
2957
3623
|
}
|
|
@@ -3424,7 +4090,7 @@ __export(tasks_exports, {
|
|
|
3424
4090
|
updateTaskStatus: () => updateTaskStatus,
|
|
3425
4091
|
writeCheckpoint: () => writeCheckpoint
|
|
3426
4092
|
});
|
|
3427
|
-
import
|
|
4093
|
+
import path14 from "path";
|
|
3428
4094
|
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
3429
4095
|
async function createTask(input) {
|
|
3430
4096
|
const result2 = await createTaskCore(input);
|
|
@@ -3444,12 +4110,12 @@ async function updateTask(input) {
|
|
|
3444
4110
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3445
4111
|
try {
|
|
3446
4112
|
const agent = String(row.assigned_to);
|
|
3447
|
-
const cacheDir =
|
|
3448
|
-
const cachePath =
|
|
4113
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4114
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3449
4115
|
if (input.status === "in_progress") {
|
|
3450
4116
|
mkdirSync5(cacheDir, { recursive: true });
|
|
3451
4117
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3452
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4118
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3453
4119
|
try {
|
|
3454
4120
|
unlinkSync4(cachePath);
|
|
3455
4121
|
} catch {
|
|
@@ -3457,10 +4123,10 @@ async function updateTask(input) {
|
|
|
3457
4123
|
}
|
|
3458
4124
|
} catch {
|
|
3459
4125
|
}
|
|
3460
|
-
if (input.status === "done") {
|
|
4126
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3461
4127
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3462
4128
|
}
|
|
3463
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
4129
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3464
4130
|
try {
|
|
3465
4131
|
const client = getClient();
|
|
3466
4132
|
const taskTitle = String(row.title);
|
|
@@ -3476,7 +4142,7 @@ async function updateTask(input) {
|
|
|
3476
4142
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3477
4143
|
try {
|
|
3478
4144
|
const draftClient = getClient();
|
|
3479
|
-
if (input.status === "done") {
|
|
4145
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3480
4146
|
await draftClient.execute({
|
|
3481
4147
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3482
4148
|
args: [assignedAgent]
|
|
@@ -3493,7 +4159,7 @@ async function updateTask(input) {
|
|
|
3493
4159
|
try {
|
|
3494
4160
|
const client = getClient();
|
|
3495
4161
|
const cascaded = await client.execute({
|
|
3496
|
-
sql: `UPDATE tasks SET status = '
|
|
4162
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3497
4163
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3498
4164
|
args: [now, taskId]
|
|
3499
4165
|
});
|
|
@@ -3506,14 +4172,14 @@ async function updateTask(input) {
|
|
|
3506
4172
|
} catch {
|
|
3507
4173
|
}
|
|
3508
4174
|
}
|
|
3509
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4175
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3510
4176
|
if (isTerminal) {
|
|
3511
4177
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3512
4178
|
if (!isCoordinator) {
|
|
3513
4179
|
notifyTaskDone();
|
|
3514
4180
|
}
|
|
3515
4181
|
await markTaskNotificationsRead(taskFile);
|
|
3516
|
-
if (input.status === "done") {
|
|
4182
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3517
4183
|
try {
|
|
3518
4184
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3519
4185
|
} catch {
|
|
@@ -3533,7 +4199,7 @@ async function updateTask(input) {
|
|
|
3533
4199
|
}
|
|
3534
4200
|
}
|
|
3535
4201
|
}
|
|
3536
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4202
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3537
4203
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3538
4204
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3539
4205
|
taskId,
|
|
@@ -3905,6 +4571,7 @@ __export(tmux_routing_exports, {
|
|
|
3905
4571
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
3906
4572
|
isExeSession: () => isExeSession,
|
|
3907
4573
|
isSessionBusy: () => isSessionBusy,
|
|
4574
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
3908
4575
|
notifyParentExe: () => notifyParentExe,
|
|
3909
4576
|
parseParentExe: () => parseParentExe,
|
|
3910
4577
|
registerParentExe: () => registerParentExe,
|
|
@@ -3915,13 +4582,13 @@ __export(tmux_routing_exports, {
|
|
|
3915
4582
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3916
4583
|
});
|
|
3917
4584
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3918
|
-
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as
|
|
3919
|
-
import
|
|
3920
|
-
import
|
|
4585
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
|
|
4586
|
+
import path15 from "path";
|
|
4587
|
+
import os9 from "os";
|
|
3921
4588
|
import { fileURLToPath } from "url";
|
|
3922
4589
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
3923
4590
|
function spawnLockPath(sessionName) {
|
|
3924
|
-
return
|
|
4591
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3925
4592
|
}
|
|
3926
4593
|
function isProcessAlive(pid) {
|
|
3927
4594
|
try {
|
|
@@ -3932,11 +4599,11 @@ function isProcessAlive(pid) {
|
|
|
3932
4599
|
}
|
|
3933
4600
|
}
|
|
3934
4601
|
function acquireSpawnLock(sessionName) {
|
|
3935
|
-
if (!
|
|
4602
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
3936
4603
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3937
4604
|
}
|
|
3938
4605
|
const lockFile = spawnLockPath(sessionName);
|
|
3939
|
-
if (
|
|
4606
|
+
if (existsSync12(lockFile)) {
|
|
3940
4607
|
try {
|
|
3941
4608
|
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3942
4609
|
const age = Date.now() - lock.timestamp;
|
|
@@ -3958,13 +4625,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
3958
4625
|
function resolveBehaviorsExporterScript() {
|
|
3959
4626
|
try {
|
|
3960
4627
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3961
|
-
const scriptPath =
|
|
3962
|
-
|
|
4628
|
+
const scriptPath = path15.join(
|
|
4629
|
+
path15.dirname(thisFile),
|
|
3963
4630
|
"..",
|
|
3964
4631
|
"bin",
|
|
3965
4632
|
"exe-export-behaviors.js"
|
|
3966
4633
|
);
|
|
3967
|
-
return
|
|
4634
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3968
4635
|
} catch {
|
|
3969
4636
|
return null;
|
|
3970
4637
|
}
|
|
@@ -4030,11 +4697,11 @@ function extractRootExe(name) {
|
|
|
4030
4697
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4031
4698
|
}
|
|
4032
4699
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4033
|
-
if (!
|
|
4700
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4034
4701
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4035
4702
|
}
|
|
4036
4703
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4037
|
-
const filePath =
|
|
4704
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4038
4705
|
writeFileSync7(filePath, JSON.stringify({
|
|
4039
4706
|
parentExe: rootExe,
|
|
4040
4707
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -4043,7 +4710,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4043
4710
|
}
|
|
4044
4711
|
function getParentExe(sessionKey) {
|
|
4045
4712
|
try {
|
|
4046
|
-
const data = JSON.parse(readFileSync10(
|
|
4713
|
+
const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4047
4714
|
return data.parentExe || null;
|
|
4048
4715
|
} catch {
|
|
4049
4716
|
return null;
|
|
@@ -4052,7 +4719,7 @@ function getParentExe(sessionKey) {
|
|
|
4052
4719
|
function getDispatchedBy(sessionKey) {
|
|
4053
4720
|
try {
|
|
4054
4721
|
const data = JSON.parse(readFileSync10(
|
|
4055
|
-
|
|
4722
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4056
4723
|
"utf8"
|
|
4057
4724
|
));
|
|
4058
4725
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4122,7 +4789,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4122
4789
|
}
|
|
4123
4790
|
function readDebounceState() {
|
|
4124
4791
|
try {
|
|
4125
|
-
if (!
|
|
4792
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4126
4793
|
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
4127
4794
|
const state = {};
|
|
4128
4795
|
for (const [key, val] of Object.entries(raw)) {
|
|
@@ -4139,7 +4806,7 @@ function readDebounceState() {
|
|
|
4139
4806
|
}
|
|
4140
4807
|
function writeDebounceState(state) {
|
|
4141
4808
|
try {
|
|
4142
|
-
if (!
|
|
4809
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4143
4810
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4144
4811
|
} catch {
|
|
4145
4812
|
}
|
|
@@ -4238,8 +4905,8 @@ function sendIntercom(targetSession) {
|
|
|
4238
4905
|
try {
|
|
4239
4906
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4240
4907
|
const agent = baseAgentName(rawAgent);
|
|
4241
|
-
const markerPath =
|
|
4242
|
-
if (
|
|
4908
|
+
const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
|
|
4909
|
+
if (existsSync12(markerPath)) {
|
|
4243
4910
|
logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
|
|
4244
4911
|
return "debounced";
|
|
4245
4912
|
}
|
|
@@ -4248,8 +4915,8 @@ function sendIntercom(targetSession) {
|
|
|
4248
4915
|
try {
|
|
4249
4916
|
const rawAgent = targetSession.split("-")[0] ?? targetSession;
|
|
4250
4917
|
const agent = baseAgentName(rawAgent);
|
|
4251
|
-
const taskDir =
|
|
4252
|
-
if (
|
|
4918
|
+
const taskDir = path15.join(process.cwd(), "exe", agent);
|
|
4919
|
+
if (existsSync12(taskDir)) {
|
|
4253
4920
|
const files = readdirSync3(taskDir).filter(
|
|
4254
4921
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
4255
4922
|
);
|
|
@@ -4309,6 +4976,21 @@ function notifyParentExe(sessionKey) {
|
|
|
4309
4976
|
}
|
|
4310
4977
|
return true;
|
|
4311
4978
|
}
|
|
4979
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
4980
|
+
const transport = getTransport();
|
|
4981
|
+
try {
|
|
4982
|
+
const sessions = transport.listSessions();
|
|
4983
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
4984
|
+
execSync6(
|
|
4985
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
4986
|
+
{ timeout: 3e3 }
|
|
4987
|
+
);
|
|
4988
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
4989
|
+
return true;
|
|
4990
|
+
} catch {
|
|
4991
|
+
return false;
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4312
4994
|
function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
4313
4995
|
if (isCoordinatorName(employeeName2)) {
|
|
4314
4996
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -4382,23 +5064,23 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4382
5064
|
const transport = getTransport();
|
|
4383
5065
|
const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
|
|
4384
5066
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
|
|
4385
|
-
const logDir =
|
|
4386
|
-
const logFile =
|
|
4387
|
-
if (!
|
|
5067
|
+
const logDir = path15.join(os9.homedir(), ".exe-os", "session-logs");
|
|
5068
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5069
|
+
if (!existsSync12(logDir)) {
|
|
4388
5070
|
mkdirSync6(logDir, { recursive: true });
|
|
4389
5071
|
}
|
|
4390
5072
|
transport.kill(sessionName);
|
|
4391
5073
|
let cleanupSuffix = "";
|
|
4392
5074
|
try {
|
|
4393
5075
|
const thisFile = fileURLToPath(import.meta.url);
|
|
4394
|
-
const cleanupScript =
|
|
4395
|
-
if (
|
|
5076
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5077
|
+
if (existsSync12(cleanupScript)) {
|
|
4396
5078
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName2}" "${exeSession2}"`;
|
|
4397
5079
|
}
|
|
4398
5080
|
} catch {
|
|
4399
5081
|
}
|
|
4400
5082
|
try {
|
|
4401
|
-
const claudeJsonPath =
|
|
5083
|
+
const claudeJsonPath = path15.join(os9.homedir(), ".claude.json");
|
|
4402
5084
|
let claudeJson = {};
|
|
4403
5085
|
try {
|
|
4404
5086
|
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
@@ -4413,10 +5095,10 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4413
5095
|
} catch {
|
|
4414
5096
|
}
|
|
4415
5097
|
try {
|
|
4416
|
-
const settingsDir =
|
|
5098
|
+
const settingsDir = path15.join(os9.homedir(), ".claude", "projects");
|
|
4417
5099
|
const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
|
|
4418
|
-
const projSettingsDir =
|
|
4419
|
-
const settingsPath =
|
|
5100
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
5101
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
4420
5102
|
let settings = {};
|
|
4421
5103
|
try {
|
|
4422
5104
|
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
@@ -4463,8 +5145,8 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4463
5145
|
let behaviorsFlag = "";
|
|
4464
5146
|
let legacyFallbackWarned = false;
|
|
4465
5147
|
if (!useExeAgent && !useBinSymlink) {
|
|
4466
|
-
const identityPath =
|
|
4467
|
-
|
|
5148
|
+
const identityPath = path15.join(
|
|
5149
|
+
os9.homedir(),
|
|
4468
5150
|
".exe-os",
|
|
4469
5151
|
"identity",
|
|
4470
5152
|
`${employeeName2}.md`
|
|
@@ -4473,13 +5155,13 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4473
5155
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4474
5156
|
if (hasAgentFlag) {
|
|
4475
5157
|
identityFlag = ` --agent ${employeeName2}`;
|
|
4476
|
-
} else if (
|
|
5158
|
+
} else if (existsSync12(identityPath)) {
|
|
4477
5159
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4478
5160
|
legacyFallbackWarned = true;
|
|
4479
5161
|
}
|
|
4480
5162
|
const behaviorsFile = exportBehaviorsSync(
|
|
4481
5163
|
employeeName2,
|
|
4482
|
-
|
|
5164
|
+
path15.basename(spawnCwd),
|
|
4483
5165
|
sessionName
|
|
4484
5166
|
);
|
|
4485
5167
|
if (behaviorsFile) {
|
|
@@ -4494,9 +5176,9 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4494
5176
|
}
|
|
4495
5177
|
let sessionContextFlag = "";
|
|
4496
5178
|
try {
|
|
4497
|
-
const ctxDir =
|
|
5179
|
+
const ctxDir = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
4498
5180
|
mkdirSync6(ctxDir, { recursive: true });
|
|
4499
|
-
const ctxFile =
|
|
5181
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4500
5182
|
const ctxContent = [
|
|
4501
5183
|
`## Session Context`,
|
|
4502
5184
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4580,7 +5262,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4580
5262
|
transport.pipeLog(sessionName, logFile);
|
|
4581
5263
|
try {
|
|
4582
5264
|
const mySession = getMySession();
|
|
4583
|
-
const dispatchInfo =
|
|
5265
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4584
5266
|
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4585
5267
|
dispatchedBy: mySession,
|
|
4586
5268
|
rootExe: exeSession2,
|
|
@@ -4655,15 +5337,15 @@ var init_tmux_routing = __esm({
|
|
|
4655
5337
|
init_intercom_queue();
|
|
4656
5338
|
init_plan_limits();
|
|
4657
5339
|
init_employees();
|
|
4658
|
-
SPAWN_LOCK_DIR =
|
|
4659
|
-
SESSION_CACHE =
|
|
5340
|
+
SPAWN_LOCK_DIR = path15.join(os9.homedir(), ".exe-os", "spawn-locks");
|
|
5341
|
+
SESSION_CACHE = path15.join(os9.homedir(), ".exe-os", "session-cache");
|
|
4660
5342
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4661
5343
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4662
5344
|
VERIFY_PANE_LINES = 200;
|
|
4663
5345
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4664
5346
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
4665
|
-
INTERCOM_LOG2 =
|
|
4666
|
-
DEBOUNCE_FILE =
|
|
5347
|
+
INTERCOM_LOG2 = path15.join(os9.homedir(), ".exe-os", "intercom.log");
|
|
5348
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4667
5349
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4668
5350
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
4669
5351
|
}
|
|
@@ -4674,6 +5356,7 @@ var shard_manager_exports = {};
|
|
|
4674
5356
|
__export(shard_manager_exports, {
|
|
4675
5357
|
disposeShards: () => disposeShards,
|
|
4676
5358
|
ensureShardSchema: () => ensureShardSchema,
|
|
5359
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
4677
5360
|
getReadyShardClient: () => getReadyShardClient,
|
|
4678
5361
|
getShardClient: () => getShardClient,
|
|
4679
5362
|
getShardsDir: () => getShardsDir,
|
|
@@ -4682,15 +5365,18 @@ __export(shard_manager_exports, {
|
|
|
4682
5365
|
listShards: () => listShards,
|
|
4683
5366
|
shardExists: () => shardExists
|
|
4684
5367
|
});
|
|
4685
|
-
import
|
|
4686
|
-
import { existsSync as
|
|
5368
|
+
import path17 from "path";
|
|
5369
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
4687
5370
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4688
5371
|
function initShardManager(encryptionKey) {
|
|
4689
5372
|
_encryptionKey = encryptionKey;
|
|
4690
|
-
if (!
|
|
5373
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
4691
5374
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4692
5375
|
}
|
|
4693
5376
|
_shardingEnabled = true;
|
|
5377
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
5378
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
5379
|
+
_evictionTimer.unref();
|
|
4694
5380
|
}
|
|
4695
5381
|
function isShardingEnabled() {
|
|
4696
5382
|
return _shardingEnabled;
|
|
@@ -4707,21 +5393,28 @@ function getShardClient(projectName) {
|
|
|
4707
5393
|
throw new Error(`Invalid project name for shard: "${projectName}"`);
|
|
4708
5394
|
}
|
|
4709
5395
|
const cached = _shards.get(safeName);
|
|
4710
|
-
if (cached)
|
|
4711
|
-
|
|
5396
|
+
if (cached) {
|
|
5397
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
5398
|
+
return cached;
|
|
5399
|
+
}
|
|
5400
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
5401
|
+
evictLRU();
|
|
5402
|
+
}
|
|
5403
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4712
5404
|
const client = createClient2({
|
|
4713
5405
|
url: `file:${dbPath}`,
|
|
4714
5406
|
encryptionKey: _encryptionKey
|
|
4715
5407
|
});
|
|
4716
5408
|
_shards.set(safeName, client);
|
|
5409
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
4717
5410
|
return client;
|
|
4718
5411
|
}
|
|
4719
5412
|
function shardExists(projectName) {
|
|
4720
5413
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4721
|
-
return
|
|
5414
|
+
return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4722
5415
|
}
|
|
4723
5416
|
function listShards() {
|
|
4724
|
-
if (!
|
|
5417
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4725
5418
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4726
5419
|
}
|
|
4727
5420
|
async function ensureShardSchema(client) {
|
|
@@ -4773,6 +5466,8 @@ async function ensureShardSchema(client) {
|
|
|
4773
5466
|
for (const col of [
|
|
4774
5467
|
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
4775
5468
|
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
5469
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
5470
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
4776
5471
|
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
4777
5472
|
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
4778
5473
|
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
@@ -4795,7 +5490,23 @@ async function ensureShardSchema(client) {
|
|
|
4795
5490
|
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
4796
5491
|
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
4797
5492
|
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
4798
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
5493
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
5494
|
+
// Metadata enrichment columns (must match database.ts)
|
|
5495
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
5496
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
5497
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
5498
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
5499
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
5500
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
5501
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
5502
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
5503
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
5504
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
5505
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
5506
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
5507
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
5508
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
5509
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
4799
5510
|
]) {
|
|
4800
5511
|
try {
|
|
4801
5512
|
await client.execute(col);
|
|
@@ -4894,21 +5605,69 @@ async function getReadyShardClient(projectName) {
|
|
|
4894
5605
|
await ensureShardSchema(client);
|
|
4895
5606
|
return client;
|
|
4896
5607
|
}
|
|
5608
|
+
function evictLRU() {
|
|
5609
|
+
let oldest = null;
|
|
5610
|
+
let oldestTime = Infinity;
|
|
5611
|
+
for (const [name, time] of _shardLastAccess) {
|
|
5612
|
+
if (time < oldestTime) {
|
|
5613
|
+
oldestTime = time;
|
|
5614
|
+
oldest = name;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
if (oldest) {
|
|
5618
|
+
const client = _shards.get(oldest);
|
|
5619
|
+
if (client) {
|
|
5620
|
+
client.close();
|
|
5621
|
+
}
|
|
5622
|
+
_shards.delete(oldest);
|
|
5623
|
+
_shardLastAccess.delete(oldest);
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
function evictIdleShards() {
|
|
5627
|
+
const now = Date.now();
|
|
5628
|
+
const toEvict = [];
|
|
5629
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
5630
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
5631
|
+
toEvict.push(name);
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
for (const name of toEvict) {
|
|
5635
|
+
const client = _shards.get(name);
|
|
5636
|
+
if (client) {
|
|
5637
|
+
client.close();
|
|
5638
|
+
}
|
|
5639
|
+
_shards.delete(name);
|
|
5640
|
+
_shardLastAccess.delete(name);
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
function getOpenShardCount() {
|
|
5644
|
+
return _shards.size;
|
|
5645
|
+
}
|
|
4897
5646
|
function disposeShards() {
|
|
5647
|
+
if (_evictionTimer) {
|
|
5648
|
+
clearInterval(_evictionTimer);
|
|
5649
|
+
_evictionTimer = null;
|
|
5650
|
+
}
|
|
4898
5651
|
for (const [, client] of _shards) {
|
|
4899
5652
|
client.close();
|
|
4900
5653
|
}
|
|
4901
5654
|
_shards.clear();
|
|
5655
|
+
_shardLastAccess.clear();
|
|
4902
5656
|
_shardingEnabled = false;
|
|
4903
5657
|
_encryptionKey = null;
|
|
4904
5658
|
}
|
|
4905
|
-
var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
|
|
5659
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
4906
5660
|
var init_shard_manager = __esm({
|
|
4907
5661
|
"src/lib/shard-manager.ts"() {
|
|
4908
5662
|
"use strict";
|
|
4909
5663
|
init_config();
|
|
4910
|
-
SHARDS_DIR =
|
|
5664
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
5665
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
5666
|
+
MAX_OPEN_SHARDS = 10;
|
|
5667
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
4911
5668
|
_shards = /* @__PURE__ */ new Map();
|
|
5669
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
5670
|
+
_evictionTimer = null;
|
|
4912
5671
|
_encryptionKey = null;
|
|
4913
5672
|
_shardingEnabled = false;
|
|
4914
5673
|
}
|
|
@@ -5111,16 +5870,16 @@ init_database();
|
|
|
5111
5870
|
|
|
5112
5871
|
// src/lib/keychain.ts
|
|
5113
5872
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5114
|
-
import { existsSync as
|
|
5115
|
-
import
|
|
5116
|
-
import
|
|
5873
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5874
|
+
import path16 from "path";
|
|
5875
|
+
import os10 from "os";
|
|
5117
5876
|
var SERVICE = "exe-mem";
|
|
5118
5877
|
var ACCOUNT = "master-key";
|
|
5119
5878
|
function getKeyDir() {
|
|
5120
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5879
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os10.homedir(), ".exe-os");
|
|
5121
5880
|
}
|
|
5122
5881
|
function getKeyPath() {
|
|
5123
|
-
return
|
|
5882
|
+
return path16.join(getKeyDir(), "master.key");
|
|
5124
5883
|
}
|
|
5125
5884
|
async function tryKeytar() {
|
|
5126
5885
|
try {
|
|
@@ -5141,9 +5900,9 @@ async function getMasterKey() {
|
|
|
5141
5900
|
}
|
|
5142
5901
|
}
|
|
5143
5902
|
const keyPath = getKeyPath();
|
|
5144
|
-
if (!
|
|
5903
|
+
if (!existsSync13(keyPath)) {
|
|
5145
5904
|
process.stderr.write(
|
|
5146
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
5905
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5147
5906
|
`
|
|
5148
5907
|
);
|
|
5149
5908
|
return null;
|