@askexenow/exe-os 0.8.33 → 0.8.37
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 +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- package/src/commands/exe/rename.md +12 -0
package/dist/mcp/server.js
CHANGED
|
@@ -454,11 +454,11 @@ async function connectEmbedDaemon() {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
const start = Date.now();
|
|
457
|
-
let
|
|
457
|
+
let delay2 = 100;
|
|
458
458
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
459
|
-
await new Promise((r) => setTimeout(r,
|
|
459
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
460
460
|
if (await connectToSocket()) return true;
|
|
461
|
-
|
|
461
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
462
462
|
}
|
|
463
463
|
return false;
|
|
464
464
|
}
|
|
@@ -550,11 +550,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
550
550
|
`);
|
|
551
551
|
killAndRespawnDaemon();
|
|
552
552
|
const start = Date.now();
|
|
553
|
-
let
|
|
553
|
+
let delay2 = 200;
|
|
554
554
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
555
|
-
await new Promise((r) => setTimeout(r,
|
|
555
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
556
556
|
if (await connectToSocket()) break;
|
|
557
|
-
|
|
557
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
558
558
|
}
|
|
559
559
|
if (!_connected) return null;
|
|
560
560
|
}
|
|
@@ -566,11 +566,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
566
566
|
`);
|
|
567
567
|
killAndRespawnDaemon();
|
|
568
568
|
const start = Date.now();
|
|
569
|
-
let
|
|
569
|
+
let delay2 = 200;
|
|
570
570
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
571
|
-
await new Promise((r) => setTimeout(r,
|
|
571
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
572
572
|
if (await connectToSocket()) break;
|
|
573
|
-
|
|
573
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
574
574
|
}
|
|
575
575
|
if (!_connected) return null;
|
|
576
576
|
const retry = await sendRequest([text], priority);
|
|
@@ -681,12 +681,68 @@ var init_embedder = __esm({
|
|
|
681
681
|
}
|
|
682
682
|
});
|
|
683
683
|
|
|
684
|
+
// src/lib/db-retry.ts
|
|
685
|
+
function isBusyError(err) {
|
|
686
|
+
if (err instanceof Error) {
|
|
687
|
+
const msg = err.message.toLowerCase();
|
|
688
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
689
|
+
}
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
function delay(ms) {
|
|
693
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
694
|
+
}
|
|
695
|
+
async function retryOnBusy(fn, label) {
|
|
696
|
+
let lastError;
|
|
697
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
698
|
+
try {
|
|
699
|
+
return await fn();
|
|
700
|
+
} catch (err) {
|
|
701
|
+
lastError = err;
|
|
702
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
703
|
+
throw err;
|
|
704
|
+
}
|
|
705
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
706
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
707
|
+
process.stderr.write(
|
|
708
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
709
|
+
`
|
|
710
|
+
);
|
|
711
|
+
await delay(backoff + jitter);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
throw lastError;
|
|
715
|
+
}
|
|
716
|
+
function wrapWithRetry(client) {
|
|
717
|
+
return new Proxy(client, {
|
|
718
|
+
get(target, prop, receiver) {
|
|
719
|
+
if (prop === "execute") {
|
|
720
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
721
|
+
}
|
|
722
|
+
if (prop === "batch") {
|
|
723
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
724
|
+
}
|
|
725
|
+
return Reflect.get(target, prop, receiver);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
730
|
+
var init_db_retry = __esm({
|
|
731
|
+
"src/lib/db-retry.ts"() {
|
|
732
|
+
"use strict";
|
|
733
|
+
MAX_RETRIES = 3;
|
|
734
|
+
BASE_DELAY_MS = 200;
|
|
735
|
+
MAX_JITTER_MS = 300;
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
684
739
|
// src/lib/database.ts
|
|
685
740
|
import { createClient } from "@libsql/client";
|
|
686
741
|
async function initDatabase(config2) {
|
|
687
742
|
if (_client) {
|
|
688
743
|
_client.close();
|
|
689
744
|
_client = null;
|
|
745
|
+
_resilientClient = null;
|
|
690
746
|
}
|
|
691
747
|
const opts = {
|
|
692
748
|
url: `file:${config2.dbPath}`
|
|
@@ -695,20 +751,27 @@ async function initDatabase(config2) {
|
|
|
695
751
|
opts.encryptionKey = config2.encryptionKey;
|
|
696
752
|
}
|
|
697
753
|
_client = createClient(opts);
|
|
754
|
+
_resilientClient = wrapWithRetry(_client);
|
|
698
755
|
}
|
|
699
756
|
function isInitialized() {
|
|
700
757
|
return _client !== null;
|
|
701
758
|
}
|
|
702
759
|
function getClient() {
|
|
760
|
+
if (!_resilientClient) {
|
|
761
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
762
|
+
}
|
|
763
|
+
return _resilientClient;
|
|
764
|
+
}
|
|
765
|
+
function getRawClient() {
|
|
703
766
|
if (!_client) {
|
|
704
767
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
705
768
|
}
|
|
706
769
|
return _client;
|
|
707
770
|
}
|
|
708
771
|
async function ensureSchema() {
|
|
709
|
-
const client =
|
|
772
|
+
const client = getRawClient();
|
|
710
773
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
711
|
-
await client.execute("PRAGMA busy_timeout =
|
|
774
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
712
775
|
try {
|
|
713
776
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
714
777
|
} catch {
|
|
@@ -1501,13 +1564,16 @@ async function disposeDatabase() {
|
|
|
1501
1564
|
if (_client) {
|
|
1502
1565
|
_client.close();
|
|
1503
1566
|
_client = null;
|
|
1567
|
+
_resilientClient = null;
|
|
1504
1568
|
}
|
|
1505
1569
|
}
|
|
1506
|
-
var _client, initTurso, disposeTurso;
|
|
1570
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1507
1571
|
var init_database = __esm({
|
|
1508
1572
|
"src/lib/database.ts"() {
|
|
1509
1573
|
"use strict";
|
|
1574
|
+
init_db_retry();
|
|
1510
1575
|
_client = null;
|
|
1576
|
+
_resilientClient = null;
|
|
1511
1577
|
initTurso = initDatabase;
|
|
1512
1578
|
disposeTurso = disposeDatabase;
|
|
1513
1579
|
}
|
|
@@ -1620,7 +1686,7 @@ function listShards() {
|
|
|
1620
1686
|
}
|
|
1621
1687
|
async function ensureShardSchema(client) {
|
|
1622
1688
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1623
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1689
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1624
1690
|
try {
|
|
1625
1691
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1626
1692
|
} catch {
|
|
@@ -1881,7 +1947,8 @@ async function writeMemory(record) {
|
|
|
1881
1947
|
has_error: record.has_error ? 1 : 0,
|
|
1882
1948
|
raw_text: record.raw_text,
|
|
1883
1949
|
vector: record.vector,
|
|
1884
|
-
version:
|
|
1950
|
+
version: 0,
|
|
1951
|
+
// Placeholder — assigned atomically at flush time
|
|
1885
1952
|
task_id: record.task_id ?? null,
|
|
1886
1953
|
importance: record.importance ?? 5,
|
|
1887
1954
|
status: record.status ?? "active",
|
|
@@ -1915,6 +1982,13 @@ async function flushBatch() {
|
|
|
1915
1982
|
_flushing = true;
|
|
1916
1983
|
try {
|
|
1917
1984
|
const batch = _pendingRecords.slice(0);
|
|
1985
|
+
const client = getClient();
|
|
1986
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1987
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1988
|
+
for (const row of batch) {
|
|
1989
|
+
row.version = baseVersion++;
|
|
1990
|
+
}
|
|
1991
|
+
_nextVersion = baseVersion;
|
|
1918
1992
|
const buildStmt = (row) => {
|
|
1919
1993
|
const hasVector = row.vector !== null;
|
|
1920
1994
|
const taskId = row.task_id ?? null;
|
|
@@ -2817,7 +2891,7 @@ function getActiveAgent() {
|
|
|
2817
2891
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
2818
2892
|
{ encoding: "utf8", timeout: 2e3 }
|
|
2819
2893
|
).trim();
|
|
2820
|
-
const empMatch = sessionName.match(/^(
|
|
2894
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
2821
2895
|
if (empMatch && empMatch[1] !== "exe") {
|
|
2822
2896
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
2823
2897
|
}
|
|
@@ -2890,10 +2964,40 @@ var init_active_agent = __esm({
|
|
|
2890
2964
|
});
|
|
2891
2965
|
|
|
2892
2966
|
// src/lib/employees.ts
|
|
2967
|
+
var employees_exports = {};
|
|
2968
|
+
__export(employees_exports, {
|
|
2969
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
2970
|
+
addEmployee: () => addEmployee,
|
|
2971
|
+
getEmployee: () => getEmployee,
|
|
2972
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
2973
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
2974
|
+
hasRole: () => hasRole,
|
|
2975
|
+
isMultiInstance: () => isMultiInstance,
|
|
2976
|
+
loadEmployees: () => loadEmployees,
|
|
2977
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
2978
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
2979
|
+
saveEmployees: () => saveEmployees,
|
|
2980
|
+
validateEmployeeName: () => validateEmployeeName
|
|
2981
|
+
});
|
|
2893
2982
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2894
|
-
import { existsSync as existsSync7, symlinkSync, readlinkSync } from "fs";
|
|
2983
|
+
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5 } from "fs";
|
|
2895
2984
|
import { execSync as execSync5 } from "child_process";
|
|
2896
2985
|
import path9 from "path";
|
|
2986
|
+
function validateEmployeeName(name) {
|
|
2987
|
+
if (!name) {
|
|
2988
|
+
return { valid: false, error: "Name is required" };
|
|
2989
|
+
}
|
|
2990
|
+
if (name.length > 32) {
|
|
2991
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
2992
|
+
}
|
|
2993
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
2994
|
+
return {
|
|
2995
|
+
valid: false,
|
|
2996
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
return { valid: true };
|
|
3000
|
+
}
|
|
2897
3001
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
2898
3002
|
if (!existsSync7(employeesPath)) {
|
|
2899
3003
|
return [];
|
|
@@ -2909,6 +3013,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
2909
3013
|
await mkdir3(path9.dirname(employeesPath), { recursive: true });
|
|
2910
3014
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
2911
3015
|
}
|
|
3016
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
3017
|
+
if (!existsSync7(employeesPath)) return [];
|
|
3018
|
+
try {
|
|
3019
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
3020
|
+
} catch {
|
|
3021
|
+
return [];
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
function getEmployee(employees, name) {
|
|
3025
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
3026
|
+
}
|
|
3027
|
+
function getEmployeeByRole(employees, role) {
|
|
3028
|
+
const lower = role.toLowerCase();
|
|
3029
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
3030
|
+
}
|
|
3031
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
3032
|
+
const lower = role.toLowerCase();
|
|
3033
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
3034
|
+
}
|
|
3035
|
+
function hasRole(agentName, role) {
|
|
3036
|
+
const employees = loadEmployeesSync();
|
|
3037
|
+
const emp = getEmployee(employees, agentName);
|
|
3038
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
3039
|
+
}
|
|
3040
|
+
function isMultiInstance(agentName, employees) {
|
|
3041
|
+
const roster = employees ?? loadEmployeesSync();
|
|
3042
|
+
const emp = getEmployee(roster, agentName);
|
|
3043
|
+
if (!emp) return false;
|
|
3044
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
3045
|
+
}
|
|
2912
3046
|
function addEmployee(employees, employee) {
|
|
2913
3047
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
2914
3048
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -2916,12 +3050,48 @@ function addEmployee(employees, employee) {
|
|
|
2916
3050
|
}
|
|
2917
3051
|
return [...employees, normalized];
|
|
2918
3052
|
}
|
|
2919
|
-
|
|
3053
|
+
function registerBinSymlinks(name) {
|
|
3054
|
+
const created = [];
|
|
3055
|
+
const skipped = [];
|
|
3056
|
+
const errors = [];
|
|
3057
|
+
let exeBinPath;
|
|
3058
|
+
try {
|
|
3059
|
+
exeBinPath = execSync5("which exe", { encoding: "utf-8" }).trim();
|
|
3060
|
+
} catch {
|
|
3061
|
+
errors.push("Could not find 'exe' in PATH");
|
|
3062
|
+
return { created, skipped, errors };
|
|
3063
|
+
}
|
|
3064
|
+
const binDir = path9.dirname(exeBinPath);
|
|
3065
|
+
let target;
|
|
3066
|
+
try {
|
|
3067
|
+
target = readlinkSync(exeBinPath);
|
|
3068
|
+
} catch {
|
|
3069
|
+
errors.push("Could not read 'exe' symlink");
|
|
3070
|
+
return { created, skipped, errors };
|
|
3071
|
+
}
|
|
3072
|
+
for (const suffix of ["", "-opencode"]) {
|
|
3073
|
+
const linkName = `${name}${suffix}`;
|
|
3074
|
+
const linkPath = path9.join(binDir, linkName);
|
|
3075
|
+
if (existsSync7(linkPath)) {
|
|
3076
|
+
skipped.push(linkName);
|
|
3077
|
+
continue;
|
|
3078
|
+
}
|
|
3079
|
+
try {
|
|
3080
|
+
symlinkSync(target, linkPath);
|
|
3081
|
+
created.push(linkName);
|
|
3082
|
+
} catch (err) {
|
|
3083
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
return { created, skipped, errors };
|
|
3087
|
+
}
|
|
3088
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
2920
3089
|
var init_employees = __esm({
|
|
2921
3090
|
"src/lib/employees.ts"() {
|
|
2922
3091
|
"use strict";
|
|
2923
3092
|
init_config();
|
|
2924
3093
|
EMPLOYEES_PATH = path9.join(EXE_AI_DIR, "exe-employees.json");
|
|
3094
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
2925
3095
|
}
|
|
2926
3096
|
});
|
|
2927
3097
|
|
|
@@ -2940,7 +3110,7 @@ __export(license_exports, {
|
|
|
2940
3110
|
saveLicense: () => saveLicense,
|
|
2941
3111
|
validateLicense: () => validateLicense
|
|
2942
3112
|
});
|
|
2943
|
-
import { readFileSync as
|
|
3113
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
2944
3114
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2945
3115
|
import path10 from "path";
|
|
2946
3116
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -2948,14 +3118,14 @@ function loadDeviceId() {
|
|
|
2948
3118
|
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
2949
3119
|
try {
|
|
2950
3120
|
if (existsSync8(deviceJsonPath)) {
|
|
2951
|
-
const data = JSON.parse(
|
|
3121
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
2952
3122
|
if (data.deviceId) return data.deviceId;
|
|
2953
3123
|
}
|
|
2954
3124
|
} catch {
|
|
2955
3125
|
}
|
|
2956
3126
|
try {
|
|
2957
3127
|
if (existsSync8(DEVICE_ID_PATH)) {
|
|
2958
|
-
const id2 =
|
|
3128
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
2959
3129
|
if (id2) return id2;
|
|
2960
3130
|
}
|
|
2961
3131
|
} catch {
|
|
@@ -2968,7 +3138,7 @@ function loadDeviceId() {
|
|
|
2968
3138
|
function loadLicense() {
|
|
2969
3139
|
try {
|
|
2970
3140
|
if (!existsSync8(LICENSE_PATH)) return null;
|
|
2971
|
-
return
|
|
3141
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
2972
3142
|
} catch {
|
|
2973
3143
|
return null;
|
|
2974
3144
|
}
|
|
@@ -3002,7 +3172,7 @@ async function verifyLicenseJwt(token) {
|
|
|
3002
3172
|
async function getCachedLicense() {
|
|
3003
3173
|
try {
|
|
3004
3174
|
if (!existsSync8(CACHE_PATH)) return null;
|
|
3005
|
-
const raw = JSON.parse(
|
|
3175
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
3006
3176
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3007
3177
|
return await verifyLicenseJwt(raw.token);
|
|
3008
3178
|
} catch {
|
|
@@ -3012,7 +3182,7 @@ async function getCachedLicense() {
|
|
|
3012
3182
|
function readCachedToken() {
|
|
3013
3183
|
try {
|
|
3014
3184
|
if (!existsSync8(CACHE_PATH)) return null;
|
|
3015
|
-
const raw = JSON.parse(
|
|
3185
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
3016
3186
|
return typeof raw.token === "string" ? raw.token : null;
|
|
3017
3187
|
} catch {
|
|
3018
3188
|
return null;
|
|
@@ -3234,12 +3404,12 @@ __export(plan_limits_exports, {
|
|
|
3234
3404
|
countActiveMemories: () => countActiveMemories,
|
|
3235
3405
|
getLicenseSync: () => getLicenseSync
|
|
3236
3406
|
});
|
|
3237
|
-
import { readFileSync as
|
|
3407
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
3238
3408
|
import path11 from "path";
|
|
3239
3409
|
function getLicenseSync() {
|
|
3240
3410
|
try {
|
|
3241
3411
|
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
3242
|
-
const raw = JSON.parse(
|
|
3412
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
3243
3413
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
3244
3414
|
const parts = raw.token.split(".");
|
|
3245
3415
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -3307,7 +3477,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
3307
3477
|
let count = 0;
|
|
3308
3478
|
try {
|
|
3309
3479
|
if (existsSync9(filePath)) {
|
|
3310
|
-
const raw =
|
|
3480
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
3311
3481
|
const employees = JSON.parse(raw);
|
|
3312
3482
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
3313
3483
|
}
|
|
@@ -3353,7 +3523,7 @@ import crypto5 from "crypto";
|
|
|
3353
3523
|
import path14 from "path";
|
|
3354
3524
|
import os2 from "os";
|
|
3355
3525
|
import {
|
|
3356
|
-
readFileSync as
|
|
3526
|
+
readFileSync as readFileSync8,
|
|
3357
3527
|
readdirSync as readdirSync3,
|
|
3358
3528
|
unlinkSync as unlinkSync3,
|
|
3359
3529
|
existsSync as existsSync10,
|
|
@@ -3405,7 +3575,7 @@ import crypto6 from "crypto";
|
|
|
3405
3575
|
import path15 from "path";
|
|
3406
3576
|
import { execSync as execSync6 } from "child_process";
|
|
3407
3577
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3408
|
-
import { existsSync as existsSync11, readFileSync as
|
|
3578
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3409
3579
|
async function writeCheckpoint(input) {
|
|
3410
3580
|
const client = getClient();
|
|
3411
3581
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3782,7 +3952,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3782
3952
|
const gitignorePath = path15.join(baseDir, ".gitignore");
|
|
3783
3953
|
try {
|
|
3784
3954
|
if (existsSync11(gitignorePath)) {
|
|
3785
|
-
const content =
|
|
3955
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
3786
3956
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3787
3957
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3788
3958
|
} else {
|
|
@@ -3802,7 +3972,7 @@ var init_tasks_crud = __esm({
|
|
|
3802
3972
|
});
|
|
3803
3973
|
|
|
3804
3974
|
// src/lib/session-registry.ts
|
|
3805
|
-
import { readFileSync as
|
|
3975
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "fs";
|
|
3806
3976
|
import path16 from "path";
|
|
3807
3977
|
import os3 from "os";
|
|
3808
3978
|
function registerSession(entry) {
|
|
@@ -3821,7 +3991,7 @@ function registerSession(entry) {
|
|
|
3821
3991
|
}
|
|
3822
3992
|
function listSessions() {
|
|
3823
3993
|
try {
|
|
3824
|
-
const raw =
|
|
3994
|
+
const raw = readFileSync10(REGISTRY_PATH, "utf8");
|
|
3825
3995
|
return JSON.parse(raw);
|
|
3826
3996
|
} catch {
|
|
3827
3997
|
return [];
|
|
@@ -4016,7 +4186,7 @@ var init_provider_table = __esm({
|
|
|
4016
4186
|
});
|
|
4017
4187
|
|
|
4018
4188
|
// src/lib/intercom-queue.ts
|
|
4019
|
-
import { readFileSync as
|
|
4189
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
|
|
4020
4190
|
import path17 from "path";
|
|
4021
4191
|
import os4 from "os";
|
|
4022
4192
|
function ensureDir() {
|
|
@@ -4026,7 +4196,7 @@ function ensureDir() {
|
|
|
4026
4196
|
function readQueue() {
|
|
4027
4197
|
try {
|
|
4028
4198
|
if (!existsSync13(QUEUE_PATH)) return [];
|
|
4029
|
-
return JSON.parse(
|
|
4199
|
+
return JSON.parse(readFileSync11(QUEUE_PATH, "utf8"));
|
|
4030
4200
|
} catch {
|
|
4031
4201
|
return [];
|
|
4032
4202
|
}
|
|
@@ -4066,10 +4236,46 @@ var init_intercom_queue = __esm({
|
|
|
4066
4236
|
|
|
4067
4237
|
// src/lib/tmux-routing.ts
|
|
4068
4238
|
import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
|
|
4069
|
-
import { readFileSync as
|
|
4239
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync } from "fs";
|
|
4070
4240
|
import path18 from "path";
|
|
4071
4241
|
import os5 from "os";
|
|
4072
4242
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4243
|
+
import { unlinkSync as unlinkSync4 } from "fs";
|
|
4244
|
+
function spawnLockPath(sessionName) {
|
|
4245
|
+
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4246
|
+
}
|
|
4247
|
+
function isProcessAlive(pid) {
|
|
4248
|
+
try {
|
|
4249
|
+
process.kill(pid, 0);
|
|
4250
|
+
return true;
|
|
4251
|
+
} catch {
|
|
4252
|
+
return false;
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
function acquireSpawnLock2(sessionName) {
|
|
4256
|
+
if (!existsSync14(SPAWN_LOCK_DIR)) {
|
|
4257
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4258
|
+
}
|
|
4259
|
+
const lockFile = spawnLockPath(sessionName);
|
|
4260
|
+
if (existsSync14(lockFile)) {
|
|
4261
|
+
try {
|
|
4262
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
4263
|
+
const age = Date.now() - lock.timestamp;
|
|
4264
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4265
|
+
return false;
|
|
4266
|
+
}
|
|
4267
|
+
} catch {
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4271
|
+
return true;
|
|
4272
|
+
}
|
|
4273
|
+
function releaseSpawnLock2(sessionName) {
|
|
4274
|
+
try {
|
|
4275
|
+
unlinkSync4(spawnLockPath(sessionName));
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4073
4279
|
function resolveBehaviorsExporterScript() {
|
|
4074
4280
|
try {
|
|
4075
4281
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -4115,7 +4321,7 @@ function extractRootExe(name) {
|
|
|
4115
4321
|
}
|
|
4116
4322
|
function getParentExe(sessionKey) {
|
|
4117
4323
|
try {
|
|
4118
|
-
const data = JSON.parse(
|
|
4324
|
+
const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4119
4325
|
return data.parentExe || null;
|
|
4120
4326
|
} catch {
|
|
4121
4327
|
return null;
|
|
@@ -4123,7 +4329,7 @@ function getParentExe(sessionKey) {
|
|
|
4123
4329
|
}
|
|
4124
4330
|
function getDispatchedBy(sessionKey) {
|
|
4125
4331
|
try {
|
|
4126
|
-
const data = JSON.parse(
|
|
4332
|
+
const data = JSON.parse(readFileSync12(
|
|
4127
4333
|
path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4128
4334
|
"utf8"
|
|
4129
4335
|
));
|
|
@@ -4150,17 +4356,17 @@ function isEmployeeAlive(sessionName) {
|
|
|
4150
4356
|
}
|
|
4151
4357
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4152
4358
|
const base = employeeSessionName(employeeName, exeSession);
|
|
4153
|
-
if (!isAlive(base)) return 0;
|
|
4359
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4154
4360
|
for (let i = 2; i <= maxInstances; i++) {
|
|
4155
4361
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4156
|
-
if (!isAlive(candidate)) return i;
|
|
4362
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4157
4363
|
}
|
|
4158
4364
|
return null;
|
|
4159
4365
|
}
|
|
4160
4366
|
function readDebounceState() {
|
|
4161
4367
|
try {
|
|
4162
4368
|
if (!existsSync14(DEBOUNCE_FILE)) return {};
|
|
4163
|
-
return JSON.parse(
|
|
4369
|
+
return JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
4164
4370
|
} catch {
|
|
4165
4371
|
return {};
|
|
4166
4372
|
}
|
|
@@ -4356,7 +4562,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4356
4562
|
const claudeJsonPath = path18.join(os5.homedir(), ".claude.json");
|
|
4357
4563
|
let claudeJson = {};
|
|
4358
4564
|
try {
|
|
4359
|
-
claudeJson = JSON.parse(
|
|
4565
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
4360
4566
|
} catch {
|
|
4361
4567
|
}
|
|
4362
4568
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4374,7 +4580,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4374
4580
|
const settingsPath = path18.join(projSettingsDir, "settings.json");
|
|
4375
4581
|
let settings = {};
|
|
4376
4582
|
try {
|
|
4377
|
-
settings = JSON.parse(
|
|
4583
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
4378
4584
|
} catch {
|
|
4379
4585
|
}
|
|
4380
4586
|
const perms = settings.permissions ?? {};
|
|
@@ -4487,6 +4693,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4487
4693
|
command: spawnCommand
|
|
4488
4694
|
});
|
|
4489
4695
|
if (spawnResult.error) {
|
|
4696
|
+
releaseSpawnLock2(sessionName);
|
|
4490
4697
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4491
4698
|
}
|
|
4492
4699
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -4524,6 +4731,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4524
4731
|
}
|
|
4525
4732
|
}
|
|
4526
4733
|
if (!booted) {
|
|
4734
|
+
releaseSpawnLock2(sessionName);
|
|
4527
4735
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4528
4736
|
}
|
|
4529
4737
|
if (!useExeAgent) {
|
|
@@ -4540,9 +4748,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4540
4748
|
pid: 0,
|
|
4541
4749
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4542
4750
|
});
|
|
4751
|
+
releaseSpawnLock2(sessionName);
|
|
4543
4752
|
return { sessionName };
|
|
4544
4753
|
}
|
|
4545
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
4754
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
4546
4755
|
var init_tmux_routing = __esm({
|
|
4547
4756
|
"src/lib/tmux-routing.ts"() {
|
|
4548
4757
|
"use strict";
|
|
@@ -4554,6 +4763,7 @@ var init_tmux_routing = __esm({
|
|
|
4554
4763
|
init_provider_table();
|
|
4555
4764
|
init_intercom_queue();
|
|
4556
4765
|
init_plan_limits();
|
|
4766
|
+
SPAWN_LOCK_DIR = path18.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
4557
4767
|
SESSION_CACHE = path18.join(os5.homedir(), ".exe-os", "session-cache");
|
|
4558
4768
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4559
4769
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -4566,7 +4776,7 @@ var init_tmux_routing = __esm({
|
|
|
4566
4776
|
|
|
4567
4777
|
// src/lib/tasks-review.ts
|
|
4568
4778
|
import path19 from "path";
|
|
4569
|
-
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as
|
|
4779
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
4570
4780
|
async function countPendingReviews() {
|
|
4571
4781
|
const client = getClient();
|
|
4572
4782
|
const result = await client.execute({
|
|
@@ -4690,7 +4900,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4690
4900
|
if (existsSync15(cacheDir)) {
|
|
4691
4901
|
for (const f of readdirSync4(cacheDir)) {
|
|
4692
4902
|
if (f.startsWith("review-notified-")) {
|
|
4693
|
-
|
|
4903
|
+
unlinkSync5(path19.join(cacheDir, f));
|
|
4694
4904
|
}
|
|
4695
4905
|
}
|
|
4696
4906
|
}
|
|
@@ -4879,7 +5089,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
4879
5089
|
} else {
|
|
4880
5090
|
const projectDir = input.projectDir ?? process.cwd();
|
|
4881
5091
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
4882
|
-
autoInstance: input.assignedTo
|
|
5092
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
4883
5093
|
});
|
|
4884
5094
|
if (result.status === "failed") {
|
|
4885
5095
|
process.stderr.write(
|
|
@@ -4914,6 +5124,7 @@ var init_tasks_notify = __esm({
|
|
|
4914
5124
|
init_session_key();
|
|
4915
5125
|
init_notifications();
|
|
4916
5126
|
init_transport();
|
|
5127
|
+
init_employees();
|
|
4917
5128
|
}
|
|
4918
5129
|
});
|
|
4919
5130
|
|
|
@@ -5276,7 +5487,7 @@ __export(tasks_exports, {
|
|
|
5276
5487
|
writeCheckpoint: () => writeCheckpoint
|
|
5277
5488
|
});
|
|
5278
5489
|
import path21 from "path";
|
|
5279
|
-
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as
|
|
5490
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
|
|
5280
5491
|
async function createTask(input) {
|
|
5281
5492
|
const result = await createTaskCore(input);
|
|
5282
5493
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -5302,7 +5513,7 @@ async function updateTask(input) {
|
|
|
5302
5513
|
writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
5303
5514
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
5304
5515
|
try {
|
|
5305
|
-
|
|
5516
|
+
unlinkSync6(cachePath);
|
|
5306
5517
|
} catch {
|
|
5307
5518
|
}
|
|
5308
5519
|
}
|
|
@@ -5611,7 +5822,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
5611
5822
|
return true;
|
|
5612
5823
|
} catch {
|
|
5613
5824
|
const newRetryCount = msg.retryCount + 1;
|
|
5614
|
-
if (newRetryCount >=
|
|
5825
|
+
if (newRetryCount >= MAX_RETRIES2) {
|
|
5615
5826
|
await markFailed(messageId, "session unavailable after 10 retries");
|
|
5616
5827
|
} else {
|
|
5617
5828
|
await client.execute({
|
|
@@ -5629,25 +5840,25 @@ async function markFailed(messageId, reason) {
|
|
|
5629
5840
|
args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
|
|
5630
5841
|
});
|
|
5631
5842
|
}
|
|
5632
|
-
var
|
|
5843
|
+
var MAX_RETRIES2, _wsClientSend;
|
|
5633
5844
|
var init_messaging = __esm({
|
|
5634
5845
|
"src/lib/messaging.ts"() {
|
|
5635
5846
|
"use strict";
|
|
5636
5847
|
init_database();
|
|
5637
5848
|
init_tmux_routing();
|
|
5638
|
-
|
|
5849
|
+
MAX_RETRIES2 = 10;
|
|
5639
5850
|
_wsClientSend = null;
|
|
5640
5851
|
}
|
|
5641
5852
|
});
|
|
5642
5853
|
|
|
5643
5854
|
// src/gateway/whatsapp-accounts.ts
|
|
5644
|
-
import { readFileSync as
|
|
5855
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
5645
5856
|
import { join } from "path";
|
|
5646
5857
|
import { homedir } from "os";
|
|
5647
5858
|
function loadAccounts() {
|
|
5648
5859
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
5649
5860
|
try {
|
|
5650
|
-
const raw =
|
|
5861
|
+
const raw = readFileSync14(CONFIG_PATH2, "utf8");
|
|
5651
5862
|
const parsed = JSON.parse(raw);
|
|
5652
5863
|
if (!Array.isArray(parsed)) {
|
|
5653
5864
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -5680,6 +5891,376 @@ var init_whatsapp_accounts = __esm({
|
|
|
5680
5891
|
}
|
|
5681
5892
|
});
|
|
5682
5893
|
|
|
5894
|
+
// src/lib/consolidation.ts
|
|
5895
|
+
var consolidation_exports = {};
|
|
5896
|
+
__export(consolidation_exports, {
|
|
5897
|
+
buildConsolidationPrompt: () => buildConsolidationPrompt,
|
|
5898
|
+
consolidateCluster: () => consolidateCluster,
|
|
5899
|
+
countUnconsolidated: () => countUnconsolidated,
|
|
5900
|
+
groupMemories: () => groupMemories,
|
|
5901
|
+
isUserIdle: () => isUserIdle,
|
|
5902
|
+
pushToWiki: () => pushToWiki,
|
|
5903
|
+
runConsolidation: () => runConsolidation,
|
|
5904
|
+
selectUnconsolidated: () => selectUnconsolidated,
|
|
5905
|
+
storeConsolidation: () => storeConsolidation
|
|
5906
|
+
});
|
|
5907
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
5908
|
+
async function selectUnconsolidated(client, limit = 200) {
|
|
5909
|
+
const result = await client.execute({
|
|
5910
|
+
sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
|
|
5911
|
+
FROM memories
|
|
5912
|
+
WHERE consolidated = 0
|
|
5913
|
+
AND timestamp >= datetime('now', '-7 days')
|
|
5914
|
+
ORDER BY timestamp DESC
|
|
5915
|
+
LIMIT ?`,
|
|
5916
|
+
args: [limit]
|
|
5917
|
+
});
|
|
5918
|
+
return result.rows.map((row) => ({
|
|
5919
|
+
id: row.id,
|
|
5920
|
+
agent_id: row.agent_id,
|
|
5921
|
+
project_name: row.project_name,
|
|
5922
|
+
tool_name: row.tool_name,
|
|
5923
|
+
raw_text: row.raw_text,
|
|
5924
|
+
timestamp: row.timestamp
|
|
5925
|
+
}));
|
|
5926
|
+
}
|
|
5927
|
+
function groupMemories(memories) {
|
|
5928
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
5929
|
+
for (const mem of memories) {
|
|
5930
|
+
const key = `${mem.agent_id}::${mem.project_name}`;
|
|
5931
|
+
const list = byProject.get(key) ?? [];
|
|
5932
|
+
list.push(mem);
|
|
5933
|
+
byProject.set(key, list);
|
|
5934
|
+
}
|
|
5935
|
+
const clusters = [];
|
|
5936
|
+
for (const [key, projectMemories] of byProject) {
|
|
5937
|
+
const [agentId, projectName] = key.split("::");
|
|
5938
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
5939
|
+
for (const mem of projectMemories) {
|
|
5940
|
+
const day = mem.timestamp.slice(0, 10);
|
|
5941
|
+
const list = byDay.get(day) ?? [];
|
|
5942
|
+
list.push(mem);
|
|
5943
|
+
byDay.set(day, list);
|
|
5944
|
+
}
|
|
5945
|
+
for (const [day, dayMemories] of byDay) {
|
|
5946
|
+
if (dayMemories.length <= 15) {
|
|
5947
|
+
clusters.push({
|
|
5948
|
+
agentId,
|
|
5949
|
+
projectName,
|
|
5950
|
+
dateRange: day,
|
|
5951
|
+
memories: dayMemories
|
|
5952
|
+
});
|
|
5953
|
+
} else {
|
|
5954
|
+
for (let i = 0; i < dayMemories.length; i += 12) {
|
|
5955
|
+
const chunk = dayMemories.slice(i, i + 12);
|
|
5956
|
+
clusters.push({
|
|
5957
|
+
agentId,
|
|
5958
|
+
projectName,
|
|
5959
|
+
dateRange: day,
|
|
5960
|
+
memories: chunk
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
return clusters;
|
|
5967
|
+
}
|
|
5968
|
+
function buildConsolidationPrompt(cluster) {
|
|
5969
|
+
const snippets = cluster.memories.map((m, i) => {
|
|
5970
|
+
const text = m.raw_text.length > 200 ? m.raw_text.slice(0, 200) + "..." : m.raw_text;
|
|
5971
|
+
return `${i + 1}. [${m.tool_name}] ${text}`;
|
|
5972
|
+
}).join("\n");
|
|
5973
|
+
return `You are reviewing a set of work memories from an AI coding agent.
|
|
5974
|
+
These are raw tool call records from ${cluster.dateRange}.
|
|
5975
|
+
|
|
5976
|
+
Agent: ${cluster.agentId} | Project: ${cluster.projectName} | Date: ${cluster.dateRange}
|
|
5977
|
+
|
|
5978
|
+
MEMORIES:
|
|
5979
|
+
${snippets}
|
|
5980
|
+
|
|
5981
|
+
Extract EXACTLY THREE types of insights:
|
|
5982
|
+
|
|
5983
|
+
1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
|
|
5984
|
+
2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
|
|
5985
|
+
3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)
|
|
5986
|
+
|
|
5987
|
+
Format each as a single sentence. Be specific \u2014 include file names,
|
|
5988
|
+
function names, and concrete details. Skip if no insight for a category.`;
|
|
5989
|
+
}
|
|
5990
|
+
async function consolidateCluster(cluster, model) {
|
|
5991
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
5992
|
+
const client = new Anthropic();
|
|
5993
|
+
const prompt = buildConsolidationPrompt(cluster);
|
|
5994
|
+
const response = await client.messages.create({
|
|
5995
|
+
model,
|
|
5996
|
+
max_tokens: 300,
|
|
5997
|
+
messages: [{ role: "user", content: prompt }]
|
|
5998
|
+
});
|
|
5999
|
+
const textBlock = response.content.find((b) => b.type === "text");
|
|
6000
|
+
return textBlock?.text ?? "";
|
|
6001
|
+
}
|
|
6002
|
+
async function storeConsolidation(client, cluster, synthesisText, embedFn) {
|
|
6003
|
+
const consolidatedId = randomUUID4();
|
|
6004
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6005
|
+
const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
|
|
6006
|
+
|
|
6007
|
+
${synthesisText}`;
|
|
6008
|
+
let vector = null;
|
|
6009
|
+
if (embedFn) {
|
|
6010
|
+
try {
|
|
6011
|
+
vector = await embedFn(rawText);
|
|
6012
|
+
} catch {
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
const insertSql = vector ? `INSERT INTO memories
|
|
6016
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
6017
|
+
tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
|
|
6018
|
+
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
|
|
6019
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
6020
|
+
tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
|
|
6021
|
+
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
|
|
6022
|
+
const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
|
|
6023
|
+
await client.execute({ sql: insertSql, args: insertArgs });
|
|
6024
|
+
const sourceIds = cluster.memories.map((m) => m.id);
|
|
6025
|
+
const linkStmts = sourceIds.map((sourceId) => ({
|
|
6026
|
+
sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
|
|
6027
|
+
VALUES (?, ?, ?, ?)`,
|
|
6028
|
+
args: [randomUUID4(), consolidatedId, sourceId, now]
|
|
6029
|
+
}));
|
|
6030
|
+
const placeholders = sourceIds.map(() => "?").join(",");
|
|
6031
|
+
const markStmt = {
|
|
6032
|
+
sql: `UPDATE memories SET consolidated = 1 WHERE id IN (${placeholders})`,
|
|
6033
|
+
args: sourceIds
|
|
6034
|
+
};
|
|
6035
|
+
await client.batch([...linkStmts, markStmt], "write");
|
|
6036
|
+
return { consolidatedMemoryId: consolidatedId, sourceIds, rawText };
|
|
6037
|
+
}
|
|
6038
|
+
async function pushToWiki(consolidation, config2) {
|
|
6039
|
+
if (!config2.wikiEnabled || !config2.wikiAutoUpdate) {
|
|
6040
|
+
return { updated: false };
|
|
6041
|
+
}
|
|
6042
|
+
const apiUrl = config2.wikiUrl || process.env.EXE_WIKI_API_URL;
|
|
6043
|
+
const apiKey = config2.wikiApiKey || process.env.EXE_WIKI_API_KEY;
|
|
6044
|
+
if (!apiUrl || !apiKey) {
|
|
6045
|
+
return { updated: false };
|
|
6046
|
+
}
|
|
6047
|
+
const workspace = config2.wikiWorkspaceMapping[consolidation.projectName] ?? consolidation.projectName;
|
|
6048
|
+
try {
|
|
6049
|
+
const listRes = await fetch(
|
|
6050
|
+
`${apiUrl}/v1/workspace/${encodeURIComponent(workspace)}/documents`,
|
|
6051
|
+
{
|
|
6052
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
6053
|
+
signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
|
|
6054
|
+
}
|
|
6055
|
+
);
|
|
6056
|
+
if (!listRes.ok) {
|
|
6057
|
+
return { updated: false, error: `Workspace "${workspace}" not accessible (${listRes.status})` };
|
|
6058
|
+
}
|
|
6059
|
+
const listJson = await listRes.json();
|
|
6060
|
+
const docs = listJson.documents ?? listJson.localFiles?.items ?? [];
|
|
6061
|
+
const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
|
|
6062
|
+
const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
6063
|
+
let bestMatch = null;
|
|
6064
|
+
for (const doc of docs) {
|
|
6065
|
+
if (!doc.id || !doc.title) continue;
|
|
6066
|
+
const titleWords = doc.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
6067
|
+
if (titleWords.length === 0) continue;
|
|
6068
|
+
const matchCount = titleWords.filter(
|
|
6069
|
+
(tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
|
|
6070
|
+
).length;
|
|
6071
|
+
const score = matchCount / titleWords.length;
|
|
6072
|
+
if (score > (bestMatch?.score ?? 0)) {
|
|
6073
|
+
bestMatch = { id: doc.id, title: doc.title, score };
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
if (bestMatch && bestMatch.score >= config2.wikiAutoUpdateThreshold) {
|
|
6077
|
+
const updateRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
6078
|
+
method: "POST",
|
|
6079
|
+
headers: {
|
|
6080
|
+
"Content-Type": "application/json",
|
|
6081
|
+
Authorization: `Bearer ${apiKey}`
|
|
6082
|
+
},
|
|
6083
|
+
body: JSON.stringify({
|
|
6084
|
+
textContent: consolidation.rawText,
|
|
6085
|
+
metadata: { title: bestMatch.title, appendTo: bestMatch.id },
|
|
6086
|
+
workspaceSlugs: [workspace]
|
|
6087
|
+
}),
|
|
6088
|
+
signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
|
|
6089
|
+
});
|
|
6090
|
+
if (updateRes.ok) {
|
|
6091
|
+
process.stderr.write(
|
|
6092
|
+
`[consolidation] Wiki auto-updated: ${workspace}/${bestMatch.title}
|
|
6093
|
+
`
|
|
6094
|
+
);
|
|
6095
|
+
return { updated: true, action: "updated", page: bestMatch.title };
|
|
6096
|
+
}
|
|
6097
|
+
return { updated: false, error: `Update failed (${updateRes.status})` };
|
|
6098
|
+
}
|
|
6099
|
+
if (config2.wikiAutoUpdateCreateNew) {
|
|
6100
|
+
const title = `Consolidated Insights \u2014 ${consolidation.projectName} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`;
|
|
6101
|
+
const createRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
6102
|
+
method: "POST",
|
|
6103
|
+
headers: {
|
|
6104
|
+
"Content-Type": "application/json",
|
|
6105
|
+
Authorization: `Bearer ${apiKey}`
|
|
6106
|
+
},
|
|
6107
|
+
body: JSON.stringify({
|
|
6108
|
+
textContent: consolidation.rawText,
|
|
6109
|
+
metadata: { title },
|
|
6110
|
+
workspaceSlugs: [workspace]
|
|
6111
|
+
}),
|
|
6112
|
+
signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
|
|
6113
|
+
});
|
|
6114
|
+
if (createRes.ok) {
|
|
6115
|
+
process.stderr.write(
|
|
6116
|
+
`[consolidation] Wiki page created: ${workspace}/${title}
|
|
6117
|
+
`
|
|
6118
|
+
);
|
|
6119
|
+
return { updated: true, action: "created", page: title };
|
|
6120
|
+
}
|
|
6121
|
+
return { updated: false, error: `Create failed (${createRes.status})` };
|
|
6122
|
+
}
|
|
6123
|
+
return { updated: false };
|
|
6124
|
+
} catch (err) {
|
|
6125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6126
|
+
process.stderr.write(`[consolidation] Wiki push failed: ${msg}
|
|
6127
|
+
`);
|
|
6128
|
+
return { updated: false, error: msg };
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
async function runConsolidation(client, options) {
|
|
6132
|
+
const memories = await selectUnconsolidated(client);
|
|
6133
|
+
if (memories.length < 20) {
|
|
6134
|
+
return { clustersProcessed: 0, memoriesConsolidated: 0 };
|
|
6135
|
+
}
|
|
6136
|
+
const clusters = groupMemories(memories);
|
|
6137
|
+
let clustersProcessed = 0;
|
|
6138
|
+
let memoriesConsolidated = 0;
|
|
6139
|
+
for (const cluster of clusters) {
|
|
6140
|
+
if (clustersProcessed >= options.maxCalls) break;
|
|
6141
|
+
if (cluster.memories.length < 3) continue;
|
|
6142
|
+
try {
|
|
6143
|
+
const isExe = cluster.agentId === "exe";
|
|
6144
|
+
if (isExe) {
|
|
6145
|
+
const synthesis = await consolidateCluster(cluster, options.model);
|
|
6146
|
+
if (!synthesis.trim()) continue;
|
|
6147
|
+
const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
|
|
6148
|
+
if (options.wikiConfig) {
|
|
6149
|
+
await pushToWiki(
|
|
6150
|
+
{ ...result, projectName: cluster.projectName },
|
|
6151
|
+
options.wikiConfig
|
|
6152
|
+
).catch((err) => {
|
|
6153
|
+
process.stderr.write(
|
|
6154
|
+
`[consolidation] Wiki push error (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
6155
|
+
`
|
|
6156
|
+
);
|
|
6157
|
+
});
|
|
6158
|
+
}
|
|
6159
|
+
const sourceIds = result.sourceIds;
|
|
6160
|
+
if (sourceIds.length > 0) {
|
|
6161
|
+
const placeholders = sourceIds.map(() => "?").join(",");
|
|
6162
|
+
await client.execute({
|
|
6163
|
+
sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
|
|
6164
|
+
args: sourceIds
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
} else {
|
|
6168
|
+
const dedupCount = await dedupCluster(client, cluster, options.embedFn);
|
|
6169
|
+
memoriesConsolidated += dedupCount;
|
|
6170
|
+
if (dedupCount === 0) continue;
|
|
6171
|
+
}
|
|
6172
|
+
clustersProcessed++;
|
|
6173
|
+
memoriesConsolidated += isExe ? cluster.memories.length : 0;
|
|
6174
|
+
} catch (err) {
|
|
6175
|
+
process.stderr.write(
|
|
6176
|
+
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
6177
|
+
`
|
|
6178
|
+
);
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
return { clustersProcessed, memoriesConsolidated };
|
|
6182
|
+
}
|
|
6183
|
+
async function dedupCluster(client, cluster, embedFn) {
|
|
6184
|
+
if (!embedFn || cluster.memories.length < 2) return 0;
|
|
6185
|
+
const vectors = [];
|
|
6186
|
+
for (const mem of cluster.memories) {
|
|
6187
|
+
try {
|
|
6188
|
+
const v = await embedFn(mem.raw_text.slice(0, 500));
|
|
6189
|
+
vectors.push({ id: mem.id, vector: v });
|
|
6190
|
+
} catch {
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
if (vectors.length < 2) return 0;
|
|
6194
|
+
const toArchive = /* @__PURE__ */ new Set();
|
|
6195
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
6196
|
+
if (toArchive.has(vectors[i].id)) continue;
|
|
6197
|
+
for (let j = i + 1; j < vectors.length; j++) {
|
|
6198
|
+
if (toArchive.has(vectors[j].id)) continue;
|
|
6199
|
+
const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
|
|
6200
|
+
if (sim > 0.95) {
|
|
6201
|
+
toArchive.add(vectors[j].id);
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
if (toArchive.size === 0) return 0;
|
|
6206
|
+
const ids = [...toArchive];
|
|
6207
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
6208
|
+
await client.execute({
|
|
6209
|
+
sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
|
|
6210
|
+
args: ids
|
|
6211
|
+
});
|
|
6212
|
+
const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
|
|
6213
|
+
if (survivors.length > 0) {
|
|
6214
|
+
const survivorPlaceholders = survivors.map(() => "?").join(",");
|
|
6215
|
+
await client.execute({
|
|
6216
|
+
sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
|
|
6217
|
+
args: survivors
|
|
6218
|
+
});
|
|
6219
|
+
}
|
|
6220
|
+
return ids.length;
|
|
6221
|
+
}
|
|
6222
|
+
function cosineSimilarity(a, b) {
|
|
6223
|
+
let dot = 0, normA = 0, normB = 0;
|
|
6224
|
+
for (let i = 0; i < a.length; i++) {
|
|
6225
|
+
dot += a[i] * b[i];
|
|
6226
|
+
normA += a[i] * a[i];
|
|
6227
|
+
normB += b[i] * b[i];
|
|
6228
|
+
}
|
|
6229
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
6230
|
+
return denom === 0 ? 0 : dot / denom;
|
|
6231
|
+
}
|
|
6232
|
+
async function isUserIdle(client, idleMinutes = 30) {
|
|
6233
|
+
const result = await client.execute({
|
|
6234
|
+
sql: `SELECT MAX(timestamp) as last_activity
|
|
6235
|
+
FROM memories
|
|
6236
|
+
WHERE tool_name != 'consolidation'
|
|
6237
|
+
AND timestamp >= datetime('now', '-1 day')`,
|
|
6238
|
+
args: []
|
|
6239
|
+
});
|
|
6240
|
+
const lastActivity = result.rows[0]?.last_activity;
|
|
6241
|
+
if (!lastActivity) return true;
|
|
6242
|
+
const lastMs = new Date(lastActivity).getTime();
|
|
6243
|
+
const now = Date.now();
|
|
6244
|
+
return now - lastMs >= idleMinutes * 60 * 1e3;
|
|
6245
|
+
}
|
|
6246
|
+
async function countUnconsolidated(client) {
|
|
6247
|
+
const result = await client.execute({
|
|
6248
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
6249
|
+
WHERE consolidated = 0
|
|
6250
|
+
AND timestamp >= datetime('now', '-7 days')`,
|
|
6251
|
+
args: []
|
|
6252
|
+
});
|
|
6253
|
+
return Number(result.rows[0]?.cnt ?? 0);
|
|
6254
|
+
}
|
|
6255
|
+
var WIKI_FETCH_TIMEOUT_MS;
|
|
6256
|
+
var init_consolidation = __esm({
|
|
6257
|
+
"src/lib/consolidation.ts"() {
|
|
6258
|
+
"use strict";
|
|
6259
|
+
init_store();
|
|
6260
|
+
WIKI_FETCH_TIMEOUT_MS = 1e4;
|
|
6261
|
+
}
|
|
6262
|
+
});
|
|
6263
|
+
|
|
5683
6264
|
// src/mcp/server.ts
|
|
5684
6265
|
init_embedder();
|
|
5685
6266
|
init_store();
|
|
@@ -6229,7 +6810,8 @@ function registerGetSessionContext(server2) {
|
|
|
6229
6810
|
has_error, raw_text, vector, task_id
|
|
6230
6811
|
FROM memories
|
|
6231
6812
|
WHERE session_id = ?
|
|
6232
|
-
ORDER BY timestamp ASC
|
|
6813
|
+
ORDER BY timestamp ASC
|
|
6814
|
+
LIMIT 500`,
|
|
6233
6815
|
args: [session_id]
|
|
6234
6816
|
});
|
|
6235
6817
|
if (result.rows.length === 0) {
|
|
@@ -6779,8 +7361,8 @@ function registerCreateTask(server2) {
|
|
|
6779
7361
|
try {
|
|
6780
7362
|
const exeSession = resolveExeSession();
|
|
6781
7363
|
if (exeSession) {
|
|
6782
|
-
const
|
|
6783
|
-
const useAutoInstance =
|
|
7364
|
+
const { isMultiInstance: isMultiInstance2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
7365
|
+
const useAutoInstance = isMultiInstance2(assigned_to);
|
|
6784
7366
|
const { loadConfigSync: loadConfigSync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6785
7367
|
const cfg = loadConfigSync2();
|
|
6786
7368
|
const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
|
|
@@ -7588,7 +8170,7 @@ import { z as z20 } from "zod";
|
|
|
7588
8170
|
// src/lib/identity.ts
|
|
7589
8171
|
init_config();
|
|
7590
8172
|
init_database();
|
|
7591
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as
|
|
8173
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
7592
8174
|
import { readdirSync as readdirSync5 } from "fs";
|
|
7593
8175
|
import path22 from "path";
|
|
7594
8176
|
import { createHash } from "crypto";
|
|
@@ -7641,7 +8223,7 @@ function contentHash(content) {
|
|
|
7641
8223
|
function getIdentity(agentId) {
|
|
7642
8224
|
const filePath = identityPath(agentId);
|
|
7643
8225
|
if (!existsSync16(filePath)) return null;
|
|
7644
|
-
const raw =
|
|
8226
|
+
const raw = readFileSync13(filePath, "utf-8");
|
|
7645
8227
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
7646
8228
|
return {
|
|
7647
8229
|
agentId,
|
|
@@ -7932,12 +8514,20 @@ async function resolveWorkspace(slug_or_id, name) {
|
|
|
7932
8514
|
};
|
|
7933
8515
|
}
|
|
7934
8516
|
async function embedTexts(texts) {
|
|
7935
|
-
const
|
|
7936
|
-
|
|
7937
|
-
)
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
8517
|
+
const BATCH_SIZE = 50;
|
|
8518
|
+
const results = [];
|
|
8519
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
8520
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
8521
|
+
const settled = await Promise.allSettled(
|
|
8522
|
+
batch.map((text) => embed(text))
|
|
8523
|
+
);
|
|
8524
|
+
results.push(
|
|
8525
|
+
...settled.map(
|
|
8526
|
+
(result) => result.status === "fulfilled" ? result.value : null
|
|
8527
|
+
)
|
|
8528
|
+
);
|
|
8529
|
+
}
|
|
8530
|
+
return results;
|
|
7941
8531
|
}
|
|
7942
8532
|
function buildChunkStatement(input) {
|
|
7943
8533
|
const hasVector = input.vector !== null;
|
|
@@ -8297,7 +8887,6 @@ function registerAcknowledgeMessages(server2) {
|
|
|
8297
8887
|
const agent = getActiveAgent();
|
|
8298
8888
|
const agentId = agent.agentId || "exe";
|
|
8299
8889
|
const client = getClient();
|
|
8300
|
-
await client.execute({ sql: "PRAGMA busy_timeout = 5000", args: [] });
|
|
8301
8890
|
const result = await client.execute({
|
|
8302
8891
|
sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
|
|
8303
8892
|
WHERE target_agent = ? AND status IN ('pending', 'delivered')`,
|
|
@@ -8627,7 +9216,7 @@ function registerSendWhatsapp(server2) {
|
|
|
8627
9216
|
import { z as z29 } from "zod";
|
|
8628
9217
|
|
|
8629
9218
|
// src/automation/trigger-engine.ts
|
|
8630
|
-
import { readFileSync as
|
|
9219
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
8631
9220
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
8632
9221
|
import path23 from "path";
|
|
8633
9222
|
import os6 from "os";
|
|
@@ -8635,7 +9224,7 @@ var TRIGGERS_PATH = path23.join(os6.homedir(), ".exe-os", "triggers.json");
|
|
|
8635
9224
|
function loadTriggers(project) {
|
|
8636
9225
|
if (!existsSync17(TRIGGERS_PATH)) return [];
|
|
8637
9226
|
try {
|
|
8638
|
-
const raw =
|
|
9227
|
+
const raw = readFileSync15(TRIGGERS_PATH, "utf-8");
|
|
8639
9228
|
const all = JSON.parse(raw);
|
|
8640
9229
|
if (!Array.isArray(all)) return [];
|
|
8641
9230
|
if (project) {
|
|
@@ -8933,7 +9522,7 @@ function registerListTriggers(server2) {
|
|
|
8933
9522
|
import { z as z31 } from "zod";
|
|
8934
9523
|
|
|
8935
9524
|
// src/automation/starter-packs/index.ts
|
|
8936
|
-
import { readFileSync as
|
|
9525
|
+
import { readFileSync as readFileSync16, readdirSync as readdirSync6, existsSync as existsSync18 } from "fs";
|
|
8937
9526
|
import path24 from "path";
|
|
8938
9527
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8939
9528
|
var __dirname = path24.dirname(fileURLToPath3(import.meta.url));
|
|
@@ -8955,7 +9544,7 @@ function loadPack(industry) {
|
|
|
8955
9544
|
let customObjects = [];
|
|
8956
9545
|
try {
|
|
8957
9546
|
customObjects = JSON.parse(
|
|
8958
|
-
|
|
9547
|
+
readFileSync16(objectsPath, "utf-8")
|
|
8959
9548
|
);
|
|
8960
9549
|
} catch {
|
|
8961
9550
|
customObjects = [];
|
|
@@ -8964,7 +9553,7 @@ function loadPack(industry) {
|
|
|
8964
9553
|
if (existsSync18(triggersPath)) {
|
|
8965
9554
|
try {
|
|
8966
9555
|
triggers = JSON.parse(
|
|
8967
|
-
|
|
9556
|
+
readFileSync16(triggersPath, "utf-8")
|
|
8968
9557
|
);
|
|
8969
9558
|
} catch {
|
|
8970
9559
|
triggers = [];
|
|
@@ -8974,7 +9563,7 @@ function loadPack(industry) {
|
|
|
8974
9563
|
if (existsSync18(wikiDir)) {
|
|
8975
9564
|
const files = readdirSync6(wikiDir).filter((f) => f.endsWith(".md"));
|
|
8976
9565
|
for (const file of files) {
|
|
8977
|
-
const content =
|
|
9566
|
+
const content = readFileSync16(path24.join(wikiDir, file), "utf-8");
|
|
8978
9567
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
8979
9568
|
wikiSeeds.push({
|
|
8980
9569
|
filename: file,
|
|
@@ -8986,7 +9575,7 @@ function loadPack(industry) {
|
|
|
8986
9575
|
let manifest = {};
|
|
8987
9576
|
if (existsSync18(manifestPath)) {
|
|
8988
9577
|
try {
|
|
8989
|
-
manifest = JSON.parse(
|
|
9578
|
+
manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
|
|
8990
9579
|
} catch {
|
|
8991
9580
|
manifest = {};
|
|
8992
9581
|
}
|
|
@@ -8994,7 +9583,7 @@ function loadPack(industry) {
|
|
|
8994
9583
|
let identityContext = null;
|
|
8995
9584
|
if (existsSync18(identityContextPath)) {
|
|
8996
9585
|
try {
|
|
8997
|
-
identityContext =
|
|
9586
|
+
identityContext = readFileSync16(identityContextPath, "utf-8");
|
|
8998
9587
|
} catch {
|
|
8999
9588
|
identityContext = null;
|
|
9000
9589
|
}
|
|
@@ -10474,7 +11063,7 @@ function registerQueryConversations(server2) {
|
|
|
10474
11063
|
|
|
10475
11064
|
// src/mcp/tools/load-skill.ts
|
|
10476
11065
|
import { z as z39 } from "zod";
|
|
10477
|
-
import { readFileSync as
|
|
11066
|
+
import { readFileSync as readFileSync17, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
|
|
10478
11067
|
import path27 from "path";
|
|
10479
11068
|
import { homedir as homedir2 } from "os";
|
|
10480
11069
|
var SKILLS_DIR = path27.join(homedir2(), ".claude", "skills");
|
|
@@ -10531,7 +11120,7 @@ ${skills.map((s) => `- ${s}`).join("\n")}`
|
|
|
10531
11120
|
const sanitized = path27.basename(skill_name);
|
|
10532
11121
|
const skillFile = path27.join(SKILLS_DIR, sanitized, "SKILL.md");
|
|
10533
11122
|
try {
|
|
10534
|
-
const content =
|
|
11123
|
+
const content = readFileSync17(skillFile, "utf-8");
|
|
10535
11124
|
return {
|
|
10536
11125
|
content: [{
|
|
10537
11126
|
type: "text",
|
|
@@ -10557,12 +11146,133 @@ ${available.map((s) => `- ${s}`).join("\n")}` : "\n\nNo skills found in ~/.claud
|
|
|
10557
11146
|
);
|
|
10558
11147
|
}
|
|
10559
11148
|
|
|
11149
|
+
// src/mcp/tools/consolidate-memories.ts
|
|
11150
|
+
init_database();
|
|
11151
|
+
init_active_agent();
|
|
11152
|
+
import { z as z40 } from "zod";
|
|
11153
|
+
function registerConsolidateMemories(server2) {
|
|
11154
|
+
server2.registerTool(
|
|
11155
|
+
"consolidate_memories",
|
|
11156
|
+
{
|
|
11157
|
+
title: "Consolidate Memories",
|
|
11158
|
+
description: "Trigger content-aware memory consolidation. Groups recent memories by project, uses LLM to extract key facts, decisions, and patterns, then stores a high-importance summary and archives the originals. Runs automatically during idle time, but can be triggered manually when you want to compress your working memory.",
|
|
11159
|
+
inputSchema: {
|
|
11160
|
+
max_clusters: z40.coerce.number().optional().default(5).describe("Maximum number of clusters to consolidate (default 5)"),
|
|
11161
|
+
model: z40.string().optional().describe("LLM model for summarization (defaults to config value)")
|
|
11162
|
+
}
|
|
11163
|
+
},
|
|
11164
|
+
async ({ max_clusters, model }) => {
|
|
11165
|
+
const { agentId } = getActiveAgent();
|
|
11166
|
+
try {
|
|
11167
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
11168
|
+
const config2 = await loadConfig2();
|
|
11169
|
+
const consolidationModel = model ?? config2.consolidationModel ?? "claude-haiku-4-5-20251001";
|
|
11170
|
+
const { runConsolidation: runConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
|
|
11171
|
+
const client = getClient();
|
|
11172
|
+
let embedFn;
|
|
11173
|
+
try {
|
|
11174
|
+
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
11175
|
+
embedFn = embed2;
|
|
11176
|
+
} catch {
|
|
11177
|
+
}
|
|
11178
|
+
const result = await runConsolidation2(client, {
|
|
11179
|
+
model: consolidationModel,
|
|
11180
|
+
maxCalls: max_clusters,
|
|
11181
|
+
embedFn
|
|
11182
|
+
});
|
|
11183
|
+
if (result.clustersProcessed === 0) {
|
|
11184
|
+
return {
|
|
11185
|
+
content: [{
|
|
11186
|
+
type: "text",
|
|
11187
|
+
text: `No consolidation needed \u2014 fewer than 20 unconsolidated memories for ${agentId}.`
|
|
11188
|
+
}]
|
|
11189
|
+
};
|
|
11190
|
+
}
|
|
11191
|
+
return {
|
|
11192
|
+
content: [{
|
|
11193
|
+
type: "text",
|
|
11194
|
+
text: `Consolidation complete:
|
|
11195
|
+
- Clusters processed: ${result.clustersProcessed}
|
|
11196
|
+
- Memories consolidated: ${result.memoriesConsolidated}
|
|
11197
|
+
|
|
11198
|
+
Consolidated summaries stored as tier-1 (importance=9) memories.`
|
|
11199
|
+
}]
|
|
11200
|
+
};
|
|
11201
|
+
} catch (err) {
|
|
11202
|
+
return {
|
|
11203
|
+
content: [{
|
|
11204
|
+
type: "text",
|
|
11205
|
+
text: `Consolidation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11206
|
+
}]
|
|
11207
|
+
};
|
|
11208
|
+
}
|
|
11209
|
+
}
|
|
11210
|
+
);
|
|
11211
|
+
}
|
|
11212
|
+
|
|
11213
|
+
// src/lib/telemetry.ts
|
|
11214
|
+
var ENABLED = process.env.EXE_TELEMETRY === "1";
|
|
11215
|
+
var initialized = false;
|
|
11216
|
+
async function ensureInit() {
|
|
11217
|
+
if (initialized || !ENABLED) return;
|
|
11218
|
+
initialized = true;
|
|
11219
|
+
try {
|
|
11220
|
+
const { NodeSDK } = await import("@opentelemetry/sdk-node");
|
|
11221
|
+
const { ConsoleSpanExporter } = await import("@opentelemetry/sdk-trace-base");
|
|
11222
|
+
const sdk = new NodeSDK({
|
|
11223
|
+
serviceName: "exe-os",
|
|
11224
|
+
traceExporter: new ConsoleSpanExporter()
|
|
11225
|
+
});
|
|
11226
|
+
sdk.start();
|
|
11227
|
+
process.stderr.write("[exe-os] OpenTelemetry tracing enabled\n");
|
|
11228
|
+
} catch (err) {
|
|
11229
|
+
process.stderr.write(
|
|
11230
|
+
`[exe-os] OpenTelemetry init failed: ${err instanceof Error ? err.message : String(err)}
|
|
11231
|
+
`
|
|
11232
|
+
);
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
async function withTrace(toolName, fn) {
|
|
11236
|
+
if (!ENABLED) return fn();
|
|
11237
|
+
await ensureInit();
|
|
11238
|
+
const { trace, SpanStatusCode } = await import("@opentelemetry/api");
|
|
11239
|
+
const tracer = trace.getTracer("exe-os", "1.0.0");
|
|
11240
|
+
return tracer.startActiveSpan(`mcp.tool.${toolName}`, async (span) => {
|
|
11241
|
+
span.setAttribute("mcp.tool.name", toolName);
|
|
11242
|
+
try {
|
|
11243
|
+
const result = await fn();
|
|
11244
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
11245
|
+
return result;
|
|
11246
|
+
} catch (err) {
|
|
11247
|
+
span.setStatus({
|
|
11248
|
+
code: SpanStatusCode.ERROR,
|
|
11249
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11250
|
+
});
|
|
11251
|
+
span.recordException(
|
|
11252
|
+
err instanceof Error ? err : new Error(String(err))
|
|
11253
|
+
);
|
|
11254
|
+
throw err;
|
|
11255
|
+
} finally {
|
|
11256
|
+
span.end();
|
|
11257
|
+
}
|
|
11258
|
+
});
|
|
11259
|
+
}
|
|
11260
|
+
function instrumentServer(server2) {
|
|
11261
|
+
if (!ENABLED) return;
|
|
11262
|
+
const original = server2.registerTool.bind(server2);
|
|
11263
|
+
server2.registerTool = (name, config2, handler) => {
|
|
11264
|
+
const traced = async (...args) => withTrace(name, () => handler(...args));
|
|
11265
|
+
return original(name, config2, traced);
|
|
11266
|
+
};
|
|
11267
|
+
}
|
|
11268
|
+
|
|
10560
11269
|
// src/mcp/server.ts
|
|
10561
11270
|
var server = new McpServer({
|
|
10562
11271
|
name: "exe-os",
|
|
10563
11272
|
version: "1.3.0"
|
|
10564
11273
|
});
|
|
10565
11274
|
var _backfillTimer = null;
|
|
11275
|
+
instrumentServer(server);
|
|
10566
11276
|
registerRecallMyMemory(server);
|
|
10567
11277
|
registerAskTeamMemory(server);
|
|
10568
11278
|
registerGetSessionContext(server);
|
|
@@ -10603,6 +11313,7 @@ registerUpdateWikiPage(server);
|
|
|
10603
11313
|
registerDeployClient(server);
|
|
10604
11314
|
registerQueryConversations(server);
|
|
10605
11315
|
registerLoadSkill(server);
|
|
11316
|
+
registerConsolidateMemories(server);
|
|
10606
11317
|
try {
|
|
10607
11318
|
await initStore();
|
|
10608
11319
|
process.stderr.write("[exe-os] MCP server starting...\n");
|
|
@@ -10678,3 +11389,18 @@ async function shutdown() {
|
|
|
10678
11389
|
}
|
|
10679
11390
|
process.on("SIGINT", () => void shutdown());
|
|
10680
11391
|
process.on("SIGTERM", () => void shutdown());
|
|
11392
|
+
process.on("uncaughtException", (err) => {
|
|
11393
|
+
process.stderr.write(
|
|
11394
|
+
`[exe-os] uncaughtException (non-fatal): ${err.message}
|
|
11395
|
+
`
|
|
11396
|
+
);
|
|
11397
|
+
if (err.stack) {
|
|
11398
|
+
process.stderr.write(`[exe-os] ${err.stack.split("\n").slice(1, 3).join("\n ")}
|
|
11399
|
+
`);
|
|
11400
|
+
}
|
|
11401
|
+
});
|
|
11402
|
+
process.on("unhandledRejection", (reason) => {
|
|
11403
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
11404
|
+
process.stderr.write(`[exe-os] unhandledRejection (non-fatal): ${msg}
|
|
11405
|
+
`);
|
|
11406
|
+
});
|