@askexenow/exe-os 0.8.83 → 0.8.85
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -272,6 +272,7 @@ __export(employees_exports, {
|
|
|
272
272
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
273
273
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
274
274
|
addEmployee: () => addEmployee,
|
|
275
|
+
baseAgentName: () => baseAgentName,
|
|
275
276
|
canCoordinate: () => canCoordinate,
|
|
276
277
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
277
278
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -368,6 +369,14 @@ function hasRole(agentName, role) {
|
|
|
368
369
|
const emp = getEmployee(employees, agentName);
|
|
369
370
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
370
371
|
}
|
|
372
|
+
function baseAgentName(name, employees) {
|
|
373
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
374
|
+
if (!match) return name;
|
|
375
|
+
const base = match[1];
|
|
376
|
+
const roster = employees ?? loadEmployeesSync();
|
|
377
|
+
if (getEmployee(roster, base)) return base;
|
|
378
|
+
return name;
|
|
379
|
+
}
|
|
371
380
|
function isMultiInstance(agentName, employees) {
|
|
372
381
|
const roster = employees ?? loadEmployeesSync();
|
|
373
382
|
const emp = getEmployee(roster, agentName);
|
|
@@ -852,388 +861,941 @@ var init_db_retry = __esm({
|
|
|
852
861
|
}
|
|
853
862
|
});
|
|
854
863
|
|
|
855
|
-
// src/lib/
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
import { createClient } from "@libsql/client";
|
|
868
|
-
async function initDatabase(config2) {
|
|
869
|
-
if (_client) {
|
|
870
|
-
_client.close();
|
|
871
|
-
_client = null;
|
|
872
|
-
_resilientClient = null;
|
|
864
|
+
// src/lib/exe-daemon-client.ts
|
|
865
|
+
import net from "net";
|
|
866
|
+
import { spawn } from "child_process";
|
|
867
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
868
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
869
|
+
import path6 from "path";
|
|
870
|
+
import { fileURLToPath } from "url";
|
|
871
|
+
function handleData(chunk) {
|
|
872
|
+
_buffer += chunk.toString();
|
|
873
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
874
|
+
_buffer = "";
|
|
875
|
+
return;
|
|
873
876
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
877
|
+
let newlineIdx;
|
|
878
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
879
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
880
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
881
|
+
if (!line) continue;
|
|
882
|
+
try {
|
|
883
|
+
const response = JSON.parse(line);
|
|
884
|
+
const id = response.id;
|
|
885
|
+
if (!id) continue;
|
|
886
|
+
const entry = _pending.get(id);
|
|
887
|
+
if (entry) {
|
|
888
|
+
clearTimeout(entry.timer);
|
|
889
|
+
_pending.delete(id);
|
|
890
|
+
entry.resolve(response);
|
|
891
|
+
}
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
879
894
|
}
|
|
880
|
-
_client = createClient(opts);
|
|
881
|
-
_resilientClient = wrapWithRetry(_client);
|
|
882
895
|
}
|
|
883
|
-
function
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
896
|
+
function cleanupStaleFiles() {
|
|
897
|
+
if (existsSync5(PID_PATH)) {
|
|
898
|
+
try {
|
|
899
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
900
|
+
if (pid > 0) {
|
|
901
|
+
try {
|
|
902
|
+
process.kill(pid, 0);
|
|
903
|
+
return;
|
|
904
|
+
} catch {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
try {
|
|
910
|
+
unlinkSync2(PID_PATH);
|
|
911
|
+
} catch {
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
unlinkSync2(SOCKET_PATH);
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
889
917
|
}
|
|
890
|
-
return _resilientClient;
|
|
891
918
|
}
|
|
892
|
-
function
|
|
893
|
-
|
|
894
|
-
|
|
919
|
+
function findPackageRoot() {
|
|
920
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
921
|
+
const { root } = path6.parse(dir);
|
|
922
|
+
while (dir !== root) {
|
|
923
|
+
if (existsSync5(path6.join(dir, "package.json"))) return dir;
|
|
924
|
+
dir = path6.dirname(dir);
|
|
895
925
|
}
|
|
896
|
-
return
|
|
926
|
+
return null;
|
|
897
927
|
}
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
905
|
-
} catch {
|
|
928
|
+
function spawnDaemon() {
|
|
929
|
+
const pkgRoot = findPackageRoot();
|
|
930
|
+
if (!pkgRoot) {
|
|
931
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
932
|
+
return;
|
|
906
933
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
session_id TEXT NOT NULL,
|
|
913
|
-
timestamp TEXT NOT NULL,
|
|
914
|
-
tool_name TEXT NOT NULL,
|
|
915
|
-
project_name TEXT NOT NULL,
|
|
916
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
917
|
-
raw_text TEXT NOT NULL,
|
|
918
|
-
vector F32_BLOB(1024),
|
|
919
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
920
|
-
);
|
|
921
|
-
|
|
922
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
923
|
-
ON memories(agent_id);
|
|
924
|
-
|
|
925
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
926
|
-
ON memories(timestamp);
|
|
927
|
-
|
|
928
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
929
|
-
ON memories(session_id);
|
|
930
|
-
|
|
931
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
932
|
-
ON memories(project_name);
|
|
933
|
-
|
|
934
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
935
|
-
ON memories(tool_name);
|
|
936
|
-
|
|
937
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
938
|
-
ON memories(version);
|
|
939
|
-
|
|
940
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
941
|
-
ON memories(agent_id, project_name);
|
|
942
|
-
`);
|
|
943
|
-
await client.executeMultiple(`
|
|
944
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
945
|
-
raw_text,
|
|
946
|
-
content='memories',
|
|
947
|
-
content_rowid='rowid'
|
|
948
|
-
);
|
|
949
|
-
|
|
950
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
951
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
952
|
-
END;
|
|
953
|
-
|
|
954
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
955
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
956
|
-
END;
|
|
957
|
-
|
|
958
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
959
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
960
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
961
|
-
END;
|
|
962
|
-
`);
|
|
963
|
-
await client.executeMultiple(`
|
|
964
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
965
|
-
key TEXT PRIMARY KEY,
|
|
966
|
-
value TEXT NOT NULL
|
|
967
|
-
);
|
|
968
|
-
`);
|
|
969
|
-
await client.executeMultiple(`
|
|
970
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
971
|
-
id TEXT PRIMARY KEY,
|
|
972
|
-
title TEXT NOT NULL,
|
|
973
|
-
assigned_to TEXT NOT NULL,
|
|
974
|
-
assigned_by TEXT NOT NULL,
|
|
975
|
-
project_name TEXT NOT NULL,
|
|
976
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
977
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
978
|
-
task_file TEXT,
|
|
979
|
-
created_at TEXT NOT NULL,
|
|
980
|
-
updated_at TEXT NOT NULL
|
|
981
|
-
);
|
|
982
|
-
|
|
983
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
984
|
-
ON tasks(assigned_to, status);
|
|
985
|
-
`);
|
|
986
|
-
await client.executeMultiple(`
|
|
987
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
988
|
-
id TEXT PRIMARY KEY,
|
|
989
|
-
agent_id TEXT NOT NULL,
|
|
990
|
-
project_name TEXT,
|
|
991
|
-
domain TEXT,
|
|
992
|
-
content TEXT NOT NULL,
|
|
993
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
994
|
-
created_at TEXT NOT NULL,
|
|
995
|
-
updated_at TEXT NOT NULL
|
|
996
|
-
);
|
|
997
|
-
|
|
998
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
999
|
-
ON behaviors(agent_id, active);
|
|
1000
|
-
`);
|
|
1001
|
-
try {
|
|
1002
|
-
const coordinatorName = getCoordinatorName();
|
|
1003
|
-
const existing = await client.execute({
|
|
1004
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1005
|
-
args: [coordinatorName]
|
|
1006
|
-
});
|
|
1007
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1008
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
1009
|
-
for (const [domain, content] of [
|
|
1010
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1011
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1012
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1013
|
-
]) {
|
|
1014
|
-
await client.execute({
|
|
1015
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1016
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1017
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
} catch {
|
|
934
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
935
|
+
if (!existsSync5(daemonPath)) {
|
|
936
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
937
|
+
`);
|
|
938
|
+
return;
|
|
1022
939
|
}
|
|
940
|
+
const resolvedPath = daemonPath;
|
|
941
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
942
|
+
`);
|
|
943
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
944
|
+
let stderrFd = "ignore";
|
|
1023
945
|
try {
|
|
1024
|
-
|
|
1025
|
-
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1026
|
-
args: []
|
|
1027
|
-
});
|
|
946
|
+
stderrFd = openSync(logPath, "a");
|
|
1028
947
|
} catch {
|
|
1029
948
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
949
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
950
|
+
detached: true,
|
|
951
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
952
|
+
env: {
|
|
953
|
+
...process.env,
|
|
954
|
+
TMUX: void 0,
|
|
955
|
+
// Daemon is global — must not inherit session scope
|
|
956
|
+
TMUX_PANE: void 0,
|
|
957
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
958
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
959
|
+
EXE_DAEMON_PID: PID_PATH
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
child.unref();
|
|
963
|
+
if (typeof stderrFd === "number") {
|
|
964
|
+
try {
|
|
965
|
+
closeSync(stderrFd);
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
1036
968
|
}
|
|
969
|
+
}
|
|
970
|
+
function acquireSpawnLock() {
|
|
1037
971
|
try {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
});
|
|
972
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
973
|
+
closeSync(fd);
|
|
974
|
+
return true;
|
|
1042
975
|
} catch {
|
|
976
|
+
try {
|
|
977
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
978
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
979
|
+
try {
|
|
980
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
985
|
+
closeSync(fd);
|
|
986
|
+
return true;
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
1043
993
|
}
|
|
994
|
+
}
|
|
995
|
+
function releaseSpawnLock() {
|
|
1044
996
|
try {
|
|
1045
|
-
|
|
1046
|
-
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1047
|
-
ON tasks(parent_task_id)
|
|
1048
|
-
WHERE parent_task_id IS NOT NULL`,
|
|
1049
|
-
args: []
|
|
1050
|
-
});
|
|
997
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1051
998
|
} catch {
|
|
1052
999
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1000
|
+
}
|
|
1001
|
+
function connectToSocket() {
|
|
1002
|
+
return new Promise((resolve) => {
|
|
1003
|
+
if (_socket && _connected) {
|
|
1004
|
+
resolve(true);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
1008
|
+
const connectTimeout = setTimeout(() => {
|
|
1009
|
+
socket.destroy();
|
|
1010
|
+
resolve(false);
|
|
1011
|
+
}, 2e3);
|
|
1012
|
+
socket.on("connect", () => {
|
|
1013
|
+
clearTimeout(connectTimeout);
|
|
1014
|
+
_socket = socket;
|
|
1015
|
+
_connected = true;
|
|
1016
|
+
_buffer = "";
|
|
1017
|
+
socket.on("data", handleData);
|
|
1018
|
+
socket.on("close", () => {
|
|
1019
|
+
_connected = false;
|
|
1020
|
+
_socket = null;
|
|
1021
|
+
for (const [id, entry] of _pending) {
|
|
1022
|
+
clearTimeout(entry.timer);
|
|
1023
|
+
_pending.delete(id);
|
|
1024
|
+
entry.resolve({ error: "Connection closed" });
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
socket.on("error", () => {
|
|
1028
|
+
_connected = false;
|
|
1029
|
+
_socket = null;
|
|
1030
|
+
});
|
|
1031
|
+
resolve(true);
|
|
1057
1032
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
await client.execute({
|
|
1062
|
-
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1063
|
-
args: []
|
|
1033
|
+
socket.on("error", () => {
|
|
1034
|
+
clearTimeout(connectTimeout);
|
|
1035
|
+
resolve(false);
|
|
1064
1036
|
});
|
|
1065
|
-
}
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
async function connectEmbedDaemon() {
|
|
1040
|
+
if (_socket && _connected) return true;
|
|
1041
|
+
if (await connectToSocket()) return true;
|
|
1042
|
+
if (acquireSpawnLock()) {
|
|
1043
|
+
try {
|
|
1044
|
+
cleanupStaleFiles();
|
|
1045
|
+
spawnDaemon();
|
|
1046
|
+
} finally {
|
|
1047
|
+
releaseSpawnLock();
|
|
1048
|
+
}
|
|
1066
1049
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1050
|
+
const start = Date.now();
|
|
1051
|
+
let delay2 = 100;
|
|
1052
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1053
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1054
|
+
if (await connectToSocket()) return true;
|
|
1055
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1073
1056
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
function sendRequest(texts, priority) {
|
|
1060
|
+
return sendDaemonRequest({ texts, priority });
|
|
1061
|
+
}
|
|
1062
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
1063
|
+
return new Promise((resolve) => {
|
|
1064
|
+
if (!_socket || !_connected) {
|
|
1065
|
+
resolve({ error: "Not connected" });
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const id = randomUUID2();
|
|
1069
|
+
const timer = setTimeout(() => {
|
|
1070
|
+
_pending.delete(id);
|
|
1071
|
+
resolve({ error: "Request timeout" });
|
|
1072
|
+
}, timeoutMs);
|
|
1073
|
+
_pending.set(id, { resolve, timer });
|
|
1074
|
+
try {
|
|
1075
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1076
|
+
} catch {
|
|
1077
|
+
clearTimeout(timer);
|
|
1078
|
+
_pending.delete(id);
|
|
1079
|
+
resolve({ error: "Write failed" });
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
async function pingDaemon() {
|
|
1084
|
+
if (!_socket || !_connected) return null;
|
|
1085
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
1086
|
+
if (response.health) {
|
|
1087
|
+
return response.health;
|
|
1080
1088
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
function killAndRespawnDaemon() {
|
|
1092
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1093
|
+
if (existsSync5(PID_PATH)) {
|
|
1094
|
+
try {
|
|
1095
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1096
|
+
if (pid > 0) {
|
|
1097
|
+
try {
|
|
1098
|
+
process.kill(pid, "SIGKILL");
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1087
1104
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
args: []
|
|
1092
|
-
});
|
|
1093
|
-
} catch {
|
|
1105
|
+
if (_socket) {
|
|
1106
|
+
_socket.destroy();
|
|
1107
|
+
_socket = null;
|
|
1094
1108
|
}
|
|
1109
|
+
_connected = false;
|
|
1110
|
+
_buffer = "";
|
|
1095
1111
|
try {
|
|
1096
|
-
|
|
1097
|
-
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1098
|
-
args: []
|
|
1099
|
-
});
|
|
1112
|
+
unlinkSync2(PID_PATH);
|
|
1100
1113
|
} catch {
|
|
1101
1114
|
}
|
|
1102
1115
|
try {
|
|
1103
|
-
|
|
1104
|
-
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1105
|
-
args: []
|
|
1106
|
-
});
|
|
1116
|
+
unlinkSync2(SOCKET_PATH);
|
|
1107
1117
|
} catch {
|
|
1108
1118
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1119
|
+
spawnDaemon();
|
|
1120
|
+
}
|
|
1121
|
+
async function embedViaClient(text, priority = "high") {
|
|
1122
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1123
|
+
_requestCount++;
|
|
1124
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1125
|
+
const health = await pingDaemon();
|
|
1126
|
+
if (!health) {
|
|
1127
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1128
|
+
`);
|
|
1129
|
+
killAndRespawnDaemon();
|
|
1130
|
+
const start = Date.now();
|
|
1131
|
+
let delay2 = 200;
|
|
1132
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1133
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1134
|
+
if (await connectToSocket()) break;
|
|
1135
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1136
|
+
}
|
|
1137
|
+
if (!_connected) return null;
|
|
1138
|
+
}
|
|
1115
1139
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1140
|
+
const result = await sendRequest([text], priority);
|
|
1141
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
1142
|
+
if (result.error) {
|
|
1143
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
1144
|
+
`);
|
|
1145
|
+
killAndRespawnDaemon();
|
|
1146
|
+
const start = Date.now();
|
|
1147
|
+
let delay2 = 200;
|
|
1148
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1149
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1150
|
+
if (await connectToSocket()) break;
|
|
1151
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1152
|
+
}
|
|
1153
|
+
if (!_connected) return null;
|
|
1154
|
+
const retry = await sendRequest([text], priority);
|
|
1155
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
1156
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
1157
|
+
`);
|
|
1122
1158
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
function disconnectClient() {
|
|
1162
|
+
if (_socket) {
|
|
1163
|
+
_socket.destroy();
|
|
1164
|
+
_socket = null;
|
|
1129
1165
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1166
|
+
_connected = false;
|
|
1167
|
+
_buffer = "";
|
|
1168
|
+
for (const [id, entry] of _pending) {
|
|
1169
|
+
clearTimeout(entry.timer);
|
|
1170
|
+
_pending.delete(id);
|
|
1171
|
+
entry.resolve({ error: "Client disconnected" });
|
|
1136
1172
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1173
|
+
}
|
|
1174
|
+
function isClientConnected() {
|
|
1175
|
+
return _connected;
|
|
1176
|
+
}
|
|
1177
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
1178
|
+
var init_exe_daemon_client = __esm({
|
|
1179
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
1180
|
+
"use strict";
|
|
1181
|
+
init_config();
|
|
1182
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1183
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1184
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1185
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1186
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
1187
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
1188
|
+
_socket = null;
|
|
1189
|
+
_connected = false;
|
|
1190
|
+
_buffer = "";
|
|
1191
|
+
_requestCount = 0;
|
|
1192
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
1193
|
+
_pending = /* @__PURE__ */ new Map();
|
|
1194
|
+
MAX_BUFFER = 1e7;
|
|
1143
1195
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
// src/lib/daemon-protocol.ts
|
|
1199
|
+
function serializeValue(v) {
|
|
1200
|
+
if (v === null || v === void 0) return null;
|
|
1201
|
+
if (typeof v === "bigint") return Number(v);
|
|
1202
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
1203
|
+
if (v instanceof Uint8Array) {
|
|
1204
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
1205
|
+
}
|
|
1206
|
+
if (ArrayBuffer.isView(v)) {
|
|
1207
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
1208
|
+
}
|
|
1209
|
+
if (v instanceof ArrayBuffer) {
|
|
1210
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
1213
|
+
return String(v);
|
|
1214
|
+
}
|
|
1215
|
+
function deserializeValue(v) {
|
|
1216
|
+
if (v === null) return null;
|
|
1217
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
1218
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
1219
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
1220
|
+
}
|
|
1221
|
+
return v;
|
|
1222
|
+
}
|
|
1223
|
+
function deserializeResultSet(srs) {
|
|
1224
|
+
const rows = srs.rows.map((obj) => {
|
|
1225
|
+
const values = srs.columns.map(
|
|
1226
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
1150
1227
|
);
|
|
1228
|
+
const row = values;
|
|
1229
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
1230
|
+
const col = srs.columns[i];
|
|
1231
|
+
if (col !== void 0) {
|
|
1232
|
+
row[col] = values[i] ?? null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
Object.defineProperty(row, "length", {
|
|
1236
|
+
value: values.length,
|
|
1237
|
+
enumerable: false
|
|
1238
|
+
});
|
|
1239
|
+
return row;
|
|
1240
|
+
});
|
|
1241
|
+
return {
|
|
1242
|
+
columns: srs.columns,
|
|
1243
|
+
columnTypes: srs.columnTypes ?? [],
|
|
1244
|
+
rows,
|
|
1245
|
+
rowsAffected: srs.rowsAffected,
|
|
1246
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
1247
|
+
toJSON: () => ({
|
|
1248
|
+
columns: srs.columns,
|
|
1249
|
+
columnTypes: srs.columnTypes ?? [],
|
|
1250
|
+
rows: srs.rows,
|
|
1251
|
+
rowsAffected: srs.rowsAffected,
|
|
1252
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
1253
|
+
})
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
var init_daemon_protocol = __esm({
|
|
1257
|
+
"src/lib/daemon-protocol.ts"() {
|
|
1258
|
+
"use strict";
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1151
1261
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1262
|
+
// src/lib/db-daemon-client.ts
|
|
1263
|
+
var db_daemon_client_exports = {};
|
|
1264
|
+
__export(db_daemon_client_exports, {
|
|
1265
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
1266
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
1267
|
+
});
|
|
1268
|
+
function normalizeStatement(stmt) {
|
|
1269
|
+
if (typeof stmt === "string") {
|
|
1270
|
+
return { sql: stmt, args: [] };
|
|
1271
|
+
}
|
|
1272
|
+
const sql = stmt.sql;
|
|
1273
|
+
let args = [];
|
|
1274
|
+
if (Array.isArray(stmt.args)) {
|
|
1275
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
1276
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
1277
|
+
const named = {};
|
|
1278
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
1279
|
+
named[key] = serializeValue(val);
|
|
1280
|
+
}
|
|
1281
|
+
return { sql, args: named };
|
|
1282
|
+
}
|
|
1283
|
+
return { sql, args };
|
|
1284
|
+
}
|
|
1285
|
+
function createDaemonDbClient(fallbackClient) {
|
|
1286
|
+
let _useDaemon = false;
|
|
1287
|
+
const client = {
|
|
1288
|
+
async execute(stmt) {
|
|
1289
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1290
|
+
return fallbackClient.execute(stmt);
|
|
1291
|
+
}
|
|
1292
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
1293
|
+
const response = await sendDaemonRequest({
|
|
1294
|
+
type: "db-execute",
|
|
1295
|
+
sql,
|
|
1296
|
+
args
|
|
1297
|
+
});
|
|
1298
|
+
if (response.error) {
|
|
1299
|
+
const errMsg = String(response.error);
|
|
1300
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1301
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1302
|
+
`);
|
|
1303
|
+
return fallbackClient.execute(stmt);
|
|
1304
|
+
}
|
|
1305
|
+
throw new Error(errMsg);
|
|
1306
|
+
}
|
|
1307
|
+
if (response.db) {
|
|
1308
|
+
return deserializeResultSet(response.db);
|
|
1309
|
+
}
|
|
1310
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1311
|
+
return fallbackClient.execute(stmt);
|
|
1312
|
+
},
|
|
1313
|
+
async batch(stmts, mode) {
|
|
1314
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1315
|
+
return fallbackClient.batch(stmts, mode);
|
|
1316
|
+
}
|
|
1317
|
+
const statements = stmts.map(normalizeStatement);
|
|
1318
|
+
const response = await sendDaemonRequest({
|
|
1319
|
+
type: "db-batch",
|
|
1320
|
+
statements,
|
|
1321
|
+
mode: mode ?? "deferred"
|
|
1322
|
+
});
|
|
1323
|
+
if (response.error) {
|
|
1324
|
+
const errMsg = String(response.error);
|
|
1325
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1326
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1327
|
+
`);
|
|
1328
|
+
return fallbackClient.batch(stmts, mode);
|
|
1329
|
+
}
|
|
1330
|
+
throw new Error(errMsg);
|
|
1331
|
+
}
|
|
1332
|
+
const batchResults = response["db-batch"];
|
|
1333
|
+
if (batchResults) {
|
|
1334
|
+
return batchResults.map(deserializeResultSet);
|
|
1335
|
+
}
|
|
1336
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1337
|
+
return fallbackClient.batch(stmts, mode);
|
|
1338
|
+
},
|
|
1339
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1340
|
+
async transaction(mode) {
|
|
1341
|
+
return fallbackClient.transaction(mode);
|
|
1342
|
+
},
|
|
1343
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1344
|
+
async executeMultiple(sql) {
|
|
1345
|
+
return fallbackClient.executeMultiple(sql);
|
|
1346
|
+
},
|
|
1347
|
+
// migrate — delegate to fallback
|
|
1348
|
+
async migrate(stmts) {
|
|
1349
|
+
return fallbackClient.migrate(stmts);
|
|
1350
|
+
},
|
|
1351
|
+
// Sync mode — delegate to fallback
|
|
1352
|
+
sync() {
|
|
1353
|
+
return fallbackClient.sync();
|
|
1354
|
+
},
|
|
1355
|
+
close() {
|
|
1356
|
+
_useDaemon = false;
|
|
1357
|
+
},
|
|
1358
|
+
get closed() {
|
|
1359
|
+
return fallbackClient.closed;
|
|
1360
|
+
},
|
|
1361
|
+
get protocol() {
|
|
1362
|
+
return fallbackClient.protocol;
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
return {
|
|
1366
|
+
...client,
|
|
1367
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1368
|
+
_enableDaemon() {
|
|
1369
|
+
_useDaemon = true;
|
|
1370
|
+
},
|
|
1371
|
+
/** Check if daemon routing is active */
|
|
1372
|
+
_isDaemonActive() {
|
|
1373
|
+
return _useDaemon && isClientConnected();
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1378
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1379
|
+
const connected = await connectEmbedDaemon();
|
|
1380
|
+
if (!connected) {
|
|
1381
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1385
|
+
client._enableDaemon();
|
|
1386
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1387
|
+
return client;
|
|
1388
|
+
}
|
|
1389
|
+
var init_db_daemon_client = __esm({
|
|
1390
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1391
|
+
"use strict";
|
|
1392
|
+
init_exe_daemon_client();
|
|
1393
|
+
init_daemon_protocol();
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1154
1396
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1397
|
+
// src/lib/database.ts
|
|
1398
|
+
var database_exports = {};
|
|
1399
|
+
__export(database_exports, {
|
|
1400
|
+
disposeDatabase: () => disposeDatabase,
|
|
1401
|
+
disposeTurso: () => disposeTurso,
|
|
1402
|
+
ensureSchema: () => ensureSchema,
|
|
1403
|
+
getClient: () => getClient,
|
|
1404
|
+
getRawClient: () => getRawClient,
|
|
1405
|
+
initDaemonClient: () => initDaemonClient,
|
|
1406
|
+
initDatabase: () => initDatabase,
|
|
1407
|
+
initTurso: () => initTurso,
|
|
1408
|
+
isInitialized: () => isInitialized
|
|
1409
|
+
});
|
|
1410
|
+
import { createClient } from "@libsql/client";
|
|
1411
|
+
async function initDatabase(config2) {
|
|
1412
|
+
if (_client) {
|
|
1413
|
+
_client.close();
|
|
1414
|
+
_client = null;
|
|
1415
|
+
_resilientClient = null;
|
|
1416
|
+
}
|
|
1417
|
+
const opts = {
|
|
1418
|
+
url: `file:${config2.dbPath}`
|
|
1419
|
+
};
|
|
1420
|
+
if (config2.encryptionKey) {
|
|
1421
|
+
opts.encryptionKey = config2.encryptionKey;
|
|
1422
|
+
}
|
|
1423
|
+
_client = createClient(opts);
|
|
1424
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1425
|
+
}
|
|
1426
|
+
function isInitialized() {
|
|
1427
|
+
return _client !== null;
|
|
1428
|
+
}
|
|
1429
|
+
function getClient() {
|
|
1430
|
+
if (!_resilientClient) {
|
|
1431
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1432
|
+
}
|
|
1433
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1434
|
+
return _resilientClient;
|
|
1435
|
+
}
|
|
1436
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1437
|
+
return _daemonClient;
|
|
1438
|
+
}
|
|
1439
|
+
return _resilientClient;
|
|
1440
|
+
}
|
|
1441
|
+
async function initDaemonClient() {
|
|
1442
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1443
|
+
if (!_resilientClient) return;
|
|
1444
|
+
try {
|
|
1445
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1446
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1447
|
+
} catch (err) {
|
|
1448
|
+
process.stderr.write(
|
|
1449
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1450
|
+
`
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function getRawClient() {
|
|
1455
|
+
if (!_client) {
|
|
1456
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1457
|
+
}
|
|
1458
|
+
return _client;
|
|
1459
|
+
}
|
|
1460
|
+
async function ensureSchema() {
|
|
1461
|
+
const client = getRawClient();
|
|
1462
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1463
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1464
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1465
|
+
try {
|
|
1466
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1467
|
+
} catch {
|
|
1468
|
+
}
|
|
1158
1469
|
await client.executeMultiple(`
|
|
1159
|
-
CREATE TABLE IF NOT EXISTS
|
|
1470
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1160
1471
|
id TEXT PRIMARY KEY,
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1472
|
+
agent_id TEXT NOT NULL,
|
|
1473
|
+
agent_role TEXT NOT NULL,
|
|
1474
|
+
session_id TEXT NOT NULL,
|
|
1475
|
+
timestamp TEXT NOT NULL,
|
|
1476
|
+
tool_name TEXT NOT NULL,
|
|
1477
|
+
project_name TEXT NOT NULL,
|
|
1478
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1479
|
+
raw_text TEXT NOT NULL,
|
|
1480
|
+
vector F32_BLOB(1024),
|
|
1481
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
1165
1482
|
);
|
|
1483
|
+
|
|
1484
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1485
|
+
ON memories(agent_id);
|
|
1486
|
+
|
|
1487
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1488
|
+
ON memories(timestamp);
|
|
1489
|
+
|
|
1490
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1491
|
+
ON memories(session_id);
|
|
1492
|
+
|
|
1493
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1494
|
+
ON memories(project_name);
|
|
1495
|
+
|
|
1496
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1497
|
+
ON memories(tool_name);
|
|
1498
|
+
|
|
1499
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1500
|
+
ON memories(version);
|
|
1501
|
+
|
|
1502
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1503
|
+
ON memories(agent_id, project_name);
|
|
1166
1504
|
`);
|
|
1167
1505
|
await client.executeMultiple(`
|
|
1168
|
-
CREATE TABLE IF NOT EXISTS
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
event TEXT NOT NULL,
|
|
1173
|
-
project TEXT NOT NULL,
|
|
1174
|
-
summary TEXT NOT NULL,
|
|
1175
|
-
task_file TEXT,
|
|
1176
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
1177
|
-
created_at TEXT NOT NULL
|
|
1506
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1507
|
+
raw_text,
|
|
1508
|
+
content='memories',
|
|
1509
|
+
content_rowid='rowid'
|
|
1178
1510
|
);
|
|
1179
1511
|
|
|
1180
|
-
CREATE
|
|
1181
|
-
|
|
1512
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1513
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1514
|
+
END;
|
|
1182
1515
|
|
|
1183
|
-
CREATE
|
|
1184
|
-
|
|
1516
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1517
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1518
|
+
END;
|
|
1185
1519
|
|
|
1186
|
-
CREATE
|
|
1187
|
-
|
|
1520
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1521
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1522
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1523
|
+
END;
|
|
1188
1524
|
`);
|
|
1189
1525
|
await client.executeMultiple(`
|
|
1190
|
-
CREATE TABLE IF NOT EXISTS
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
description TEXT NOT NULL,
|
|
1194
|
-
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1195
|
-
prompt TEXT,
|
|
1196
|
-
assigned_to TEXT,
|
|
1197
|
-
project_name TEXT,
|
|
1198
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
1199
|
-
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1200
|
-
created_at TEXT NOT NULL
|
|
1526
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1527
|
+
key TEXT PRIMARY KEY,
|
|
1528
|
+
value TEXT NOT NULL
|
|
1201
1529
|
);
|
|
1202
1530
|
`);
|
|
1203
1531
|
await client.executeMultiple(`
|
|
1204
|
-
CREATE TABLE IF NOT EXISTS
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1532
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1533
|
+
id TEXT PRIMARY KEY,
|
|
1534
|
+
title TEXT NOT NULL,
|
|
1535
|
+
assigned_to TEXT NOT NULL,
|
|
1536
|
+
assigned_by TEXT NOT NULL,
|
|
1537
|
+
project_name TEXT NOT NULL,
|
|
1538
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1539
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1540
|
+
task_file TEXT,
|
|
1541
|
+
created_at TEXT NOT NULL,
|
|
1542
|
+
updated_at TEXT NOT NULL
|
|
1212
1543
|
);
|
|
1544
|
+
|
|
1545
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1546
|
+
ON tasks(assigned_to, status);
|
|
1213
1547
|
`);
|
|
1214
1548
|
await client.executeMultiple(`
|
|
1215
|
-
CREATE TABLE IF NOT EXISTS
|
|
1216
|
-
id
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
status TEXT DEFAULT 'pending',
|
|
1225
|
-
server_seq INTEGER,
|
|
1226
|
-
retry_count INTEGER DEFAULT 0,
|
|
1227
|
-
created_at TEXT NOT NULL,
|
|
1228
|
-
delivered_at TEXT,
|
|
1229
|
-
processed_at TEXT,
|
|
1230
|
-
failed_at TEXT,
|
|
1231
|
-
failure_reason TEXT
|
|
1549
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1550
|
+
id TEXT PRIMARY KEY,
|
|
1551
|
+
agent_id TEXT NOT NULL,
|
|
1552
|
+
project_name TEXT,
|
|
1553
|
+
domain TEXT,
|
|
1554
|
+
content TEXT NOT NULL,
|
|
1555
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1556
|
+
created_at TEXT NOT NULL,
|
|
1557
|
+
updated_at TEXT NOT NULL
|
|
1232
1558
|
);
|
|
1233
1559
|
|
|
1234
|
-
CREATE INDEX IF NOT EXISTS
|
|
1235
|
-
ON
|
|
1236
|
-
|
|
1560
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1561
|
+
ON behaviors(agent_id, active);
|
|
1562
|
+
`);
|
|
1563
|
+
try {
|
|
1564
|
+
const coordinatorName = getCoordinatorName();
|
|
1565
|
+
const existing = await client.execute({
|
|
1566
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1567
|
+
args: [coordinatorName]
|
|
1568
|
+
});
|
|
1569
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1570
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1571
|
+
for (const [domain, content] of [
|
|
1572
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1573
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1574
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1575
|
+
]) {
|
|
1576
|
+
await client.execute({
|
|
1577
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1578
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1579
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
try {
|
|
1586
|
+
await client.execute({
|
|
1587
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1588
|
+
args: []
|
|
1589
|
+
});
|
|
1590
|
+
} catch {
|
|
1591
|
+
}
|
|
1592
|
+
try {
|
|
1593
|
+
await client.execute({
|
|
1594
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1595
|
+
args: []
|
|
1596
|
+
});
|
|
1597
|
+
} catch {
|
|
1598
|
+
}
|
|
1599
|
+
try {
|
|
1600
|
+
await client.execute({
|
|
1601
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1602
|
+
args: []
|
|
1603
|
+
});
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
await client.execute({
|
|
1608
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1609
|
+
ON tasks(parent_task_id)
|
|
1610
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1611
|
+
args: []
|
|
1612
|
+
});
|
|
1613
|
+
} catch {
|
|
1614
|
+
}
|
|
1615
|
+
try {
|
|
1616
|
+
await client.execute({
|
|
1617
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1618
|
+
args: []
|
|
1619
|
+
});
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
try {
|
|
1623
|
+
await client.execute({
|
|
1624
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1625
|
+
args: []
|
|
1626
|
+
});
|
|
1627
|
+
} catch {
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
await client.execute({
|
|
1631
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1632
|
+
args: []
|
|
1633
|
+
});
|
|
1634
|
+
} catch {
|
|
1635
|
+
}
|
|
1636
|
+
try {
|
|
1637
|
+
await client.execute({
|
|
1638
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1639
|
+
args: []
|
|
1640
|
+
});
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
await client.execute({
|
|
1645
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1646
|
+
args: []
|
|
1647
|
+
});
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
await client.execute({
|
|
1652
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1653
|
+
args: []
|
|
1654
|
+
});
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
try {
|
|
1658
|
+
await client.execute({
|
|
1659
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1660
|
+
args: []
|
|
1661
|
+
});
|
|
1662
|
+
} catch {
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
await client.execute({
|
|
1666
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1667
|
+
args: []
|
|
1668
|
+
});
|
|
1669
|
+
} catch {
|
|
1670
|
+
}
|
|
1671
|
+
try {
|
|
1672
|
+
await client.execute({
|
|
1673
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1674
|
+
args: []
|
|
1675
|
+
});
|
|
1676
|
+
} catch {
|
|
1677
|
+
}
|
|
1678
|
+
try {
|
|
1679
|
+
await client.execute({
|
|
1680
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1681
|
+
args: []
|
|
1682
|
+
});
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
try {
|
|
1686
|
+
await client.execute({
|
|
1687
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1688
|
+
args: []
|
|
1689
|
+
});
|
|
1690
|
+
} catch {
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
await client.execute({
|
|
1694
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1695
|
+
args: []
|
|
1696
|
+
});
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1699
|
+
try {
|
|
1700
|
+
await client.execute({
|
|
1701
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1702
|
+
args: []
|
|
1703
|
+
});
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1706
|
+
await client.executeMultiple(`
|
|
1707
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1708
|
+
id TEXT PRIMARY KEY,
|
|
1709
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1710
|
+
source_memory_id TEXT NOT NULL,
|
|
1711
|
+
created_at TEXT NOT NULL
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1714
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1715
|
+
ON consolidations(source_memory_id);
|
|
1716
|
+
|
|
1717
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1718
|
+
ON consolidations(consolidated_memory_id);
|
|
1719
|
+
`);
|
|
1720
|
+
await client.executeMultiple(`
|
|
1721
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1722
|
+
id TEXT PRIMARY KEY,
|
|
1723
|
+
text TEXT NOT NULL,
|
|
1724
|
+
created_at TEXT NOT NULL,
|
|
1725
|
+
due_date TEXT,
|
|
1726
|
+
completed_at TEXT
|
|
1727
|
+
);
|
|
1728
|
+
`);
|
|
1729
|
+
await client.executeMultiple(`
|
|
1730
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1731
|
+
id TEXT PRIMARY KEY,
|
|
1732
|
+
agent_id TEXT NOT NULL,
|
|
1733
|
+
agent_role TEXT NOT NULL,
|
|
1734
|
+
event TEXT NOT NULL,
|
|
1735
|
+
project TEXT NOT NULL,
|
|
1736
|
+
summary TEXT NOT NULL,
|
|
1737
|
+
task_file TEXT,
|
|
1738
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1739
|
+
created_at TEXT NOT NULL
|
|
1740
|
+
);
|
|
1741
|
+
|
|
1742
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1743
|
+
ON notifications(read);
|
|
1744
|
+
|
|
1745
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1746
|
+
ON notifications(agent_id);
|
|
1747
|
+
|
|
1748
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1749
|
+
ON notifications(task_file);
|
|
1750
|
+
`);
|
|
1751
|
+
await client.executeMultiple(`
|
|
1752
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1753
|
+
id TEXT PRIMARY KEY,
|
|
1754
|
+
cron TEXT NOT NULL,
|
|
1755
|
+
description TEXT NOT NULL,
|
|
1756
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1757
|
+
prompt TEXT,
|
|
1758
|
+
assigned_to TEXT,
|
|
1759
|
+
project_name TEXT,
|
|
1760
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1761
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1762
|
+
created_at TEXT NOT NULL
|
|
1763
|
+
);
|
|
1764
|
+
`);
|
|
1765
|
+
await client.executeMultiple(`
|
|
1766
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1767
|
+
device_id TEXT PRIMARY KEY,
|
|
1768
|
+
friendly_name TEXT NOT NULL,
|
|
1769
|
+
hostname TEXT NOT NULL,
|
|
1770
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1771
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1772
|
+
connected INTEGER DEFAULT 0,
|
|
1773
|
+
last_seen TEXT NOT NULL
|
|
1774
|
+
);
|
|
1775
|
+
`);
|
|
1776
|
+
await client.executeMultiple(`
|
|
1777
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1778
|
+
id TEXT PRIMARY KEY,
|
|
1779
|
+
from_agent TEXT NOT NULL,
|
|
1780
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
1781
|
+
target_agent TEXT NOT NULL,
|
|
1782
|
+
target_project TEXT,
|
|
1783
|
+
target_device TEXT NOT NULL DEFAULT 'local',
|
|
1784
|
+
content TEXT NOT NULL,
|
|
1785
|
+
priority TEXT DEFAULT 'normal',
|
|
1786
|
+
status TEXT DEFAULT 'pending',
|
|
1787
|
+
server_seq INTEGER,
|
|
1788
|
+
retry_count INTEGER DEFAULT 0,
|
|
1789
|
+
created_at TEXT NOT NULL,
|
|
1790
|
+
delivered_at TEXT,
|
|
1791
|
+
processed_at TEXT,
|
|
1792
|
+
failed_at TEXT,
|
|
1793
|
+
failure_reason TEXT
|
|
1794
|
+
);
|
|
1795
|
+
|
|
1796
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
1797
|
+
ON messages(target_agent, status);
|
|
1798
|
+
|
|
1237
1799
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
1238
1800
|
ON messages(target_agent, from_agent, server_seq);
|
|
1239
1801
|
`);
|
|
@@ -1375,6 +1937,12 @@ async function ensureSchema() {
|
|
|
1375
1937
|
} catch {
|
|
1376
1938
|
}
|
|
1377
1939
|
}
|
|
1940
|
+
try {
|
|
1941
|
+
await client.execute(
|
|
1942
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1943
|
+
);
|
|
1944
|
+
} catch {
|
|
1945
|
+
}
|
|
1378
1946
|
await client.executeMultiple(`
|
|
1379
1947
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1380
1948
|
id TEXT PRIMARY KEY,
|
|
@@ -1427,7 +1995,30 @@ async function ensureSchema() {
|
|
|
1427
1995
|
entity_id TEXT NOT NULL,
|
|
1428
1996
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1429
1997
|
);
|
|
1998
|
+
|
|
1999
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
2000
|
+
name,
|
|
2001
|
+
content=entities,
|
|
2002
|
+
content_rowid=rowid
|
|
2003
|
+
);
|
|
2004
|
+
|
|
2005
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
2006
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2007
|
+
END;
|
|
2008
|
+
|
|
2009
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
2010
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2011
|
+
END;
|
|
2012
|
+
|
|
2013
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
2014
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2015
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2016
|
+
END;
|
|
1430
2017
|
`);
|
|
2018
|
+
try {
|
|
2019
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
1431
2022
|
await client.executeMultiple(`
|
|
1432
2023
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1433
2024
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1608,7 +2199,34 @@ async function ensureSchema() {
|
|
|
1608
2199
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1609
2200
|
ON conversations(channel_id);
|
|
1610
2201
|
`);
|
|
1611
|
-
|
|
2202
|
+
await client.executeMultiple(`
|
|
2203
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
2204
|
+
session_uuid TEXT PRIMARY KEY,
|
|
2205
|
+
agent_id TEXT NOT NULL,
|
|
2206
|
+
session_name TEXT,
|
|
2207
|
+
task_id TEXT,
|
|
2208
|
+
project_name TEXT,
|
|
2209
|
+
started_at TEXT NOT NULL
|
|
2210
|
+
);
|
|
2211
|
+
|
|
2212
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
2213
|
+
ON session_agent_map(agent_id);
|
|
2214
|
+
`);
|
|
2215
|
+
try {
|
|
2216
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
2217
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
2218
|
+
await client.execute({
|
|
2219
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
2220
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
2221
|
+
FROM memories
|
|
2222
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
2223
|
+
GROUP BY session_id, agent_id`,
|
|
2224
|
+
args: []
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
} catch {
|
|
2228
|
+
}
|
|
2229
|
+
try {
|
|
1612
2230
|
await client.execute({
|
|
1613
2231
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1614
2232
|
args: []
|
|
@@ -1741,15 +2359,41 @@ async function ensureSchema() {
|
|
|
1741
2359
|
});
|
|
1742
2360
|
} catch {
|
|
1743
2361
|
}
|
|
2362
|
+
for (const col of [
|
|
2363
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2364
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2365
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2366
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2367
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2368
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2369
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2370
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2371
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2372
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2373
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2374
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2375
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2376
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2377
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2378
|
+
]) {
|
|
2379
|
+
try {
|
|
2380
|
+
await client.execute(col);
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
1744
2384
|
}
|
|
1745
2385
|
async function disposeDatabase() {
|
|
2386
|
+
if (_daemonClient) {
|
|
2387
|
+
_daemonClient.close();
|
|
2388
|
+
_daemonClient = null;
|
|
2389
|
+
}
|
|
1746
2390
|
if (_client) {
|
|
1747
2391
|
_client.close();
|
|
1748
2392
|
_client = null;
|
|
1749
2393
|
_resilientClient = null;
|
|
1750
2394
|
}
|
|
1751
2395
|
}
|
|
1752
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2396
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1753
2397
|
var init_database = __esm({
|
|
1754
2398
|
"src/lib/database.ts"() {
|
|
1755
2399
|
"use strict";
|
|
@@ -1757,24 +2401,25 @@ var init_database = __esm({
|
|
|
1757
2401
|
init_employees();
|
|
1758
2402
|
_client = null;
|
|
1759
2403
|
_resilientClient = null;
|
|
2404
|
+
_daemonClient = null;
|
|
1760
2405
|
initTurso = initDatabase;
|
|
1761
2406
|
disposeTurso = disposeDatabase;
|
|
1762
2407
|
}
|
|
1763
2408
|
});
|
|
1764
2409
|
|
|
1765
2410
|
// src/lib/license.ts
|
|
1766
|
-
import { readFileSync as
|
|
1767
|
-
import { randomUUID as
|
|
1768
|
-
import
|
|
2411
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2412
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2413
|
+
import path7 from "path";
|
|
1769
2414
|
import { jwtVerify, importSPKI } from "jose";
|
|
1770
2415
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1771
2416
|
var init_license = __esm({
|
|
1772
2417
|
"src/lib/license.ts"() {
|
|
1773
2418
|
"use strict";
|
|
1774
2419
|
init_config();
|
|
1775
|
-
LICENSE_PATH =
|
|
1776
|
-
CACHE_PATH =
|
|
1777
|
-
DEVICE_ID_PATH =
|
|
2420
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2421
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2422
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1778
2423
|
PLAN_LIMITS = {
|
|
1779
2424
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1780
2425
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1786,12 +2431,12 @@ var init_license = __esm({
|
|
|
1786
2431
|
});
|
|
1787
2432
|
|
|
1788
2433
|
// src/lib/plan-limits.ts
|
|
1789
|
-
import { readFileSync as
|
|
1790
|
-
import
|
|
2434
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
2435
|
+
import path8 from "path";
|
|
1791
2436
|
function getLicenseSync() {
|
|
1792
2437
|
try {
|
|
1793
|
-
if (!
|
|
1794
|
-
const raw = JSON.parse(
|
|
2438
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
2439
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1795
2440
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1796
2441
|
const parts = raw.token.split(".");
|
|
1797
2442
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1829,8 +2474,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1829
2474
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1830
2475
|
let count = 0;
|
|
1831
2476
|
try {
|
|
1832
|
-
if (
|
|
1833
|
-
const raw =
|
|
2477
|
+
if (existsSync7(filePath)) {
|
|
2478
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1834
2479
|
const employees = JSON.parse(raw);
|
|
1835
2480
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1836
2481
|
}
|
|
@@ -1859,19 +2504,19 @@ var init_plan_limits = __esm({
|
|
|
1859
2504
|
this.name = "PlanLimitError";
|
|
1860
2505
|
}
|
|
1861
2506
|
};
|
|
1862
|
-
CACHE_PATH2 =
|
|
2507
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1863
2508
|
}
|
|
1864
2509
|
});
|
|
1865
2510
|
|
|
1866
2511
|
// src/lib/notifications.ts
|
|
1867
2512
|
import crypto from "crypto";
|
|
1868
|
-
import
|
|
2513
|
+
import path9 from "path";
|
|
1869
2514
|
import os6 from "os";
|
|
1870
2515
|
import {
|
|
1871
|
-
readFileSync as
|
|
2516
|
+
readFileSync as readFileSync8,
|
|
1872
2517
|
readdirSync,
|
|
1873
|
-
unlinkSync as
|
|
1874
|
-
existsSync as
|
|
2518
|
+
unlinkSync as unlinkSync3,
|
|
2519
|
+
existsSync as existsSync8,
|
|
1875
2520
|
rmdirSync
|
|
1876
2521
|
} from "fs";
|
|
1877
2522
|
async function writeNotification(notification) {
|
|
@@ -2035,10 +2680,11 @@ var init_state_bus = __esm({
|
|
|
2035
2680
|
|
|
2036
2681
|
// src/lib/tasks-crud.ts
|
|
2037
2682
|
import crypto3 from "crypto";
|
|
2038
|
-
import
|
|
2683
|
+
import path10 from "path";
|
|
2684
|
+
import os7 from "os";
|
|
2039
2685
|
import { execSync as execSync5 } from "child_process";
|
|
2040
2686
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2041
|
-
import { existsSync as
|
|
2687
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
2042
2688
|
async function writeCheckpoint(input) {
|
|
2043
2689
|
const client = getClient();
|
|
2044
2690
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2079,6 +2725,35 @@ function extractParentFromContext(contextBody) {
|
|
|
2079
2725
|
function slugify(title) {
|
|
2080
2726
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2081
2727
|
}
|
|
2728
|
+
function buildKeywordIndex() {
|
|
2729
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2730
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2731
|
+
for (const kw of keywords) {
|
|
2732
|
+
const existing = idx.get(kw) ?? [];
|
|
2733
|
+
existing.push(role);
|
|
2734
|
+
idx.set(kw, existing);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return idx;
|
|
2738
|
+
}
|
|
2739
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2740
|
+
const employees = loadEmployeesSync();
|
|
2741
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2742
|
+
if (!employee) return void 0;
|
|
2743
|
+
const assigneeRole = employee.role;
|
|
2744
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2745
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2746
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2747
|
+
if (text.includes(keyword)) {
|
|
2748
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2752
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2753
|
+
if (assigneeRole === "COO") return void 0;
|
|
2754
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2755
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2756
|
+
}
|
|
2082
2757
|
async function resolveTask(client, identifier, scopeSession) {
|
|
2083
2758
|
const scope = sessionScopeFilter(scopeSession);
|
|
2084
2759
|
let result = await client.execute({
|
|
@@ -2128,7 +2803,14 @@ async function createTaskCore(input) {
|
|
|
2128
2803
|
const id = crypto3.randomUUID();
|
|
2129
2804
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2130
2805
|
const slug = slugify(input.title);
|
|
2131
|
-
|
|
2806
|
+
let earlySessionScope = null;
|
|
2807
|
+
try {
|
|
2808
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2809
|
+
earlySessionScope = resolveExeSession2();
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
const scope = earlySessionScope ?? "default";
|
|
2813
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
2132
2814
|
let blockedById = null;
|
|
2133
2815
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
2134
2816
|
if (input.blockedBy) {
|
|
@@ -2168,22 +2850,24 @@ async function createTaskCore(input) {
|
|
|
2168
2850
|
if (dupCheck.rows.length > 0) {
|
|
2169
2851
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
2170
2852
|
}
|
|
2853
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2854
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2855
|
+
if (laneWarning) {
|
|
2856
|
+
warning = warning ? `${warning}
|
|
2857
|
+
${laneWarning}` : laneWarning;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2171
2860
|
if (input.baseDir) {
|
|
2172
2861
|
try {
|
|
2173
|
-
await mkdir3(
|
|
2174
|
-
await mkdir3(
|
|
2862
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2863
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2175
2864
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2176
2865
|
await ensureGitignoreExe(input.baseDir);
|
|
2177
2866
|
} catch {
|
|
2178
2867
|
}
|
|
2179
2868
|
}
|
|
2180
2869
|
const complexity = input.complexity ?? "standard";
|
|
2181
|
-
|
|
2182
|
-
try {
|
|
2183
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2184
|
-
sessionScope = resolveExeSession2();
|
|
2185
|
-
} catch {
|
|
2186
|
-
}
|
|
2870
|
+
const sessionScope = earlySessionScope;
|
|
2187
2871
|
await client.execute({
|
|
2188
2872
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
2189
2873
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2210,6 +2894,39 @@ async function createTaskCore(input) {
|
|
|
2210
2894
|
now
|
|
2211
2895
|
]
|
|
2212
2896
|
});
|
|
2897
|
+
if (input.baseDir) {
|
|
2898
|
+
try {
|
|
2899
|
+
const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
|
|
2900
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2901
|
+
const mdDir = path10.dirname(mdPath);
|
|
2902
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2903
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2904
|
+
const mdContent = `# ${input.title}
|
|
2905
|
+
|
|
2906
|
+
**ID:** ${id}
|
|
2907
|
+
**Status:** ${initialStatus}
|
|
2908
|
+
**Priority:** ${input.priority}
|
|
2909
|
+
**Assigned by:** ${input.assignedBy}
|
|
2910
|
+
**Assigned to:** ${input.assignedTo}
|
|
2911
|
+
**Project:** ${input.projectName}
|
|
2912
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2913
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2914
|
+
**Reviewer:** ${reviewer}
|
|
2915
|
+
|
|
2916
|
+
## Context
|
|
2917
|
+
|
|
2918
|
+
${input.context}
|
|
2919
|
+
|
|
2920
|
+
## MANDATORY: When done
|
|
2921
|
+
|
|
2922
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2923
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2924
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2925
|
+
`;
|
|
2926
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2927
|
+
} catch {
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2213
2930
|
return {
|
|
2214
2931
|
id,
|
|
2215
2932
|
title: input.title,
|
|
@@ -2402,7 +3119,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2402
3119
|
return { row, taskFile, now, taskId };
|
|
2403
3120
|
}
|
|
2404
3121
|
}
|
|
2405
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
3122
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2406
3123
|
process.stderr.write(
|
|
2407
3124
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2408
3125
|
`
|
|
@@ -2467,9 +3184,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2467
3184
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2468
3185
|
}
|
|
2469
3186
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2470
|
-
const archPath =
|
|
3187
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2471
3188
|
try {
|
|
2472
|
-
if (
|
|
3189
|
+
if (existsSync9(archPath)) return;
|
|
2473
3190
|
const template = [
|
|
2474
3191
|
`# ${projectName} \u2014 System Architecture`,
|
|
2475
3192
|
"",
|
|
@@ -2502,10 +3219,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2502
3219
|
}
|
|
2503
3220
|
}
|
|
2504
3221
|
async function ensureGitignoreExe(baseDir) {
|
|
2505
|
-
const gitignorePath =
|
|
3222
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2506
3223
|
try {
|
|
2507
|
-
if (
|
|
2508
|
-
const content =
|
|
3224
|
+
if (existsSync9(gitignorePath)) {
|
|
3225
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2509
3226
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2510
3227
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2511
3228
|
} else {
|
|
@@ -2514,20 +3231,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2514
3231
|
} catch {
|
|
2515
3232
|
}
|
|
2516
3233
|
}
|
|
2517
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3234
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2518
3235
|
var init_tasks_crud = __esm({
|
|
2519
3236
|
"src/lib/tasks-crud.ts"() {
|
|
2520
3237
|
"use strict";
|
|
2521
3238
|
init_database();
|
|
2522
3239
|
init_task_scope();
|
|
3240
|
+
init_employees();
|
|
3241
|
+
LANE_KEYWORDS = {
|
|
3242
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
3243
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
3244
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
3245
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
3246
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
3247
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
3248
|
+
};
|
|
3249
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2523
3250
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2524
3251
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2525
3252
|
}
|
|
2526
3253
|
});
|
|
2527
3254
|
|
|
2528
3255
|
// src/lib/tasks-review.ts
|
|
2529
|
-
import
|
|
2530
|
-
import { existsSync as
|
|
3256
|
+
import path11 from "path";
|
|
3257
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2531
3258
|
async function countPendingReviews(sessionScope) {
|
|
2532
3259
|
const client = getClient();
|
|
2533
3260
|
if (sessionScope) {
|
|
@@ -2549,7 +3276,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2549
3276
|
const result2 = await client.execute({
|
|
2550
3277
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2551
3278
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2552
|
-
AND
|
|
3279
|
+
AND session_scope = ?`,
|
|
2553
3280
|
args: [sinceIso, sessionScope]
|
|
2554
3281
|
});
|
|
2555
3282
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2567,7 +3294,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2567
3294
|
const result2 = await client.execute({
|
|
2568
3295
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2569
3296
|
WHERE status = 'needs_review'
|
|
2570
|
-
AND
|
|
3297
|
+
AND session_scope = ?
|
|
2571
3298
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2572
3299
|
args: [sessionScope, limit]
|
|
2573
3300
|
});
|
|
@@ -2688,14 +3415,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2688
3415
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2689
3416
|
const agent = parts[1];
|
|
2690
3417
|
const slug = parts.slice(2).join("-");
|
|
2691
|
-
const
|
|
3418
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2692
3419
|
const result = await client.execute({
|
|
2693
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2694
|
-
args: [now,
|
|
3420
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3421
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2695
3422
|
});
|
|
2696
3423
|
if (result.rowsAffected > 0) {
|
|
2697
3424
|
process.stderr.write(
|
|
2698
|
-
`[review-cleanup] Cascaded original task to done
|
|
3425
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2699
3426
|
`
|
|
2700
3427
|
);
|
|
2701
3428
|
}
|
|
@@ -2708,11 +3435,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2708
3435
|
);
|
|
2709
3436
|
}
|
|
2710
3437
|
try {
|
|
2711
|
-
const cacheDir =
|
|
2712
|
-
if (
|
|
3438
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3439
|
+
if (existsSync10(cacheDir)) {
|
|
2713
3440
|
for (const f of readdirSync2(cacheDir)) {
|
|
2714
3441
|
if (f.startsWith("review-notified-")) {
|
|
2715
|
-
|
|
3442
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2716
3443
|
}
|
|
2717
3444
|
}
|
|
2718
3445
|
}
|
|
@@ -2733,7 +3460,7 @@ var init_tasks_review = __esm({
|
|
|
2733
3460
|
});
|
|
2734
3461
|
|
|
2735
3462
|
// src/lib/tasks-chain.ts
|
|
2736
|
-
import
|
|
3463
|
+
import path12 from "path";
|
|
2737
3464
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2738
3465
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2739
3466
|
const client = getClient();
|
|
@@ -2750,7 +3477,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2750
3477
|
});
|
|
2751
3478
|
for (const ur of unblockedRows.rows) {
|
|
2752
3479
|
try {
|
|
2753
|
-
const ubFile =
|
|
3480
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2754
3481
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2755
3482
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2756
3483
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2819,7 +3546,7 @@ var init_tasks_chain = __esm({
|
|
|
2819
3546
|
|
|
2820
3547
|
// src/lib/project-name.ts
|
|
2821
3548
|
import { execSync as execSync6 } from "child_process";
|
|
2822
|
-
import
|
|
3549
|
+
import path13 from "path";
|
|
2823
3550
|
function getProjectName(cwd) {
|
|
2824
3551
|
const dir = cwd ?? process.cwd();
|
|
2825
3552
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2832,7 +3559,7 @@ function getProjectName(cwd) {
|
|
|
2832
3559
|
timeout: 2e3,
|
|
2833
3560
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2834
3561
|
}).trim();
|
|
2835
|
-
repoRoot =
|
|
3562
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2836
3563
|
} catch {
|
|
2837
3564
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2838
3565
|
cwd: dir,
|
|
@@ -2841,11 +3568,11 @@ function getProjectName(cwd) {
|
|
|
2841
3568
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2842
3569
|
}).trim();
|
|
2843
3570
|
}
|
|
2844
|
-
_cached2 =
|
|
3571
|
+
_cached2 = path13.basename(repoRoot);
|
|
2845
3572
|
_cachedCwd = dir;
|
|
2846
3573
|
return _cached2;
|
|
2847
3574
|
} catch {
|
|
2848
|
-
_cached2 =
|
|
3575
|
+
_cached2 = path13.basename(dir);
|
|
2849
3576
|
_cachedCwd = dir;
|
|
2850
3577
|
return _cached2;
|
|
2851
3578
|
}
|
|
@@ -2877,7 +3604,7 @@ function findSessionForProject(projectName) {
|
|
|
2877
3604
|
const sessions = listSessions();
|
|
2878
3605
|
for (const s of sessions) {
|
|
2879
3606
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2880
|
-
if (proj === projectName &&
|
|
3607
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2881
3608
|
}
|
|
2882
3609
|
return null;
|
|
2883
3610
|
}
|
|
@@ -2923,7 +3650,7 @@ var init_session_scope = __esm({
|
|
|
2923
3650
|
|
|
2924
3651
|
// src/lib/tasks-notify.ts
|
|
2925
3652
|
async function dispatchTaskToEmployee(input) {
|
|
2926
|
-
if (
|
|
3653
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2927
3654
|
let crossProject = false;
|
|
2928
3655
|
if (input.projectName) {
|
|
2929
3656
|
try {
|
|
@@ -3380,8 +4107,8 @@ __export(tasks_exports, {
|
|
|
3380
4107
|
updateTaskStatus: () => updateTaskStatus,
|
|
3381
4108
|
writeCheckpoint: () => writeCheckpoint
|
|
3382
4109
|
});
|
|
3383
|
-
import
|
|
3384
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
4110
|
+
import path14 from "path";
|
|
4111
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3385
4112
|
async function createTask(input) {
|
|
3386
4113
|
const result = await createTaskCore(input);
|
|
3387
4114
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3400,14 +4127,14 @@ async function updateTask(input) {
|
|
|
3400
4127
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3401
4128
|
try {
|
|
3402
4129
|
const agent = String(row.assigned_to);
|
|
3403
|
-
const cacheDir =
|
|
3404
|
-
const cachePath =
|
|
4130
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
4131
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3405
4132
|
if (input.status === "in_progress") {
|
|
3406
4133
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3407
4134
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3408
4135
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3409
4136
|
try {
|
|
3410
|
-
|
|
4137
|
+
unlinkSync5(cachePath);
|
|
3411
4138
|
} catch {
|
|
3412
4139
|
}
|
|
3413
4140
|
}
|
|
@@ -3464,7 +4191,7 @@ async function updateTask(input) {
|
|
|
3464
4191
|
}
|
|
3465
4192
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3466
4193
|
if (isTerminal) {
|
|
3467
|
-
const isCoordinator =
|
|
4194
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3468
4195
|
if (!isCoordinator) {
|
|
3469
4196
|
notifyTaskDone();
|
|
3470
4197
|
}
|
|
@@ -3489,7 +4216,7 @@ async function updateTask(input) {
|
|
|
3489
4216
|
}
|
|
3490
4217
|
}
|
|
3491
4218
|
}
|
|
3492
|
-
if (input.status === "done" &&
|
|
4219
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3493
4220
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3494
4221
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3495
4222
|
taskId,
|
|
@@ -3505,7 +4232,7 @@ async function updateTask(input) {
|
|
|
3505
4232
|
});
|
|
3506
4233
|
}
|
|
3507
4234
|
let nextTask;
|
|
3508
|
-
if (isTerminal &&
|
|
4235
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3509
4236
|
try {
|
|
3510
4237
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3511
4238
|
} catch {
|
|
@@ -3849,7 +4576,7 @@ var init_capacity_monitor = __esm({
|
|
|
3849
4576
|
// src/lib/tmux-routing.ts
|
|
3850
4577
|
var tmux_routing_exports = {};
|
|
3851
4578
|
__export(tmux_routing_exports, {
|
|
3852
|
-
acquireSpawnLock: () =>
|
|
4579
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3853
4580
|
employeeSessionName: () => employeeSessionName,
|
|
3854
4581
|
ensureEmployee: () => ensureEmployee,
|
|
3855
4582
|
extractRootExe: () => extractRootExe,
|
|
@@ -3864,20 +4591,20 @@ __export(tmux_routing_exports, {
|
|
|
3864
4591
|
notifyParentExe: () => notifyParentExe,
|
|
3865
4592
|
parseParentExe: () => parseParentExe,
|
|
3866
4593
|
registerParentExe: () => registerParentExe,
|
|
3867
|
-
releaseSpawnLock: () =>
|
|
4594
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3868
4595
|
resolveExeSession: () => resolveExeSession,
|
|
3869
4596
|
sendIntercom: () => sendIntercom,
|
|
3870
4597
|
spawnEmployee: () => spawnEmployee,
|
|
3871
4598
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3872
4599
|
});
|
|
3873
4600
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3874
|
-
import { readFileSync as
|
|
3875
|
-
import
|
|
3876
|
-
import
|
|
3877
|
-
import { fileURLToPath } from "url";
|
|
3878
|
-
import { unlinkSync as
|
|
4601
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4602
|
+
import path15 from "path";
|
|
4603
|
+
import os8 from "os";
|
|
4604
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4605
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3879
4606
|
function spawnLockPath(sessionName) {
|
|
3880
|
-
return
|
|
4607
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3881
4608
|
}
|
|
3882
4609
|
function isProcessAlive(pid) {
|
|
3883
4610
|
try {
|
|
@@ -3887,14 +4614,14 @@ function isProcessAlive(pid) {
|
|
|
3887
4614
|
return false;
|
|
3888
4615
|
}
|
|
3889
4616
|
}
|
|
3890
|
-
function
|
|
3891
|
-
if (!
|
|
4617
|
+
function acquireSpawnLock2(sessionName) {
|
|
4618
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3892
4619
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3893
4620
|
}
|
|
3894
4621
|
const lockFile = spawnLockPath(sessionName);
|
|
3895
|
-
if (
|
|
4622
|
+
if (existsSync11(lockFile)) {
|
|
3896
4623
|
try {
|
|
3897
|
-
const lock = JSON.parse(
|
|
4624
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3898
4625
|
const age = Date.now() - lock.timestamp;
|
|
3899
4626
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3900
4627
|
return false;
|
|
@@ -3905,22 +4632,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3905
4632
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3906
4633
|
return true;
|
|
3907
4634
|
}
|
|
3908
|
-
function
|
|
4635
|
+
function releaseSpawnLock2(sessionName) {
|
|
3909
4636
|
try {
|
|
3910
|
-
|
|
4637
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3911
4638
|
} catch {
|
|
3912
4639
|
}
|
|
3913
4640
|
}
|
|
3914
4641
|
function resolveBehaviorsExporterScript() {
|
|
3915
4642
|
try {
|
|
3916
|
-
const thisFile =
|
|
3917
|
-
const scriptPath =
|
|
3918
|
-
|
|
4643
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4644
|
+
const scriptPath = path15.join(
|
|
4645
|
+
path15.dirname(thisFile),
|
|
3919
4646
|
"..",
|
|
3920
4647
|
"bin",
|
|
3921
4648
|
"exe-export-behaviors.js"
|
|
3922
4649
|
);
|
|
3923
|
-
return
|
|
4650
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3924
4651
|
} catch {
|
|
3925
4652
|
return null;
|
|
3926
4653
|
}
|
|
@@ -3986,11 +4713,11 @@ function extractRootExe(name) {
|
|
|
3986
4713
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3987
4714
|
}
|
|
3988
4715
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3989
|
-
if (!
|
|
4716
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3990
4717
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3991
4718
|
}
|
|
3992
4719
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3993
|
-
const filePath =
|
|
4720
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3994
4721
|
writeFileSync6(filePath, JSON.stringify({
|
|
3995
4722
|
parentExe: rootExe,
|
|
3996
4723
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3999,7 +4726,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3999
4726
|
}
|
|
4000
4727
|
function getParentExe(sessionKey) {
|
|
4001
4728
|
try {
|
|
4002
|
-
const data = JSON.parse(
|
|
4729
|
+
const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4003
4730
|
return data.parentExe || null;
|
|
4004
4731
|
} catch {
|
|
4005
4732
|
return null;
|
|
@@ -4007,8 +4734,8 @@ function getParentExe(sessionKey) {
|
|
|
4007
4734
|
}
|
|
4008
4735
|
function getDispatchedBy(sessionKey) {
|
|
4009
4736
|
try {
|
|
4010
|
-
const data = JSON.parse(
|
|
4011
|
-
|
|
4737
|
+
const data = JSON.parse(readFileSync10(
|
|
4738
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4012
4739
|
"utf8"
|
|
4013
4740
|
));
|
|
4014
4741
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4034,10 +4761,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
4034
4761
|
}
|
|
4035
4762
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4036
4763
|
const base = employeeSessionName(employeeName, exeSession);
|
|
4037
|
-
if (!isAlive(base) &&
|
|
4764
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4038
4765
|
for (let i = 2; i <= maxInstances; i++) {
|
|
4039
4766
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4040
|
-
if (!isAlive(candidate) &&
|
|
4767
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4041
4768
|
}
|
|
4042
4769
|
return null;
|
|
4043
4770
|
}
|
|
@@ -4069,15 +4796,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4069
4796
|
}
|
|
4070
4797
|
function readDebounceState() {
|
|
4071
4798
|
try {
|
|
4072
|
-
if (!
|
|
4073
|
-
return JSON.parse(
|
|
4799
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4800
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
4074
4801
|
} catch {
|
|
4075
4802
|
return {};
|
|
4076
4803
|
}
|
|
4077
4804
|
}
|
|
4078
4805
|
function writeDebounceState(state) {
|
|
4079
4806
|
try {
|
|
4080
|
-
if (!
|
|
4807
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
4081
4808
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4082
4809
|
} catch {
|
|
4083
4810
|
}
|
|
@@ -4197,7 +4924,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4197
4924
|
return true;
|
|
4198
4925
|
}
|
|
4199
4926
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4200
|
-
if (
|
|
4927
|
+
if (isCoordinatorName(employeeName)) {
|
|
4201
4928
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4202
4929
|
}
|
|
4203
4930
|
try {
|
|
@@ -4269,26 +4996,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4269
4996
|
const transport = getTransport();
|
|
4270
4997
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4271
4998
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4272
|
-
const logDir =
|
|
4273
|
-
const logFile =
|
|
4274
|
-
if (!
|
|
4999
|
+
const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
|
|
5000
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5001
|
+
if (!existsSync11(logDir)) {
|
|
4275
5002
|
mkdirSync5(logDir, { recursive: true });
|
|
4276
5003
|
}
|
|
4277
5004
|
transport.kill(sessionName);
|
|
4278
5005
|
let cleanupSuffix = "";
|
|
4279
5006
|
try {
|
|
4280
|
-
const thisFile =
|
|
4281
|
-
const cleanupScript =
|
|
4282
|
-
if (
|
|
5007
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
5008
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5009
|
+
if (existsSync11(cleanupScript)) {
|
|
4283
5010
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4284
5011
|
}
|
|
4285
5012
|
} catch {
|
|
4286
5013
|
}
|
|
4287
5014
|
try {
|
|
4288
|
-
const claudeJsonPath =
|
|
5015
|
+
const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
|
|
4289
5016
|
let claudeJson = {};
|
|
4290
5017
|
try {
|
|
4291
|
-
claudeJson = JSON.parse(
|
|
5018
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
4292
5019
|
} catch {
|
|
4293
5020
|
}
|
|
4294
5021
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4300,13 +5027,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4300
5027
|
} catch {
|
|
4301
5028
|
}
|
|
4302
5029
|
try {
|
|
4303
|
-
const settingsDir =
|
|
5030
|
+
const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
|
|
4304
5031
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4305
|
-
const projSettingsDir =
|
|
4306
|
-
const settingsPath =
|
|
5032
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
5033
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
4307
5034
|
let settings = {};
|
|
4308
5035
|
try {
|
|
4309
|
-
settings = JSON.parse(
|
|
5036
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
4310
5037
|
} catch {
|
|
4311
5038
|
}
|
|
4312
5039
|
const perms = settings.permissions ?? {};
|
|
@@ -4347,8 +5074,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4347
5074
|
let behaviorsFlag = "";
|
|
4348
5075
|
let legacyFallbackWarned = false;
|
|
4349
5076
|
if (!useExeAgent && !useBinSymlink) {
|
|
4350
|
-
const identityPath =
|
|
4351
|
-
|
|
5077
|
+
const identityPath = path15.join(
|
|
5078
|
+
os8.homedir(),
|
|
4352
5079
|
".exe-os",
|
|
4353
5080
|
"identity",
|
|
4354
5081
|
`${employeeName}.md`
|
|
@@ -4357,13 +5084,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4357
5084
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4358
5085
|
if (hasAgentFlag) {
|
|
4359
5086
|
identityFlag = ` --agent ${employeeName}`;
|
|
4360
|
-
} else if (
|
|
5087
|
+
} else if (existsSync11(identityPath)) {
|
|
4361
5088
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4362
5089
|
legacyFallbackWarned = true;
|
|
4363
5090
|
}
|
|
4364
5091
|
const behaviorsFile = exportBehaviorsSync(
|
|
4365
5092
|
employeeName,
|
|
4366
|
-
|
|
5093
|
+
path15.basename(spawnCwd),
|
|
4367
5094
|
sessionName
|
|
4368
5095
|
);
|
|
4369
5096
|
if (behaviorsFile) {
|
|
@@ -4378,9 +5105,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4378
5105
|
}
|
|
4379
5106
|
let sessionContextFlag = "";
|
|
4380
5107
|
try {
|
|
4381
|
-
const ctxDir =
|
|
5108
|
+
const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4382
5109
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4383
|
-
const ctxFile =
|
|
5110
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4384
5111
|
const ctxContent = [
|
|
4385
5112
|
`## Session Context`,
|
|
4386
5113
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4419,13 +5146,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4419
5146
|
command: spawnCommand
|
|
4420
5147
|
});
|
|
4421
5148
|
if (spawnResult.error) {
|
|
4422
|
-
|
|
5149
|
+
releaseSpawnLock2(sessionName);
|
|
4423
5150
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4424
5151
|
}
|
|
4425
5152
|
transport.pipeLog(sessionName, logFile);
|
|
4426
5153
|
try {
|
|
4427
5154
|
const mySession = getMySession();
|
|
4428
|
-
const dispatchInfo =
|
|
5155
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4429
5156
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4430
5157
|
dispatchedBy: mySession,
|
|
4431
5158
|
rootExe: exeSession,
|
|
@@ -4457,7 +5184,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4457
5184
|
}
|
|
4458
5185
|
}
|
|
4459
5186
|
if (!booted) {
|
|
4460
|
-
|
|
5187
|
+
releaseSpawnLock2(sessionName);
|
|
4461
5188
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4462
5189
|
}
|
|
4463
5190
|
if (!useExeAgent) {
|
|
@@ -4474,7 +5201,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4474
5201
|
pid: 0,
|
|
4475
5202
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4476
5203
|
});
|
|
4477
|
-
|
|
5204
|
+
releaseSpawnLock2(sessionName);
|
|
4478
5205
|
return { sessionName };
|
|
4479
5206
|
}
|
|
4480
5207
|
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
@@ -4490,14 +5217,14 @@ var init_tmux_routing = __esm({
|
|
|
4490
5217
|
init_intercom_queue();
|
|
4491
5218
|
init_plan_limits();
|
|
4492
5219
|
init_employees();
|
|
4493
|
-
SPAWN_LOCK_DIR =
|
|
4494
|
-
SESSION_CACHE =
|
|
5220
|
+
SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
5221
|
+
SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4495
5222
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4496
5223
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4497
5224
|
VERIFY_PANE_LINES = 200;
|
|
4498
5225
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4499
|
-
INTERCOM_LOG2 =
|
|
4500
|
-
DEBOUNCE_FILE =
|
|
5226
|
+
INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
5227
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4501
5228
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4502
5229
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4503
5230
|
}
|
|
@@ -4514,14 +5241,14 @@ var init_memory = __esm({
|
|
|
4514
5241
|
|
|
4515
5242
|
// src/lib/keychain.ts
|
|
4516
5243
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4517
|
-
import { existsSync as
|
|
4518
|
-
import
|
|
4519
|
-
import
|
|
5244
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5245
|
+
import path16 from "path";
|
|
5246
|
+
import os9 from "os";
|
|
4520
5247
|
function getKeyDir() {
|
|
4521
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5248
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
|
|
4522
5249
|
}
|
|
4523
5250
|
function getKeyPath() {
|
|
4524
|
-
return
|
|
5251
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4525
5252
|
}
|
|
4526
5253
|
async function tryKeytar() {
|
|
4527
5254
|
try {
|
|
@@ -4542,13 +5269,21 @@ async function getMasterKey() {
|
|
|
4542
5269
|
}
|
|
4543
5270
|
}
|
|
4544
5271
|
const keyPath = getKeyPath();
|
|
4545
|
-
if (!
|
|
5272
|
+
if (!existsSync12(keyPath)) {
|
|
5273
|
+
process.stderr.write(
|
|
5274
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5275
|
+
`
|
|
5276
|
+
);
|
|
4546
5277
|
return null;
|
|
4547
5278
|
}
|
|
4548
5279
|
try {
|
|
4549
5280
|
const content = await readFile4(keyPath, "utf-8");
|
|
4550
5281
|
return Buffer.from(content.trim(), "base64");
|
|
4551
|
-
} catch {
|
|
5282
|
+
} catch (err) {
|
|
5283
|
+
process.stderr.write(
|
|
5284
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5285
|
+
`
|
|
5286
|
+
);
|
|
4552
5287
|
return null;
|
|
4553
5288
|
}
|
|
4554
5289
|
}
|
|
@@ -4574,12 +5309,12 @@ __export(shard_manager_exports, {
|
|
|
4574
5309
|
listShards: () => listShards,
|
|
4575
5310
|
shardExists: () => shardExists
|
|
4576
5311
|
});
|
|
4577
|
-
import
|
|
4578
|
-
import { existsSync as
|
|
5312
|
+
import path17 from "path";
|
|
5313
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4579
5314
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4580
5315
|
function initShardManager(encryptionKey) {
|
|
4581
5316
|
_encryptionKey = encryptionKey;
|
|
4582
|
-
if (!
|
|
5317
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4583
5318
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
4584
5319
|
}
|
|
4585
5320
|
_shardingEnabled = true;
|
|
@@ -4600,7 +5335,7 @@ function getShardClient(projectName) {
|
|
|
4600
5335
|
}
|
|
4601
5336
|
const cached = _shards.get(safeName);
|
|
4602
5337
|
if (cached) return cached;
|
|
4603
|
-
const dbPath =
|
|
5338
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4604
5339
|
const client = createClient2({
|
|
4605
5340
|
url: `file:${dbPath}`,
|
|
4606
5341
|
encryptionKey: _encryptionKey
|
|
@@ -4610,10 +5345,10 @@ function getShardClient(projectName) {
|
|
|
4610
5345
|
}
|
|
4611
5346
|
function shardExists(projectName) {
|
|
4612
5347
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4613
|
-
return
|
|
5348
|
+
return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4614
5349
|
}
|
|
4615
5350
|
function listShards() {
|
|
4616
|
-
if (!
|
|
5351
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4617
5352
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4618
5353
|
}
|
|
4619
5354
|
async function ensureShardSchema(client) {
|
|
@@ -4799,7 +5534,7 @@ var init_shard_manager = __esm({
|
|
|
4799
5534
|
"src/lib/shard-manager.ts"() {
|
|
4800
5535
|
"use strict";
|
|
4801
5536
|
init_config();
|
|
4802
|
-
SHARDS_DIR =
|
|
5537
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4803
5538
|
_shards = /* @__PURE__ */ new Map();
|
|
4804
5539
|
_encryptionKey = null;
|
|
4805
5540
|
_shardingEnabled = false;
|
|
@@ -4924,7 +5659,7 @@ __export(global_procedures_exports, {
|
|
|
4924
5659
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4925
5660
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4926
5661
|
});
|
|
4927
|
-
import { randomUUID as
|
|
5662
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4928
5663
|
async function loadGlobalProcedures() {
|
|
4929
5664
|
const client = getClient();
|
|
4930
5665
|
const result = await client.execute({
|
|
@@ -4953,7 +5688,7 @@ ${sections.join("\n\n")}
|
|
|
4953
5688
|
`;
|
|
4954
5689
|
}
|
|
4955
5690
|
async function storeGlobalProcedure(input) {
|
|
4956
|
-
const id =
|
|
5691
|
+
const id = randomUUID4();
|
|
4957
5692
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4958
5693
|
const client = getClient();
|
|
4959
5694
|
await client.execute({
|
|
@@ -5004,6 +5739,7 @@ __export(store_exports, {
|
|
|
5004
5739
|
vectorToBlob: () => vectorToBlob,
|
|
5005
5740
|
writeMemory: () => writeMemory
|
|
5006
5741
|
});
|
|
5742
|
+
import { createHash } from "crypto";
|
|
5007
5743
|
function isBusyError2(err) {
|
|
5008
5744
|
if (err instanceof Error) {
|
|
5009
5745
|
const msg = err.message.toLowerCase();
|
|
@@ -5077,12 +5813,52 @@ function classifyTier(record) {
|
|
|
5077
5813
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
5078
5814
|
return 3;
|
|
5079
5815
|
}
|
|
5816
|
+
function inferFilePaths(record) {
|
|
5817
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5818
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5819
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5820
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5821
|
+
}
|
|
5822
|
+
function inferCommitHash(record) {
|
|
5823
|
+
if (record.tool_name !== "Bash") return null;
|
|
5824
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5825
|
+
return match ? match[1] : null;
|
|
5826
|
+
}
|
|
5827
|
+
function inferLanguageType(record) {
|
|
5828
|
+
const text = record.raw_text;
|
|
5829
|
+
if (!text || text.length < 10) return null;
|
|
5830
|
+
const trimmed = text.trimStart();
|
|
5831
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5832
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5833
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5834
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5835
|
+
return "mixed";
|
|
5836
|
+
}
|
|
5837
|
+
function inferDomain(record) {
|
|
5838
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5839
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5840
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5841
|
+
return null;
|
|
5842
|
+
}
|
|
5080
5843
|
async function writeMemory(record) {
|
|
5081
5844
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
5082
5845
|
throw new Error(
|
|
5083
5846
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
5084
5847
|
);
|
|
5085
5848
|
}
|
|
5849
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5850
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5851
|
+
return;
|
|
5852
|
+
}
|
|
5853
|
+
try {
|
|
5854
|
+
const client = getClient();
|
|
5855
|
+
const existing = await client.execute({
|
|
5856
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5857
|
+
args: [contentHash, record.agent_id]
|
|
5858
|
+
});
|
|
5859
|
+
if (existing.rows.length > 0) return;
|
|
5860
|
+
} catch {
|
|
5861
|
+
}
|
|
5086
5862
|
const dbRow = {
|
|
5087
5863
|
id: record.id,
|
|
5088
5864
|
agent_id: record.agent_id,
|
|
@@ -5112,7 +5888,23 @@ async function writeMemory(record) {
|
|
|
5112
5888
|
supersedes_id: record.supersedes_id ?? null,
|
|
5113
5889
|
draft: record.draft ? 1 : 0,
|
|
5114
5890
|
memory_type: record.memory_type ?? "raw",
|
|
5115
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5891
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5892
|
+
content_hash: contentHash,
|
|
5893
|
+
intent: record.intent ?? null,
|
|
5894
|
+
outcome: record.outcome ?? null,
|
|
5895
|
+
domain: record.domain ?? inferDomain(record),
|
|
5896
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5897
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5898
|
+
chain_position: record.chain_position ?? null,
|
|
5899
|
+
review_status: record.review_status ?? null,
|
|
5900
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5901
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5902
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5903
|
+
duration_ms: record.duration_ms ?? null,
|
|
5904
|
+
token_cost: record.token_cost ?? null,
|
|
5905
|
+
audience: record.audience ?? null,
|
|
5906
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5907
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
5116
5908
|
};
|
|
5117
5909
|
_pendingRecords.push(dbRow);
|
|
5118
5910
|
orgBus.emit({
|
|
@@ -5170,80 +5962,85 @@ async function flushBatch() {
|
|
|
5170
5962
|
const draft = row.draft ? 1 : 0;
|
|
5171
5963
|
const memoryType = row.memory_type ?? "raw";
|
|
5172
5964
|
const trajectory = row.trajectory ?? null;
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5965
|
+
const contentHash = row.content_hash ?? null;
|
|
5966
|
+
const intent = row.intent ?? null;
|
|
5967
|
+
const outcome = row.outcome ?? null;
|
|
5968
|
+
const domain = row.domain ?? null;
|
|
5969
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5970
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5971
|
+
const chainPosition = row.chain_position ?? null;
|
|
5972
|
+
const reviewStatus = row.review_status ?? null;
|
|
5973
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5974
|
+
const filePaths = row.file_paths ?? null;
|
|
5975
|
+
const commitHash = row.commit_hash ?? null;
|
|
5976
|
+
const durationMs = row.duration_ms ?? null;
|
|
5977
|
+
const tokenCost = row.token_cost ?? null;
|
|
5978
|
+
const audience = row.audience ?? null;
|
|
5979
|
+
const languageType = row.language_type ?? null;
|
|
5980
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5981
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
5176
5982
|
tool_name, project_name,
|
|
5177
5983
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5178
5984
|
confidence, last_accessed,
|
|
5179
5985
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5180
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
pageNumber,
|
|
5239
|
-
sourcePath,
|
|
5240
|
-
sourceType,
|
|
5241
|
-
tier,
|
|
5242
|
-
supersedesId,
|
|
5243
|
-
draft,
|
|
5244
|
-
memoryType,
|
|
5245
|
-
trajectory
|
|
5246
|
-
]
|
|
5986
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5987
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5988
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5989
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5990
|
+
const metaArgs = [
|
|
5991
|
+
intent,
|
|
5992
|
+
outcome,
|
|
5993
|
+
domain,
|
|
5994
|
+
referencedEntities,
|
|
5995
|
+
retrievalCount,
|
|
5996
|
+
chainPosition,
|
|
5997
|
+
reviewStatus,
|
|
5998
|
+
contextWindowPct,
|
|
5999
|
+
filePaths,
|
|
6000
|
+
commitHash,
|
|
6001
|
+
durationMs,
|
|
6002
|
+
tokenCost,
|
|
6003
|
+
audience,
|
|
6004
|
+
languageType,
|
|
6005
|
+
parentMemoryId
|
|
6006
|
+
];
|
|
6007
|
+
const baseArgs = [
|
|
6008
|
+
row.id,
|
|
6009
|
+
row.agent_id,
|
|
6010
|
+
row.agent_role,
|
|
6011
|
+
row.session_id,
|
|
6012
|
+
row.timestamp,
|
|
6013
|
+
row.tool_name,
|
|
6014
|
+
row.project_name,
|
|
6015
|
+
row.has_error,
|
|
6016
|
+
row.raw_text
|
|
6017
|
+
];
|
|
6018
|
+
const sharedArgs = [
|
|
6019
|
+
row.version,
|
|
6020
|
+
taskId,
|
|
6021
|
+
importance,
|
|
6022
|
+
status,
|
|
6023
|
+
confidence,
|
|
6024
|
+
lastAccessed,
|
|
6025
|
+
workspaceId,
|
|
6026
|
+
documentId,
|
|
6027
|
+
userId,
|
|
6028
|
+
charOffset,
|
|
6029
|
+
pageNumber,
|
|
6030
|
+
sourcePath,
|
|
6031
|
+
sourceType,
|
|
6032
|
+
tier,
|
|
6033
|
+
supersedesId,
|
|
6034
|
+
draft,
|
|
6035
|
+
memoryType,
|
|
6036
|
+
trajectory,
|
|
6037
|
+
contentHash
|
|
6038
|
+
];
|
|
6039
|
+
return {
|
|
6040
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
6041
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
6042
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6043
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
5247
6044
|
};
|
|
5248
6045
|
};
|
|
5249
6046
|
const globalClient = getClient();
|
|
@@ -5950,494 +6747,149 @@ async function createTimelineActivity(personId, params) {
|
|
|
5950
6747
|
agentResponse: params.agentResponse || null,
|
|
5951
6748
|
agentName: params.agentName || null,
|
|
5952
6749
|
threadId: `${params.platform}:${params.senderId}`,
|
|
5953
|
-
direction: params.direction,
|
|
5954
|
-
accountId: params.accountId || null
|
|
5955
|
-
},
|
|
5956
|
-
targetPersonId: personId
|
|
5957
|
-
};
|
|
5958
|
-
const query = `
|
|
5959
|
-
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
5960
|
-
createTimelineActivity(data: $input) {
|
|
5961
|
-
id
|
|
5962
|
-
}
|
|
5963
|
-
}
|
|
5964
|
-
`;
|
|
5965
|
-
const res = await gqlRequest(
|
|
5966
|
-
query,
|
|
5967
|
-
{ input }
|
|
5968
|
-
);
|
|
5969
|
-
if (res.errors && res.errors.length > 0) {
|
|
5970
|
-
console.error(
|
|
5971
|
-
"[crm-bridge] createTimelineActivity error:",
|
|
5972
|
-
res.errors[0].message
|
|
5973
|
-
);
|
|
5974
|
-
return false;
|
|
5975
|
-
}
|
|
5976
|
-
return true;
|
|
5977
|
-
}
|
|
5978
|
-
async function pushConversationToCRM(params) {
|
|
5979
|
-
if (!config) return;
|
|
5980
|
-
try {
|
|
5981
|
-
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
5982
|
-
if (!personId) {
|
|
5983
|
-
personId = await createPerson(
|
|
5984
|
-
params.platform,
|
|
5985
|
-
params.senderId,
|
|
5986
|
-
params.senderName
|
|
5987
|
-
);
|
|
5988
|
-
}
|
|
5989
|
-
if (!personId) {
|
|
5990
|
-
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
5991
|
-
return;
|
|
5992
|
-
}
|
|
5993
|
-
const ok = await createTimelineActivity(personId, {
|
|
5994
|
-
...params,
|
|
5995
|
-
direction: "conversation"
|
|
5996
|
-
});
|
|
5997
|
-
if (ok) {
|
|
5998
|
-
console.log(
|
|
5999
|
-
`[crm-bridge] Pushed ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6000
|
-
);
|
|
6001
|
-
}
|
|
6002
|
-
} catch (err) {
|
|
6003
|
-
console.error("[crm-bridge] Push failed:", err instanceof Error ? err.message : err);
|
|
6004
|
-
}
|
|
6005
|
-
}
|
|
6006
|
-
async function pushInboundMessageToCRM(params) {
|
|
6007
|
-
if (!config) return;
|
|
6008
|
-
try {
|
|
6009
|
-
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6010
|
-
if (!personId) {
|
|
6011
|
-
personId = await createPerson(
|
|
6012
|
-
params.platform,
|
|
6013
|
-
params.senderId,
|
|
6014
|
-
params.senderName
|
|
6015
|
-
);
|
|
6016
|
-
}
|
|
6017
|
-
if (!personId) {
|
|
6018
|
-
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6019
|
-
return;
|
|
6020
|
-
}
|
|
6021
|
-
const ok = await createTimelineActivity(personId, {
|
|
6022
|
-
...params,
|
|
6023
|
-
direction: "inbound"
|
|
6024
|
-
});
|
|
6025
|
-
if (ok) {
|
|
6026
|
-
console.log(
|
|
6027
|
-
`[crm-bridge] Inbound ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6028
|
-
);
|
|
6029
|
-
}
|
|
6030
|
-
} catch (err) {
|
|
6031
|
-
console.error("[crm-bridge] Inbound push failed:", err instanceof Error ? err.message : err);
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6034
|
-
async function pushGatewayEventToCRM(params) {
|
|
6035
|
-
if (!config) return;
|
|
6036
|
-
try {
|
|
6037
|
-
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6038
|
-
if (!personId) {
|
|
6039
|
-
personId = await createPerson(
|
|
6040
|
-
params.platform,
|
|
6041
|
-
params.senderId,
|
|
6042
|
-
params.senderName
|
|
6043
|
-
);
|
|
6044
|
-
}
|
|
6045
|
-
if (!personId) {
|
|
6046
|
-
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6047
|
-
return;
|
|
6048
|
-
}
|
|
6049
|
-
const channelLabel = params.platform.charAt(0).toUpperCase() + params.platform.slice(1);
|
|
6050
|
-
const categoryLabel = params.dataCategory.replace(/_/g, " ");
|
|
6051
|
-
const input = {
|
|
6052
|
-
name: `${channelLabel} ${categoryLabel} \u2014 ${params.senderName || params.senderId}`,
|
|
6053
|
-
happensAt: params.timestamp,
|
|
6054
|
-
properties: {
|
|
6055
|
-
channel: params.platform,
|
|
6056
|
-
dataCategory: params.dataCategory,
|
|
6057
|
-
senderId: params.senderId,
|
|
6058
|
-
senderName: params.senderName || null,
|
|
6059
|
-
accountId: params.accountId || null,
|
|
6060
|
-
direction: "inbound",
|
|
6061
|
-
...params.eventData
|
|
6062
|
-
},
|
|
6063
|
-
targetPersonId: personId
|
|
6064
|
-
};
|
|
6065
|
-
const query = `
|
|
6066
|
-
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
6067
|
-
createTimelineActivity(data: $input) {
|
|
6068
|
-
id
|
|
6069
|
-
}
|
|
6070
|
-
}
|
|
6071
|
-
`;
|
|
6072
|
-
const res = await gqlRequest(
|
|
6073
|
-
query,
|
|
6074
|
-
{ input }
|
|
6075
|
-
);
|
|
6076
|
-
if (res.errors && res.errors.length > 0) {
|
|
6077
|
-
console.error(
|
|
6078
|
-
"[crm-bridge] pushGatewayEvent error:",
|
|
6079
|
-
res.errors[0].message
|
|
6080
|
-
);
|
|
6081
|
-
return;
|
|
6082
|
-
}
|
|
6083
|
-
console.log(
|
|
6084
|
-
`[crm-bridge] Event ${params.dataCategory} ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6085
|
-
);
|
|
6086
|
-
} catch (err) {
|
|
6087
|
-
console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
|
-
var config, disabledLogged;
|
|
6091
|
-
var init_crm_bridge = __esm({
|
|
6092
|
-
"src/gateway/crm-bridge.ts"() {
|
|
6093
|
-
"use strict";
|
|
6094
|
-
config = null;
|
|
6095
|
-
disabledLogged = false;
|
|
6096
|
-
}
|
|
6097
|
-
});
|
|
6098
|
-
|
|
6099
|
-
// src/lib/exe-daemon-client.ts
|
|
6100
|
-
import net from "net";
|
|
6101
|
-
import { spawn } from "child_process";
|
|
6102
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
6103
|
-
import { existsSync as existsSync13, unlinkSync as unlinkSync6, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
|
|
6104
|
-
import path17 from "path";
|
|
6105
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6106
|
-
function handleData(chunk) {
|
|
6107
|
-
_buffer += chunk.toString();
|
|
6108
|
-
if (_buffer.length > MAX_BUFFER) {
|
|
6109
|
-
_buffer = "";
|
|
6110
|
-
return;
|
|
6111
|
-
}
|
|
6112
|
-
let newlineIdx;
|
|
6113
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
6114
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
6115
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
6116
|
-
if (!line) continue;
|
|
6117
|
-
try {
|
|
6118
|
-
const response = JSON.parse(line);
|
|
6119
|
-
const entry = _pending.get(response.id);
|
|
6120
|
-
if (entry) {
|
|
6121
|
-
clearTimeout(entry.timer);
|
|
6122
|
-
_pending.delete(response.id);
|
|
6123
|
-
entry.resolve(response);
|
|
6124
|
-
}
|
|
6125
|
-
} catch {
|
|
6126
|
-
}
|
|
6127
|
-
}
|
|
6128
|
-
}
|
|
6129
|
-
function cleanupStaleFiles() {
|
|
6130
|
-
if (existsSync13(PID_PATH)) {
|
|
6131
|
-
try {
|
|
6132
|
-
const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
|
|
6133
|
-
if (pid > 0) {
|
|
6134
|
-
try {
|
|
6135
|
-
process.kill(pid, 0);
|
|
6136
|
-
return;
|
|
6137
|
-
} catch {
|
|
6138
|
-
}
|
|
6139
|
-
}
|
|
6140
|
-
} catch {
|
|
6141
|
-
}
|
|
6142
|
-
try {
|
|
6143
|
-
unlinkSync6(PID_PATH);
|
|
6144
|
-
} catch {
|
|
6145
|
-
}
|
|
6146
|
-
try {
|
|
6147
|
-
unlinkSync6(SOCKET_PATH);
|
|
6148
|
-
} catch {
|
|
6149
|
-
}
|
|
6150
|
-
}
|
|
6151
|
-
}
|
|
6152
|
-
function findPackageRoot() {
|
|
6153
|
-
let dir = path17.dirname(fileURLToPath2(import.meta.url));
|
|
6154
|
-
const { root } = path17.parse(dir);
|
|
6155
|
-
while (dir !== root) {
|
|
6156
|
-
if (existsSync13(path17.join(dir, "package.json"))) return dir;
|
|
6157
|
-
dir = path17.dirname(dir);
|
|
6158
|
-
}
|
|
6159
|
-
return null;
|
|
6160
|
-
}
|
|
6161
|
-
function spawnDaemon() {
|
|
6162
|
-
const pkgRoot = findPackageRoot();
|
|
6163
|
-
if (!pkgRoot) {
|
|
6164
|
-
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
6165
|
-
return;
|
|
6166
|
-
}
|
|
6167
|
-
const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
6168
|
-
if (!existsSync13(daemonPath)) {
|
|
6169
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
6170
|
-
`);
|
|
6171
|
-
return;
|
|
6172
|
-
}
|
|
6173
|
-
const resolvedPath = daemonPath;
|
|
6174
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
6175
|
-
`);
|
|
6176
|
-
const logPath = path17.join(path17.dirname(SOCKET_PATH), "exed.log");
|
|
6177
|
-
let stderrFd = "ignore";
|
|
6178
|
-
try {
|
|
6179
|
-
stderrFd = openSync(logPath, "a");
|
|
6180
|
-
} catch {
|
|
6181
|
-
}
|
|
6182
|
-
const child = spawn(process.execPath, [resolvedPath], {
|
|
6183
|
-
detached: true,
|
|
6184
|
-
stdio: ["ignore", "ignore", stderrFd],
|
|
6185
|
-
env: {
|
|
6186
|
-
...process.env,
|
|
6187
|
-
TMUX: void 0,
|
|
6188
|
-
// Daemon is global — must not inherit session scope
|
|
6189
|
-
TMUX_PANE: void 0,
|
|
6190
|
-
// Prevents resolveExeSession() from scoping to one session
|
|
6191
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
6192
|
-
EXE_DAEMON_PID: PID_PATH
|
|
6193
|
-
}
|
|
6194
|
-
});
|
|
6195
|
-
child.unref();
|
|
6196
|
-
if (typeof stderrFd === "number") {
|
|
6197
|
-
try {
|
|
6198
|
-
closeSync(stderrFd);
|
|
6199
|
-
} catch {
|
|
6200
|
-
}
|
|
6201
|
-
}
|
|
6202
|
-
}
|
|
6203
|
-
function acquireSpawnLock2() {
|
|
6204
|
-
try {
|
|
6205
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
6206
|
-
closeSync(fd);
|
|
6207
|
-
return true;
|
|
6208
|
-
} catch {
|
|
6209
|
-
try {
|
|
6210
|
-
const stat = statSync(SPAWN_LOCK_PATH);
|
|
6211
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
6212
|
-
try {
|
|
6213
|
-
unlinkSync6(SPAWN_LOCK_PATH);
|
|
6214
|
-
} catch {
|
|
6215
|
-
}
|
|
6216
|
-
try {
|
|
6217
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
6218
|
-
closeSync(fd);
|
|
6219
|
-
return true;
|
|
6220
|
-
} catch {
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6223
|
-
} catch {
|
|
6224
|
-
}
|
|
6225
|
-
return false;
|
|
6226
|
-
}
|
|
6227
|
-
}
|
|
6228
|
-
function releaseSpawnLock2() {
|
|
6229
|
-
try {
|
|
6230
|
-
unlinkSync6(SPAWN_LOCK_PATH);
|
|
6231
|
-
} catch {
|
|
6232
|
-
}
|
|
6233
|
-
}
|
|
6234
|
-
function connectToSocket() {
|
|
6235
|
-
return new Promise((resolve) => {
|
|
6236
|
-
if (_socket && _connected) {
|
|
6237
|
-
resolve(true);
|
|
6238
|
-
return;
|
|
6239
|
-
}
|
|
6240
|
-
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
6241
|
-
const connectTimeout = setTimeout(() => {
|
|
6242
|
-
socket.destroy();
|
|
6243
|
-
resolve(false);
|
|
6244
|
-
}, 2e3);
|
|
6245
|
-
socket.on("connect", () => {
|
|
6246
|
-
clearTimeout(connectTimeout);
|
|
6247
|
-
_socket = socket;
|
|
6248
|
-
_connected = true;
|
|
6249
|
-
_buffer = "";
|
|
6250
|
-
socket.on("data", handleData);
|
|
6251
|
-
socket.on("close", () => {
|
|
6252
|
-
_connected = false;
|
|
6253
|
-
_socket = null;
|
|
6254
|
-
for (const [id, entry] of _pending) {
|
|
6255
|
-
clearTimeout(entry.timer);
|
|
6256
|
-
_pending.delete(id);
|
|
6257
|
-
entry.resolve({ error: "Connection closed" });
|
|
6258
|
-
}
|
|
6259
|
-
});
|
|
6260
|
-
socket.on("error", () => {
|
|
6261
|
-
_connected = false;
|
|
6262
|
-
_socket = null;
|
|
6263
|
-
});
|
|
6264
|
-
resolve(true);
|
|
6265
|
-
});
|
|
6266
|
-
socket.on("error", () => {
|
|
6267
|
-
clearTimeout(connectTimeout);
|
|
6268
|
-
resolve(false);
|
|
6269
|
-
});
|
|
6270
|
-
});
|
|
6271
|
-
}
|
|
6272
|
-
async function connectEmbedDaemon() {
|
|
6273
|
-
if (_socket && _connected) return true;
|
|
6274
|
-
if (await connectToSocket()) return true;
|
|
6275
|
-
if (acquireSpawnLock2()) {
|
|
6276
|
-
try {
|
|
6277
|
-
cleanupStaleFiles();
|
|
6278
|
-
spawnDaemon();
|
|
6279
|
-
} finally {
|
|
6280
|
-
releaseSpawnLock2();
|
|
6281
|
-
}
|
|
6282
|
-
}
|
|
6283
|
-
const start = Date.now();
|
|
6284
|
-
let delay2 = 100;
|
|
6285
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6286
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
6287
|
-
if (await connectToSocket()) return true;
|
|
6288
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
6289
|
-
}
|
|
6290
|
-
return false;
|
|
6291
|
-
}
|
|
6292
|
-
function sendRequest(texts, priority) {
|
|
6293
|
-
return new Promise((resolve) => {
|
|
6294
|
-
if (!_socket || !_connected) {
|
|
6295
|
-
resolve({ error: "Not connected" });
|
|
6296
|
-
return;
|
|
6297
|
-
}
|
|
6298
|
-
const id = randomUUID6();
|
|
6299
|
-
const timer = setTimeout(() => {
|
|
6300
|
-
_pending.delete(id);
|
|
6301
|
-
resolve({ error: "Request timeout" });
|
|
6302
|
-
}, REQUEST_TIMEOUT_MS);
|
|
6303
|
-
_pending.set(id, { resolve, timer });
|
|
6304
|
-
try {
|
|
6305
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
6306
|
-
} catch {
|
|
6307
|
-
clearTimeout(timer);
|
|
6308
|
-
_pending.delete(id);
|
|
6309
|
-
resolve({ error: "Write failed" });
|
|
6310
|
-
}
|
|
6311
|
-
});
|
|
6312
|
-
}
|
|
6313
|
-
async function pingDaemon() {
|
|
6314
|
-
if (!_socket || !_connected) return null;
|
|
6315
|
-
return new Promise((resolve) => {
|
|
6316
|
-
const id = randomUUID6();
|
|
6317
|
-
const timer = setTimeout(() => {
|
|
6318
|
-
_pending.delete(id);
|
|
6319
|
-
resolve(null);
|
|
6320
|
-
}, 5e3);
|
|
6321
|
-
_pending.set(id, {
|
|
6322
|
-
resolve: (data) => {
|
|
6323
|
-
if (data.health) {
|
|
6324
|
-
resolve(data.health);
|
|
6325
|
-
} else {
|
|
6326
|
-
resolve(null);
|
|
6327
|
-
}
|
|
6328
|
-
},
|
|
6329
|
-
timer
|
|
6330
|
-
});
|
|
6331
|
-
try {
|
|
6332
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
6333
|
-
} catch {
|
|
6334
|
-
clearTimeout(timer);
|
|
6335
|
-
_pending.delete(id);
|
|
6336
|
-
resolve(null);
|
|
6337
|
-
}
|
|
6338
|
-
});
|
|
6339
|
-
}
|
|
6340
|
-
function killAndRespawnDaemon() {
|
|
6341
|
-
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
6342
|
-
if (existsSync13(PID_PATH)) {
|
|
6343
|
-
try {
|
|
6344
|
-
const pid = parseInt(readFileSync10(PID_PATH, "utf8").trim(), 10);
|
|
6345
|
-
if (pid > 0) {
|
|
6346
|
-
try {
|
|
6347
|
-
process.kill(pid, "SIGKILL");
|
|
6348
|
-
} catch {
|
|
6349
|
-
}
|
|
6750
|
+
direction: params.direction,
|
|
6751
|
+
accountId: params.accountId || null
|
|
6752
|
+
},
|
|
6753
|
+
targetPersonId: personId
|
|
6754
|
+
};
|
|
6755
|
+
const query = `
|
|
6756
|
+
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
6757
|
+
createTimelineActivity(data: $input) {
|
|
6758
|
+
id
|
|
6350
6759
|
}
|
|
6351
|
-
} catch {
|
|
6352
6760
|
}
|
|
6761
|
+
`;
|
|
6762
|
+
const res = await gqlRequest(
|
|
6763
|
+
query,
|
|
6764
|
+
{ input }
|
|
6765
|
+
);
|
|
6766
|
+
if (res.errors && res.errors.length > 0) {
|
|
6767
|
+
console.error(
|
|
6768
|
+
"[crm-bridge] createTimelineActivity error:",
|
|
6769
|
+
res.errors[0].message
|
|
6770
|
+
);
|
|
6771
|
+
return false;
|
|
6353
6772
|
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
_connected = false;
|
|
6359
|
-
_buffer = "";
|
|
6360
|
-
try {
|
|
6361
|
-
unlinkSync6(PID_PATH);
|
|
6362
|
-
} catch {
|
|
6363
|
-
}
|
|
6773
|
+
return true;
|
|
6774
|
+
}
|
|
6775
|
+
async function pushConversationToCRM(params) {
|
|
6776
|
+
if (!config) return;
|
|
6364
6777
|
try {
|
|
6365
|
-
|
|
6366
|
-
|
|
6778
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6779
|
+
if (!personId) {
|
|
6780
|
+
personId = await createPerson(
|
|
6781
|
+
params.platform,
|
|
6782
|
+
params.senderId,
|
|
6783
|
+
params.senderName
|
|
6784
|
+
);
|
|
6785
|
+
}
|
|
6786
|
+
if (!personId) {
|
|
6787
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6788
|
+
return;
|
|
6789
|
+
}
|
|
6790
|
+
const ok = await createTimelineActivity(personId, {
|
|
6791
|
+
...params,
|
|
6792
|
+
direction: "conversation"
|
|
6793
|
+
});
|
|
6794
|
+
if (ok) {
|
|
6795
|
+
console.log(
|
|
6796
|
+
`[crm-bridge] Pushed ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6797
|
+
);
|
|
6798
|
+
}
|
|
6799
|
+
} catch (err) {
|
|
6800
|
+
console.error("[crm-bridge] Push failed:", err instanceof Error ? err.message : err);
|
|
6367
6801
|
}
|
|
6368
|
-
spawnDaemon();
|
|
6369
6802
|
}
|
|
6370
|
-
async function
|
|
6371
|
-
if (!
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
let delay2 = 200;
|
|
6381
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6382
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
6383
|
-
if (await connectToSocket()) break;
|
|
6384
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
6385
|
-
}
|
|
6386
|
-
if (!_connected) return null;
|
|
6803
|
+
async function pushInboundMessageToCRM(params) {
|
|
6804
|
+
if (!config) return;
|
|
6805
|
+
try {
|
|
6806
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6807
|
+
if (!personId) {
|
|
6808
|
+
personId = await createPerson(
|
|
6809
|
+
params.platform,
|
|
6810
|
+
params.senderId,
|
|
6811
|
+
params.senderName
|
|
6812
|
+
);
|
|
6387
6813
|
}
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
if (result.error) {
|
|
6392
|
-
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
6393
|
-
`);
|
|
6394
|
-
killAndRespawnDaemon();
|
|
6395
|
-
const start = Date.now();
|
|
6396
|
-
let delay2 = 200;
|
|
6397
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
6398
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
6399
|
-
if (await connectToSocket()) break;
|
|
6400
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
6814
|
+
if (!personId) {
|
|
6815
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6816
|
+
return;
|
|
6401
6817
|
}
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6818
|
+
const ok = await createTimelineActivity(personId, {
|
|
6819
|
+
...params,
|
|
6820
|
+
direction: "inbound"
|
|
6821
|
+
});
|
|
6822
|
+
if (ok) {
|
|
6823
|
+
console.log(
|
|
6824
|
+
`[crm-bridge] Inbound ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6825
|
+
);
|
|
6826
|
+
}
|
|
6827
|
+
} catch (err) {
|
|
6828
|
+
console.error("[crm-bridge] Inbound push failed:", err instanceof Error ? err.message : err);
|
|
6407
6829
|
}
|
|
6408
|
-
return null;
|
|
6409
6830
|
}
|
|
6410
|
-
function
|
|
6411
|
-
if (
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6831
|
+
async function pushGatewayEventToCRM(params) {
|
|
6832
|
+
if (!config) return;
|
|
6833
|
+
try {
|
|
6834
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6835
|
+
if (!personId) {
|
|
6836
|
+
personId = await createPerson(
|
|
6837
|
+
params.platform,
|
|
6838
|
+
params.senderId,
|
|
6839
|
+
params.senderName
|
|
6840
|
+
);
|
|
6841
|
+
}
|
|
6842
|
+
if (!personId) {
|
|
6843
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6844
|
+
return;
|
|
6845
|
+
}
|
|
6846
|
+
const channelLabel = params.platform.charAt(0).toUpperCase() + params.platform.slice(1);
|
|
6847
|
+
const categoryLabel = params.dataCategory.replace(/_/g, " ");
|
|
6848
|
+
const input = {
|
|
6849
|
+
name: `${channelLabel} ${categoryLabel} \u2014 ${params.senderName || params.senderId}`,
|
|
6850
|
+
happensAt: params.timestamp,
|
|
6851
|
+
properties: {
|
|
6852
|
+
channel: params.platform,
|
|
6853
|
+
dataCategory: params.dataCategory,
|
|
6854
|
+
senderId: params.senderId,
|
|
6855
|
+
senderName: params.senderName || null,
|
|
6856
|
+
accountId: params.accountId || null,
|
|
6857
|
+
direction: "inbound",
|
|
6858
|
+
...params.eventData
|
|
6859
|
+
},
|
|
6860
|
+
targetPersonId: personId
|
|
6861
|
+
};
|
|
6862
|
+
const query = `
|
|
6863
|
+
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
6864
|
+
createTimelineActivity(data: $input) {
|
|
6865
|
+
id
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
`;
|
|
6869
|
+
const res = await gqlRequest(
|
|
6870
|
+
query,
|
|
6871
|
+
{ input }
|
|
6872
|
+
);
|
|
6873
|
+
if (res.errors && res.errors.length > 0) {
|
|
6874
|
+
console.error(
|
|
6875
|
+
"[crm-bridge] pushGatewayEvent error:",
|
|
6876
|
+
res.errors[0].message
|
|
6877
|
+
);
|
|
6878
|
+
return;
|
|
6879
|
+
}
|
|
6880
|
+
console.log(
|
|
6881
|
+
`[crm-bridge] Event ${params.dataCategory} ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6882
|
+
);
|
|
6883
|
+
} catch (err) {
|
|
6884
|
+
console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
|
|
6421
6885
|
}
|
|
6422
6886
|
}
|
|
6423
|
-
var
|
|
6424
|
-
var
|
|
6425
|
-
"src/
|
|
6887
|
+
var config, disabledLogged;
|
|
6888
|
+
var init_crm_bridge = __esm({
|
|
6889
|
+
"src/gateway/crm-bridge.ts"() {
|
|
6426
6890
|
"use strict";
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path17.join(EXE_AI_DIR, "exed.pid");
|
|
6430
|
-
SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
6431
|
-
SPAWN_LOCK_STALE_MS = 3e4;
|
|
6432
|
-
CONNECT_TIMEOUT_MS = 15e3;
|
|
6433
|
-
REQUEST_TIMEOUT_MS = 3e4;
|
|
6434
|
-
_socket = null;
|
|
6435
|
-
_connected = false;
|
|
6436
|
-
_buffer = "";
|
|
6437
|
-
_requestCount = 0;
|
|
6438
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
6439
|
-
_pending = /* @__PURE__ */ new Map();
|
|
6440
|
-
MAX_BUFFER = 1e7;
|
|
6891
|
+
config = null;
|
|
6892
|
+
disabledLogged = false;
|
|
6441
6893
|
}
|
|
6442
6894
|
});
|
|
6443
6895
|
|
|
@@ -6646,7 +7098,7 @@ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
|
6646
7098
|
var init_wiki_client = __esm({
|
|
6647
7099
|
"src/lib/wiki-client.ts"() {
|
|
6648
7100
|
"use strict";
|
|
6649
|
-
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
7101
|
+
LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
|
|
6650
7102
|
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
6651
7103
|
}
|
|
6652
7104
|
});
|
|
@@ -9554,11 +10006,11 @@ function createQuietRenderer() {
|
|
|
9554
10006
|
init_state_bus();
|
|
9555
10007
|
|
|
9556
10008
|
// src/runtime/session-manager.ts
|
|
9557
|
-
import { randomUUID as
|
|
10009
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
9558
10010
|
init_state_bus();
|
|
9559
10011
|
|
|
9560
10012
|
// src/runtime/exe-hooks.ts
|
|
9561
|
-
import { randomUUID as
|
|
10013
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
9562
10014
|
function createExeOSHooks(config2) {
|
|
9563
10015
|
let sessionRegistered = false;
|
|
9564
10016
|
return {
|
|
@@ -9617,7 +10069,7 @@ function createExeOSHooks(config2) {
|
|
|
9617
10069
|
const toolResponse = result.isError ? { error: result.content } : { output: result.content };
|
|
9618
10070
|
const rawText = extractSemanticText2(toolName, toolInput, toolResponse);
|
|
9619
10071
|
await writeMemory2({
|
|
9620
|
-
id:
|
|
10072
|
+
id: randomUUID5(),
|
|
9621
10073
|
agent_id: config2.agentId,
|
|
9622
10074
|
agent_role: "employee",
|
|
9623
10075
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9640,7 +10092,7 @@ function createExeOSHooks(config2) {
|
|
|
9640
10092
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9641
10093
|
if (summary) {
|
|
9642
10094
|
await writeMemory2({
|
|
9643
|
-
id:
|
|
10095
|
+
id: randomUUID5(),
|
|
9644
10096
|
agent_id: config2.agentId,
|
|
9645
10097
|
agent_role: "employee",
|
|
9646
10098
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9668,8 +10120,8 @@ function createExeOSHooks(config2) {
|
|
|
9668
10120
|
},
|
|
9669
10121
|
async onError(error, _context) {
|
|
9670
10122
|
try {
|
|
9671
|
-
const { classifyError:
|
|
9672
|
-
const classification =
|
|
10123
|
+
const { classifyError: classifyError3 } = await Promise.resolve().then(() => (init_error_detector(), error_detector_exports));
|
|
10124
|
+
const classification = classifyError3(error.message);
|
|
9673
10125
|
process.stderr.write(
|
|
9674
10126
|
`[exe-hooks] Error (${classification}): ${error.message.slice(0, 100)}
|
|
9675
10127
|
`
|
|
@@ -9713,7 +10165,7 @@ function createExeOSHooks(config2) {
|
|
|
9713
10165
|
await client.execute({
|
|
9714
10166
|
sql: `INSERT OR IGNORE INTO notifications (id, type, source_agent, message, task_file, created_at, read)
|
|
9715
10167
|
VALUES (?, 'system', ?, ?, NULL, ?, 0)`,
|
|
9716
|
-
args: [
|
|
10168
|
+
args: [randomUUID5(), config2.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
|
|
9717
10169
|
});
|
|
9718
10170
|
} catch {
|
|
9719
10171
|
}
|
|
@@ -9729,7 +10181,7 @@ function createExeOSHooks(config2) {
|
|
|
9729
10181
|
try {
|
|
9730
10182
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9731
10183
|
await writeMemory2({
|
|
9732
|
-
id:
|
|
10184
|
+
id: randomUUID5(),
|
|
9733
10185
|
agent_id: config2.agentId,
|
|
9734
10186
|
agent_role: "employee",
|
|
9735
10187
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9751,7 +10203,7 @@ function createExeOSHooks(config2) {
|
|
|
9751
10203
|
try {
|
|
9752
10204
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9753
10205
|
await writeMemory2({
|
|
9754
|
-
id:
|
|
10206
|
+
id: randomUUID5(),
|
|
9755
10207
|
agent_id: config2.agentId,
|
|
9756
10208
|
agent_role: "employee",
|
|
9757
10209
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9790,7 +10242,7 @@ function createExeOSHooks(config2) {
|
|
|
9790
10242
|
if (tasks.rows.length > 0) {
|
|
9791
10243
|
const taskList = tasks.rows.map((r) => `- [${String(r.status)}] ${String(r.title)} (${String(r.task_file)})`).join("\n");
|
|
9792
10244
|
await writeMemory2({
|
|
9793
|
-
id:
|
|
10245
|
+
id: randomUUID5(),
|
|
9794
10246
|
agent_id: config2.agentId,
|
|
9795
10247
|
agent_role: "employee",
|
|
9796
10248
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9816,7 +10268,7 @@ ${taskList}`,
|
|
|
9816
10268
|
try {
|
|
9817
10269
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9818
10270
|
await writeMemory2({
|
|
9819
|
-
id:
|
|
10271
|
+
id: randomUUID5(),
|
|
9820
10272
|
agent_id: config2.agentId,
|
|
9821
10273
|
agent_role: "employee",
|
|
9822
10274
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9843,7 +10295,7 @@ var SessionManager = class {
|
|
|
9843
10295
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
9844
10296
|
/** Start a new agent session for an employee */
|
|
9845
10297
|
startSession(employeeId, config2) {
|
|
9846
|
-
const sessionId =
|
|
10298
|
+
const sessionId = randomUUID6();
|
|
9847
10299
|
const abortController = new AbortController();
|
|
9848
10300
|
const session = {
|
|
9849
10301
|
info: {
|
|
@@ -10096,13 +10548,16 @@ __export(gateway_exports, {
|
|
|
10096
10548
|
FULL_ACCESS: () => FULL_ACCESS,
|
|
10097
10549
|
FailoverCascade: () => FailoverCascade,
|
|
10098
10550
|
FailoverExhaustedError: () => FailoverExhaustedError,
|
|
10551
|
+
FatalBotError: () => FatalBotError,
|
|
10099
10552
|
Gateway: () => Gateway,
|
|
10100
10553
|
IMessageAdapter: () => IMessageAdapter,
|
|
10554
|
+
MaxStepsError: () => MaxStepsError,
|
|
10101
10555
|
OllamaProvider: () => OllamaProvider,
|
|
10102
10556
|
OpenAICompatProvider: () => OpenAICompatProvider,
|
|
10103
10557
|
READ_ONLY: () => READ_ONLY,
|
|
10104
10558
|
READ_TOOLS: () => READ_TOOLS,
|
|
10105
10559
|
RateLimiter: () => RateLimiter,
|
|
10560
|
+
RecoverableBotError: () => RecoverableBotError,
|
|
10106
10561
|
SessionStore: () => SessionStore,
|
|
10107
10562
|
SignalAdapter: () => SignalAdapter,
|
|
10108
10563
|
SlackAdapter: () => SlackAdapter,
|
|
@@ -10114,6 +10569,7 @@ __export(gateway_exports, {
|
|
|
10114
10569
|
buildExecAssistantTools: () => buildExecAssistantTools,
|
|
10115
10570
|
buildPermissionContext: () => buildPermissionContext,
|
|
10116
10571
|
checkToolPermission: () => checkToolPermission,
|
|
10572
|
+
classifyError: () => classifyError2,
|
|
10117
10573
|
createCRMWebhookHandler: () => createCRMWebhookHandler,
|
|
10118
10574
|
createPerson: () => createPerson,
|
|
10119
10575
|
createReceptionist: () => createReceptionist,
|
|
@@ -10789,10 +11245,49 @@ function buildPermissionContext(platform, permissions) {
|
|
|
10789
11245
|
return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
|
|
10790
11246
|
}
|
|
10791
11247
|
|
|
11248
|
+
// src/gateway/bot-errors.ts
|
|
11249
|
+
var FatalBotError = class extends Error {
|
|
11250
|
+
constructor(message, cause) {
|
|
11251
|
+
super(message);
|
|
11252
|
+
this.cause = cause;
|
|
11253
|
+
this.name = "FatalBotError";
|
|
11254
|
+
}
|
|
11255
|
+
fatal = true;
|
|
11256
|
+
};
|
|
11257
|
+
var RecoverableBotError = class extends Error {
|
|
11258
|
+
constructor(message, toolName, cause) {
|
|
11259
|
+
super(message);
|
|
11260
|
+
this.toolName = toolName;
|
|
11261
|
+
this.cause = cause;
|
|
11262
|
+
this.name = "RecoverableBotError";
|
|
11263
|
+
}
|
|
11264
|
+
recoverable = true;
|
|
11265
|
+
};
|
|
11266
|
+
var MaxStepsError = class extends Error {
|
|
11267
|
+
constructor(stepsTaken, maxSteps) {
|
|
11268
|
+
super(
|
|
11269
|
+
`Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
|
|
11270
|
+
);
|
|
11271
|
+
this.stepsTaken = stepsTaken;
|
|
11272
|
+
this.maxSteps = maxSteps;
|
|
11273
|
+
this.name = "MaxStepsError";
|
|
11274
|
+
}
|
|
11275
|
+
};
|
|
11276
|
+
function classifyError2(err, toolName) {
|
|
11277
|
+
if (err instanceof FatalBotError) return err;
|
|
11278
|
+
if (err instanceof RecoverableBotError) return err;
|
|
11279
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11280
|
+
if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
|
|
11281
|
+
return new FatalBotError(message, err);
|
|
11282
|
+
}
|
|
11283
|
+
return new RecoverableBotError(message, toolName, err);
|
|
11284
|
+
}
|
|
11285
|
+
|
|
10792
11286
|
// src/gateway/bot-runtime.ts
|
|
10793
11287
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
10794
11288
|
var MAX_TURNS = 10;
|
|
10795
11289
|
var MAX_HISTORY = 50;
|
|
11290
|
+
var DEFAULT_PLANNING_INTERVAL = 3;
|
|
10796
11291
|
function buildExecAssistantSystemPrompt(platform, permissions) {
|
|
10797
11292
|
const permContext = buildPermissionContext(platform, permissions);
|
|
10798
11293
|
return `You are the founder's executive assistant (agent_id: "ea").
|
|
@@ -10844,7 +11339,7 @@ var BotRuntime = class {
|
|
|
10844
11339
|
async processMessage(msg, permissions) {
|
|
10845
11340
|
const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
|
|
10846
11341
|
const history = this.getHistory(sessionKey);
|
|
10847
|
-
history.push({ role: "user", content: msg.text });
|
|
11342
|
+
history.push({ role: "user", content: msg.text, frameType: "task" });
|
|
10848
11343
|
const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
|
|
10849
11344
|
const allowedTools = filterToolsForPermissions(
|
|
10850
11345
|
this.config.tools,
|
|
@@ -10852,29 +11347,54 @@ var BotRuntime = class {
|
|
|
10852
11347
|
);
|
|
10853
11348
|
const model = this.config.model ?? DEFAULT_MODEL;
|
|
10854
11349
|
const maxTurns = this.config.maxTurns ?? MAX_TURNS;
|
|
11350
|
+
const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
|
|
10855
11351
|
let turns = 0;
|
|
10856
11352
|
while (turns < maxTurns) {
|
|
10857
11353
|
turns++;
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
11354
|
+
if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
|
|
11355
|
+
history.push({
|
|
11356
|
+
role: "user",
|
|
11357
|
+
content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
|
|
11358
|
+
frameType: "planning"
|
|
11359
|
+
});
|
|
11360
|
+
}
|
|
11361
|
+
let response;
|
|
11362
|
+
try {
|
|
11363
|
+
response = await this.client.messages.create({
|
|
11364
|
+
model,
|
|
11365
|
+
max_tokens: 4096,
|
|
11366
|
+
system: systemPrompt,
|
|
11367
|
+
messages: history.map((m) => ({
|
|
11368
|
+
role: m.role,
|
|
11369
|
+
content: m.content
|
|
11370
|
+
})),
|
|
11371
|
+
tools: allowedTools.map((t) => ({
|
|
11372
|
+
name: t.name,
|
|
11373
|
+
description: t.description,
|
|
11374
|
+
input_schema: t.input_schema
|
|
11375
|
+
}))
|
|
11376
|
+
});
|
|
11377
|
+
} catch (err) {
|
|
11378
|
+
const classified = classifyError2(err);
|
|
11379
|
+
if (classified instanceof FatalBotError) {
|
|
11380
|
+
const errorMsg = `Bot error: ${classified.message}`;
|
|
11381
|
+
history.push({ role: "assistant", content: errorMsg, frameType: "error" });
|
|
11382
|
+
this.trimHistory(sessionKey);
|
|
11383
|
+
return errorMsg;
|
|
11384
|
+
}
|
|
11385
|
+
history.push({
|
|
11386
|
+
role: "assistant",
|
|
11387
|
+
content: `[API error \u2014 retrying] ${classified.message}`,
|
|
11388
|
+
frameType: "error"
|
|
11389
|
+
});
|
|
11390
|
+
continue;
|
|
11391
|
+
}
|
|
10872
11392
|
const toolUseBlocks = response.content.filter(
|
|
10873
11393
|
(b) => b.type === "tool_use"
|
|
10874
11394
|
);
|
|
10875
11395
|
if (toolUseBlocks.length === 0) {
|
|
10876
11396
|
const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
10877
|
-
history.push({ role: "assistant", content: textContent });
|
|
11397
|
+
history.push({ role: "assistant", content: textContent, frameType: "assistant" });
|
|
10878
11398
|
this.trimHistory(sessionKey);
|
|
10879
11399
|
return textContent;
|
|
10880
11400
|
}
|
|
@@ -10884,9 +11404,11 @@ var BotRuntime = class {
|
|
|
10884
11404
|
);
|
|
10885
11405
|
history.push({
|
|
10886
11406
|
role: "assistant",
|
|
10887
|
-
content: response.content
|
|
11407
|
+
content: response.content,
|
|
11408
|
+
frameType: "assistant"
|
|
10888
11409
|
});
|
|
10889
11410
|
const toolResults = [];
|
|
11411
|
+
let hadFatalError = false;
|
|
10890
11412
|
for (const block of allowed) {
|
|
10891
11413
|
try {
|
|
10892
11414
|
const result = await this.config.toolExecutor(
|
|
@@ -10899,12 +11421,23 @@ var BotRuntime = class {
|
|
|
10899
11421
|
content: result
|
|
10900
11422
|
});
|
|
10901
11423
|
} catch (err) {
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
11424
|
+
const classified = classifyError2(err, block.name);
|
|
11425
|
+
if (classified instanceof FatalBotError) {
|
|
11426
|
+
toolResults.push({
|
|
11427
|
+
type: "tool_result",
|
|
11428
|
+
tool_use_id: block.id,
|
|
11429
|
+
content: `Fatal error: ${classified.message}`,
|
|
11430
|
+
is_error: true
|
|
11431
|
+
});
|
|
11432
|
+
hadFatalError = true;
|
|
11433
|
+
} else {
|
|
11434
|
+
toolResults.push({
|
|
11435
|
+
type: "tool_result",
|
|
11436
|
+
tool_use_id: block.id,
|
|
11437
|
+
content: `Error (recoverable): ${classified.message}`,
|
|
11438
|
+
is_error: true
|
|
11439
|
+
});
|
|
11440
|
+
}
|
|
10908
11441
|
}
|
|
10909
11442
|
}
|
|
10910
11443
|
for (const { block, check } of blocked) {
|
|
@@ -10917,10 +11450,22 @@ var BotRuntime = class {
|
|
|
10917
11450
|
}
|
|
10918
11451
|
history.push({
|
|
10919
11452
|
role: "user",
|
|
10920
|
-
content: toolResults
|
|
11453
|
+
content: toolResults,
|
|
11454
|
+
frameType: hadFatalError ? "error" : "tool_result"
|
|
10921
11455
|
});
|
|
11456
|
+
if (hadFatalError) {
|
|
11457
|
+
this.trimHistory(sessionKey);
|
|
11458
|
+
return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
|
|
11459
|
+
}
|
|
10922
11460
|
}
|
|
10923
|
-
|
|
11461
|
+
const maxErr = new MaxStepsError(turns, maxTurns);
|
|
11462
|
+
history.push({
|
|
11463
|
+
role: "assistant",
|
|
11464
|
+
content: maxErr.message,
|
|
11465
|
+
frameType: "error"
|
|
11466
|
+
});
|
|
11467
|
+
this.trimHistory(sessionKey);
|
|
11468
|
+
return maxErr.message;
|
|
10924
11469
|
}
|
|
10925
11470
|
getHistory(sessionKey) {
|
|
10926
11471
|
if (!this.conversations.has(sessionKey)) {
|
|
@@ -10930,9 +11475,19 @@ var BotRuntime = class {
|
|
|
10930
11475
|
}
|
|
10931
11476
|
trimHistory(sessionKey) {
|
|
10932
11477
|
const history = this.conversations.get(sessionKey);
|
|
10933
|
-
if (history
|
|
10934
|
-
|
|
11478
|
+
if (!history || history.length <= MAX_HISTORY) return;
|
|
11479
|
+
const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
|
|
11480
|
+
let trimmed = history.filter(
|
|
11481
|
+
(m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
|
|
11482
|
+
);
|
|
11483
|
+
if (trimmed.length > MAX_HISTORY) {
|
|
11484
|
+
const tail = trimmed.slice(-MAX_HISTORY);
|
|
11485
|
+
if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
|
|
11486
|
+
tail[0] = history[firstTaskIdx];
|
|
11487
|
+
}
|
|
11488
|
+
trimmed = tail;
|
|
10935
11489
|
}
|
|
11490
|
+
this.conversations.set(sessionKey, trimmed);
|
|
10936
11491
|
}
|
|
10937
11492
|
/** Clear conversation history for a session */
|
|
10938
11493
|
clearHistory(sessionKey) {
|
|
@@ -11638,8 +12193,19 @@ import { randomUUID as randomUUID9 } from "crypto";
|
|
|
11638
12193
|
import { homedir } from "os";
|
|
11639
12194
|
import { join } from "path";
|
|
11640
12195
|
import { mkdirSync as mkdirSync7 } from "fs";
|
|
11641
|
-
var
|
|
12196
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
12197
|
+
var MAX_BACKOFF_MS = 3e5;
|
|
12198
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
12199
|
+
var JITTER_FACTOR = 0.25;
|
|
11642
12200
|
var AUTH_DIR = join(homedir(), ".exe-os", "whatsapp-auth");
|
|
12201
|
+
function calculateBackoff(retryCount) {
|
|
12202
|
+
const base = Math.min(
|
|
12203
|
+
INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
|
|
12204
|
+
MAX_BACKOFF_MS
|
|
12205
|
+
);
|
|
12206
|
+
const jitter = base * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
12207
|
+
return Math.max(INITIAL_BACKOFF_MS, Math.round(base + jitter));
|
|
12208
|
+
}
|
|
11643
12209
|
var WhatsAppAdapter = class {
|
|
11644
12210
|
platform = "whatsapp";
|
|
11645
12211
|
sock = null;
|
|
@@ -11647,6 +12213,9 @@ var WhatsAppAdapter = class {
|
|
|
11647
12213
|
connected = false;
|
|
11648
12214
|
abortController = null;
|
|
11649
12215
|
authDir = AUTH_DIR;
|
|
12216
|
+
// Resilience state
|
|
12217
|
+
retryCount = 0;
|
|
12218
|
+
disconnectedAt = 0;
|
|
11650
12219
|
async connect(config2) {
|
|
11651
12220
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
11652
12221
|
mkdirSync7(this.authDir, { recursive: true });
|
|
@@ -11655,6 +12224,20 @@ var WhatsAppAdapter = class {
|
|
|
11655
12224
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
11656
12225
|
const { version } = await fetchLatestBaileysVersion();
|
|
11657
12226
|
this.abortController = new AbortController();
|
|
12227
|
+
let agent;
|
|
12228
|
+
const socksProxy = config2.credentials.socksProxy;
|
|
12229
|
+
if (socksProxy) {
|
|
12230
|
+
try {
|
|
12231
|
+
const modName = "socks-proxy-agent";
|
|
12232
|
+
const mod = await import(modName);
|
|
12233
|
+
const SocksProxyAgent = mod.SocksProxyAgent ?? mod.default;
|
|
12234
|
+
agent = new SocksProxyAgent(socksProxy);
|
|
12235
|
+
console.log(`[whatsapp] Using SOCKS proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")}`);
|
|
12236
|
+
} catch {
|
|
12237
|
+
console.error("[whatsapp] socks-proxy-agent not installed \u2014 run: npm i socks-proxy-agent");
|
|
12238
|
+
throw new Error("SOCKS proxy configured but socks-proxy-agent package not installed");
|
|
12239
|
+
}
|
|
12240
|
+
}
|
|
11658
12241
|
const sock = makeWASocket({
|
|
11659
12242
|
auth: {
|
|
11660
12243
|
creds: state.creds,
|
|
@@ -11664,7 +12247,8 @@ var WhatsAppAdapter = class {
|
|
|
11664
12247
|
printQRInTerminal: true,
|
|
11665
12248
|
browser: ["exe-os", "cli", "1.0"],
|
|
11666
12249
|
syncFullHistory: false,
|
|
11667
|
-
markOnlineOnConnect: false
|
|
12250
|
+
markOnlineOnConnect: false,
|
|
12251
|
+
...agent ? { agent } : {}
|
|
11668
12252
|
});
|
|
11669
12253
|
this.sock = sock;
|
|
11670
12254
|
sock.ev.on("creds.update", saveCreds);
|
|
@@ -11672,18 +12256,32 @@ var WhatsAppAdapter = class {
|
|
|
11672
12256
|
const { connection, lastDisconnect } = update;
|
|
11673
12257
|
if (connection === "close") {
|
|
11674
12258
|
this.connected = false;
|
|
12259
|
+
if (this.disconnectedAt === 0) this.disconnectedAt = Date.now();
|
|
11675
12260
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
11676
12261
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
11677
12262
|
if (shouldReconnect && !this.abortController?.signal.aborted) {
|
|
11678
|
-
|
|
11679
|
-
|
|
12263
|
+
const delay2 = calculateBackoff(this.retryCount);
|
|
12264
|
+
this.retryCount++;
|
|
12265
|
+
console.log(
|
|
12266
|
+
`[whatsapp] Connection closed (code=${statusCode}), retry #${this.retryCount} in ${(delay2 / 1e3).toFixed(1)}s` + (socksProxy ? ` (proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")})` : "")
|
|
12267
|
+
);
|
|
12268
|
+
setTimeout(() => void this.connect(config2), delay2);
|
|
11680
12269
|
} else {
|
|
11681
12270
|
console.log("[whatsapp] Logged out \u2014 clear auth and re-scan QR");
|
|
11682
12271
|
}
|
|
11683
12272
|
}
|
|
11684
12273
|
if (connection === "open") {
|
|
12274
|
+
if (this.retryCount > 0) {
|
|
12275
|
+
const downtimeSec = this.disconnectedAt > 0 ? ((Date.now() - this.disconnectedAt) / 1e3).toFixed(1) : "?";
|
|
12276
|
+
console.log(
|
|
12277
|
+
`[whatsapp] Reconnected after ${this.retryCount} retries (${downtimeSec}s downtime)`
|
|
12278
|
+
);
|
|
12279
|
+
} else {
|
|
12280
|
+
console.log("[whatsapp] Connected via Baileys (linked device)");
|
|
12281
|
+
}
|
|
11685
12282
|
this.connected = true;
|
|
11686
|
-
|
|
12283
|
+
this.retryCount = 0;
|
|
12284
|
+
this.disconnectedAt = 0;
|
|
11687
12285
|
}
|
|
11688
12286
|
});
|
|
11689
12287
|
sock.ev.on("messages.upsert", (upsert) => {
|
|
@@ -12922,12 +13520,12 @@ var SlackAdapter = class {
|
|
|
12922
13520
|
// src/gateway/adapters/imessage.ts
|
|
12923
13521
|
import { execFile } from "child_process";
|
|
12924
13522
|
import { promisify } from "util";
|
|
12925
|
-
import
|
|
13523
|
+
import os10 from "os";
|
|
12926
13524
|
import path18 from "path";
|
|
12927
13525
|
var execFileAsync = promisify(execFile);
|
|
12928
13526
|
var POLL_INTERVAL_MS = 5e3;
|
|
12929
13527
|
var MESSAGES_DB_PATH = path18.join(
|
|
12930
|
-
process.env.HOME ??
|
|
13528
|
+
process.env.HOME ?? os10.homedir(),
|
|
12931
13529
|
"Library/Messages/chat.db"
|
|
12932
13530
|
);
|
|
12933
13531
|
var IMessageAdapter = class {
|
|
@@ -13773,8 +14371,8 @@ async function ensureCRMContact(info) {
|
|
|
13773
14371
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
13774
14372
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
13775
14373
|
import path19 from "path";
|
|
13776
|
-
import
|
|
13777
|
-
var TRIGGERS_PATH = path19.join(
|
|
14374
|
+
import os11 from "os";
|
|
14375
|
+
var TRIGGERS_PATH = path19.join(os11.homedir(), ".exe-os", "triggers.json");
|
|
13778
14376
|
var GRAPH_API_VERSION = "v21.0";
|
|
13779
14377
|
function substituteTemplate(template, record) {
|
|
13780
14378
|
return template.replace(
|