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