@askexenow/exe-os 0.8.83 → 0.8.86
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 +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- 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 +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- 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 +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- 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 +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- 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 +375 -152
- 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 +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
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);
|
|
@@ -748,18 +757,69 @@ var init_provider_table = __esm({
|
|
|
748
757
|
}
|
|
749
758
|
});
|
|
750
759
|
|
|
751
|
-
// src/lib/
|
|
752
|
-
|
|
760
|
+
// src/lib/runtime-table.ts
|
|
761
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
762
|
+
var init_runtime_table = __esm({
|
|
763
|
+
"src/lib/runtime-table.ts"() {
|
|
764
|
+
"use strict";
|
|
765
|
+
RUNTIME_TABLE = {
|
|
766
|
+
codex: {
|
|
767
|
+
binary: "codex",
|
|
768
|
+
launchMode: "exec",
|
|
769
|
+
autoApproveFlag: "--full-auto",
|
|
770
|
+
inlineFlag: "--no-alt-screen",
|
|
771
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
772
|
+
defaultModel: "gpt-5.4"
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
DEFAULT_RUNTIME = "claude";
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// src/lib/agent-config.ts
|
|
780
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
753
781
|
import path5 from "path";
|
|
782
|
+
function loadAgentConfig() {
|
|
783
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
784
|
+
try {
|
|
785
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
786
|
+
} catch {
|
|
787
|
+
return {};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function getAgentRuntime(agentId) {
|
|
791
|
+
const config2 = loadAgentConfig();
|
|
792
|
+
const entry = config2[agentId];
|
|
793
|
+
if (entry) return entry;
|
|
794
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
795
|
+
}
|
|
796
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
797
|
+
var init_agent_config = __esm({
|
|
798
|
+
"src/lib/agent-config.ts"() {
|
|
799
|
+
"use strict";
|
|
800
|
+
init_config();
|
|
801
|
+
init_runtime_table();
|
|
802
|
+
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
803
|
+
DEFAULT_MODELS = {
|
|
804
|
+
claude: "claude-opus-4",
|
|
805
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
806
|
+
opencode: "minimax-m2.7"
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// src/lib/intercom-queue.ts
|
|
812
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
813
|
+
import path6 from "path";
|
|
754
814
|
import os5 from "os";
|
|
755
815
|
function ensureDir() {
|
|
756
|
-
const dir =
|
|
757
|
-
if (!
|
|
816
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
817
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
758
818
|
}
|
|
759
819
|
function readQueue() {
|
|
760
820
|
try {
|
|
761
|
-
if (!
|
|
762
|
-
return JSON.parse(
|
|
821
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
822
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
763
823
|
} catch {
|
|
764
824
|
return [];
|
|
765
825
|
}
|
|
@@ -767,7 +827,7 @@ function readQueue() {
|
|
|
767
827
|
function writeQueue(queue) {
|
|
768
828
|
ensureDir();
|
|
769
829
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
770
|
-
|
|
830
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
771
831
|
renameSync3(tmp, QUEUE_PATH);
|
|
772
832
|
}
|
|
773
833
|
function queueIntercom(targetSession, reason) {
|
|
@@ -791,9 +851,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
791
851
|
var init_intercom_queue = __esm({
|
|
792
852
|
"src/lib/intercom-queue.ts"() {
|
|
793
853
|
"use strict";
|
|
794
|
-
QUEUE_PATH =
|
|
854
|
+
QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
795
855
|
TTL_MS = 60 * 60 * 1e3;
|
|
796
|
-
INTERCOM_LOG =
|
|
856
|
+
INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
797
857
|
}
|
|
798
858
|
});
|
|
799
859
|
|
|
@@ -852,370 +912,923 @@ var init_db_retry = __esm({
|
|
|
852
912
|
}
|
|
853
913
|
});
|
|
854
914
|
|
|
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;
|
|
915
|
+
// src/lib/exe-daemon-client.ts
|
|
916
|
+
import net from "net";
|
|
917
|
+
import { spawn } from "child_process";
|
|
918
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
919
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
920
|
+
import path7 from "path";
|
|
921
|
+
import { fileURLToPath } from "url";
|
|
922
|
+
function handleData(chunk) {
|
|
923
|
+
_buffer += chunk.toString();
|
|
924
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
925
|
+
_buffer = "";
|
|
926
|
+
return;
|
|
873
927
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
928
|
+
let newlineIdx;
|
|
929
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
930
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
931
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
932
|
+
if (!line) continue;
|
|
933
|
+
try {
|
|
934
|
+
const response = JSON.parse(line);
|
|
935
|
+
const id = response.id;
|
|
936
|
+
if (!id) continue;
|
|
937
|
+
const entry = _pending.get(id);
|
|
938
|
+
if (entry) {
|
|
939
|
+
clearTimeout(entry.timer);
|
|
940
|
+
_pending.delete(id);
|
|
941
|
+
entry.resolve(response);
|
|
942
|
+
}
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
879
945
|
}
|
|
880
|
-
_client = createClient(opts);
|
|
881
|
-
_resilientClient = wrapWithRetry(_client);
|
|
882
|
-
}
|
|
883
|
-
function isInitialized() {
|
|
884
|
-
return _client !== null;
|
|
885
946
|
}
|
|
886
|
-
function
|
|
887
|
-
if (
|
|
888
|
-
|
|
947
|
+
function cleanupStaleFiles() {
|
|
948
|
+
if (existsSync6(PID_PATH)) {
|
|
949
|
+
try {
|
|
950
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
951
|
+
if (pid > 0) {
|
|
952
|
+
try {
|
|
953
|
+
process.kill(pid, 0);
|
|
954
|
+
return;
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
try {
|
|
961
|
+
unlinkSync2(PID_PATH);
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
unlinkSync2(SOCKET_PATH);
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
889
968
|
}
|
|
890
|
-
return _resilientClient;
|
|
891
969
|
}
|
|
892
|
-
function
|
|
893
|
-
|
|
894
|
-
|
|
970
|
+
function findPackageRoot() {
|
|
971
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
972
|
+
const { root } = path7.parse(dir);
|
|
973
|
+
while (dir !== root) {
|
|
974
|
+
if (existsSync6(path7.join(dir, "package.json"))) return dir;
|
|
975
|
+
dir = path7.dirname(dir);
|
|
895
976
|
}
|
|
896
|
-
return
|
|
977
|
+
return null;
|
|
897
978
|
}
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
905
|
-
} catch {
|
|
906
|
-
}
|
|
907
|
-
await client.executeMultiple(`
|
|
908
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
909
|
-
id TEXT PRIMARY KEY,
|
|
910
|
-
agent_id TEXT NOT NULL,
|
|
911
|
-
agent_role TEXT NOT NULL,
|
|
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 {
|
|
979
|
+
function spawnDaemon() {
|
|
980
|
+
const pkgRoot = findPackageRoot();
|
|
981
|
+
if (!pkgRoot) {
|
|
982
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
983
|
+
return;
|
|
1022
984
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
} catch {
|
|
985
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
986
|
+
if (!existsSync6(daemonPath)) {
|
|
987
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
988
|
+
`);
|
|
989
|
+
return;
|
|
1029
990
|
}
|
|
991
|
+
const resolvedPath = daemonPath;
|
|
992
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
993
|
+
`);
|
|
994
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
995
|
+
let stderrFd = "ignore";
|
|
1030
996
|
try {
|
|
1031
|
-
|
|
1032
|
-
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1033
|
-
args: []
|
|
1034
|
-
});
|
|
997
|
+
stderrFd = openSync(logPath, "a");
|
|
1035
998
|
} catch {
|
|
1036
999
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1000
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
1001
|
+
detached: true,
|
|
1002
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
1003
|
+
env: {
|
|
1004
|
+
...process.env,
|
|
1005
|
+
TMUX: void 0,
|
|
1006
|
+
// Daemon is global — must not inherit session scope
|
|
1007
|
+
TMUX_PANE: void 0,
|
|
1008
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
1009
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1010
|
+
EXE_DAEMON_PID: PID_PATH
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
child.unref();
|
|
1014
|
+
if (typeof stderrFd === "number") {
|
|
1015
|
+
try {
|
|
1016
|
+
closeSync(stderrFd);
|
|
1017
|
+
} catch {
|
|
1018
|
+
}
|
|
1052
1019
|
}
|
|
1020
|
+
}
|
|
1021
|
+
function acquireSpawnLock() {
|
|
1053
1022
|
try {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
});
|
|
1023
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1024
|
+
closeSync(fd);
|
|
1025
|
+
return true;
|
|
1058
1026
|
} catch {
|
|
1027
|
+
try {
|
|
1028
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
1029
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1030
|
+
try {
|
|
1031
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1036
|
+
closeSync(fd);
|
|
1037
|
+
return true;
|
|
1038
|
+
} catch {
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
return false;
|
|
1059
1044
|
}
|
|
1045
|
+
}
|
|
1046
|
+
function releaseSpawnLock() {
|
|
1060
1047
|
try {
|
|
1061
|
-
|
|
1062
|
-
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1063
|
-
args: []
|
|
1064
|
-
});
|
|
1048
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1065
1049
|
} catch {
|
|
1066
1050
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1051
|
+
}
|
|
1052
|
+
function connectToSocket() {
|
|
1053
|
+
return new Promise((resolve) => {
|
|
1054
|
+
if (_socket && _connected) {
|
|
1055
|
+
resolve(true);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
1059
|
+
const connectTimeout = setTimeout(() => {
|
|
1060
|
+
socket.destroy();
|
|
1061
|
+
resolve(false);
|
|
1062
|
+
}, 2e3);
|
|
1063
|
+
socket.on("connect", () => {
|
|
1064
|
+
clearTimeout(connectTimeout);
|
|
1065
|
+
_socket = socket;
|
|
1066
|
+
_connected = true;
|
|
1067
|
+
_buffer = "";
|
|
1068
|
+
socket.on("data", handleData);
|
|
1069
|
+
socket.on("close", () => {
|
|
1070
|
+
_connected = false;
|
|
1071
|
+
_socket = null;
|
|
1072
|
+
for (const [id, entry] of _pending) {
|
|
1073
|
+
clearTimeout(entry.timer);
|
|
1074
|
+
_pending.delete(id);
|
|
1075
|
+
entry.resolve({ error: "Connection closed" });
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
socket.on("error", () => {
|
|
1079
|
+
_connected = false;
|
|
1080
|
+
_socket = null;
|
|
1081
|
+
});
|
|
1082
|
+
resolve(true);
|
|
1071
1083
|
});
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
await client.execute({
|
|
1076
|
-
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1077
|
-
args: []
|
|
1084
|
+
socket.on("error", () => {
|
|
1085
|
+
clearTimeout(connectTimeout);
|
|
1086
|
+
resolve(false);
|
|
1078
1087
|
});
|
|
1079
|
-
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
async function connectEmbedDaemon() {
|
|
1091
|
+
if (_socket && _connected) return true;
|
|
1092
|
+
if (await connectToSocket()) return true;
|
|
1093
|
+
if (acquireSpawnLock()) {
|
|
1094
|
+
try {
|
|
1095
|
+
cleanupStaleFiles();
|
|
1096
|
+
spawnDaemon();
|
|
1097
|
+
} finally {
|
|
1098
|
+
releaseSpawnLock();
|
|
1099
|
+
}
|
|
1080
1100
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1101
|
+
const start = Date.now();
|
|
1102
|
+
let delay2 = 100;
|
|
1103
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1104
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1105
|
+
if (await connectToSocket()) return true;
|
|
1106
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1087
1107
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
function sendRequest(texts, priority) {
|
|
1111
|
+
return sendDaemonRequest({ texts, priority });
|
|
1112
|
+
}
|
|
1113
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
1114
|
+
return new Promise((resolve) => {
|
|
1115
|
+
if (!_socket || !_connected) {
|
|
1116
|
+
resolve({ error: "Not connected" });
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const id = randomUUID2();
|
|
1120
|
+
const timer = setTimeout(() => {
|
|
1121
|
+
_pending.delete(id);
|
|
1122
|
+
resolve({ error: "Request timeout" });
|
|
1123
|
+
}, timeoutMs);
|
|
1124
|
+
_pending.set(id, { resolve, timer });
|
|
1125
|
+
try {
|
|
1126
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1127
|
+
} catch {
|
|
1128
|
+
clearTimeout(timer);
|
|
1129
|
+
_pending.delete(id);
|
|
1130
|
+
resolve({ error: "Write failed" });
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
async function pingDaemon() {
|
|
1135
|
+
if (!_socket || !_connected) return null;
|
|
1136
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
1137
|
+
if (response.health) {
|
|
1138
|
+
return response.health;
|
|
1094
1139
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
function killAndRespawnDaemon() {
|
|
1143
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1144
|
+
if (existsSync6(PID_PATH)) {
|
|
1145
|
+
try {
|
|
1146
|
+
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
1147
|
+
if (pid > 0) {
|
|
1148
|
+
try {
|
|
1149
|
+
process.kill(pid, "SIGKILL");
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1101
1155
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
args: []
|
|
1106
|
-
});
|
|
1107
|
-
} catch {
|
|
1156
|
+
if (_socket) {
|
|
1157
|
+
_socket.destroy();
|
|
1158
|
+
_socket = null;
|
|
1108
1159
|
}
|
|
1160
|
+
_connected = false;
|
|
1161
|
+
_buffer = "";
|
|
1109
1162
|
try {
|
|
1110
|
-
|
|
1111
|
-
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1112
|
-
args: []
|
|
1113
|
-
});
|
|
1163
|
+
unlinkSync2(PID_PATH);
|
|
1114
1164
|
} catch {
|
|
1115
1165
|
}
|
|
1116
1166
|
try {
|
|
1117
|
-
|
|
1118
|
-
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1119
|
-
args: []
|
|
1120
|
-
});
|
|
1167
|
+
unlinkSync2(SOCKET_PATH);
|
|
1121
1168
|
} catch {
|
|
1122
1169
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1170
|
+
spawnDaemon();
|
|
1171
|
+
}
|
|
1172
|
+
async function embedViaClient(text, priority = "high") {
|
|
1173
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1174
|
+
_requestCount++;
|
|
1175
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1176
|
+
const health = await pingDaemon();
|
|
1177
|
+
if (!health) {
|
|
1178
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1179
|
+
`);
|
|
1180
|
+
killAndRespawnDaemon();
|
|
1181
|
+
const start = Date.now();
|
|
1182
|
+
let delay2 = 200;
|
|
1183
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1184
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1185
|
+
if (await connectToSocket()) break;
|
|
1186
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1187
|
+
}
|
|
1188
|
+
if (!_connected) return null;
|
|
1189
|
+
}
|
|
1129
1190
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1191
|
+
const result = await sendRequest([text], priority);
|
|
1192
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
1193
|
+
if (result.error) {
|
|
1194
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
1195
|
+
`);
|
|
1196
|
+
killAndRespawnDaemon();
|
|
1197
|
+
const start = Date.now();
|
|
1198
|
+
let delay2 = 200;
|
|
1199
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1200
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1201
|
+
if (await connectToSocket()) break;
|
|
1202
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1203
|
+
}
|
|
1204
|
+
if (!_connected) return null;
|
|
1205
|
+
const retry = await sendRequest([text], priority);
|
|
1206
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
1207
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
1208
|
+
`);
|
|
1136
1209
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
function disconnectClient() {
|
|
1213
|
+
if (_socket) {
|
|
1214
|
+
_socket.destroy();
|
|
1215
|
+
_socket = null;
|
|
1143
1216
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1217
|
+
_connected = false;
|
|
1218
|
+
_buffer = "";
|
|
1219
|
+
for (const [id, entry] of _pending) {
|
|
1220
|
+
clearTimeout(entry.timer);
|
|
1221
|
+
_pending.delete(id);
|
|
1222
|
+
entry.resolve({ error: "Client disconnected" });
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function isClientConnected() {
|
|
1226
|
+
return _connected;
|
|
1227
|
+
}
|
|
1228
|
+
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;
|
|
1229
|
+
var init_exe_daemon_client = __esm({
|
|
1230
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
1231
|
+
"use strict";
|
|
1232
|
+
init_config();
|
|
1233
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
1234
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
1235
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1236
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1237
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
1238
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
1239
|
+
_socket = null;
|
|
1240
|
+
_connected = false;
|
|
1241
|
+
_buffer = "";
|
|
1242
|
+
_requestCount = 0;
|
|
1243
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
1244
|
+
_pending = /* @__PURE__ */ new Map();
|
|
1245
|
+
MAX_BUFFER = 1e7;
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// src/lib/daemon-protocol.ts
|
|
1250
|
+
function serializeValue(v) {
|
|
1251
|
+
if (v === null || v === void 0) return null;
|
|
1252
|
+
if (typeof v === "bigint") return Number(v);
|
|
1253
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
1254
|
+
if (v instanceof Uint8Array) {
|
|
1255
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
1256
|
+
}
|
|
1257
|
+
if (ArrayBuffer.isView(v)) {
|
|
1258
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
1259
|
+
}
|
|
1260
|
+
if (v instanceof ArrayBuffer) {
|
|
1261
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
1262
|
+
}
|
|
1263
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
1264
|
+
return String(v);
|
|
1265
|
+
}
|
|
1266
|
+
function deserializeValue(v) {
|
|
1267
|
+
if (v === null) return null;
|
|
1268
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
1269
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
1270
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
1271
|
+
}
|
|
1272
|
+
return v;
|
|
1273
|
+
}
|
|
1274
|
+
function deserializeResultSet(srs) {
|
|
1275
|
+
const rows = srs.rows.map((obj) => {
|
|
1276
|
+
const values = srs.columns.map(
|
|
1277
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
1150
1278
|
);
|
|
1279
|
+
const row = values;
|
|
1280
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
1281
|
+
const col = srs.columns[i];
|
|
1282
|
+
if (col !== void 0) {
|
|
1283
|
+
row[col] = values[i] ?? null;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
Object.defineProperty(row, "length", {
|
|
1287
|
+
value: values.length,
|
|
1288
|
+
enumerable: false
|
|
1289
|
+
});
|
|
1290
|
+
return row;
|
|
1291
|
+
});
|
|
1292
|
+
return {
|
|
1293
|
+
columns: srs.columns,
|
|
1294
|
+
columnTypes: srs.columnTypes ?? [],
|
|
1295
|
+
rows,
|
|
1296
|
+
rowsAffected: srs.rowsAffected,
|
|
1297
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
1298
|
+
toJSON: () => ({
|
|
1299
|
+
columns: srs.columns,
|
|
1300
|
+
columnTypes: srs.columnTypes ?? [],
|
|
1301
|
+
rows: srs.rows,
|
|
1302
|
+
rowsAffected: srs.rowsAffected,
|
|
1303
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
1304
|
+
})
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
var init_daemon_protocol = __esm({
|
|
1308
|
+
"src/lib/daemon-protocol.ts"() {
|
|
1309
|
+
"use strict";
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1151
1312
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1313
|
+
// src/lib/db-daemon-client.ts
|
|
1314
|
+
var db_daemon_client_exports = {};
|
|
1315
|
+
__export(db_daemon_client_exports, {
|
|
1316
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
1317
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
1318
|
+
});
|
|
1319
|
+
function normalizeStatement(stmt) {
|
|
1320
|
+
if (typeof stmt === "string") {
|
|
1321
|
+
return { sql: stmt, args: [] };
|
|
1322
|
+
}
|
|
1323
|
+
const sql = stmt.sql;
|
|
1324
|
+
let args = [];
|
|
1325
|
+
if (Array.isArray(stmt.args)) {
|
|
1326
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
1327
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
1328
|
+
const named = {};
|
|
1329
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
1330
|
+
named[key] = serializeValue(val);
|
|
1331
|
+
}
|
|
1332
|
+
return { sql, args: named };
|
|
1333
|
+
}
|
|
1334
|
+
return { sql, args };
|
|
1335
|
+
}
|
|
1336
|
+
function createDaemonDbClient(fallbackClient) {
|
|
1337
|
+
let _useDaemon = false;
|
|
1338
|
+
const client = {
|
|
1339
|
+
async execute(stmt) {
|
|
1340
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1341
|
+
return fallbackClient.execute(stmt);
|
|
1342
|
+
}
|
|
1343
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
1344
|
+
const response = await sendDaemonRequest({
|
|
1345
|
+
type: "db-execute",
|
|
1346
|
+
sql,
|
|
1347
|
+
args
|
|
1348
|
+
});
|
|
1349
|
+
if (response.error) {
|
|
1350
|
+
const errMsg = String(response.error);
|
|
1351
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1352
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1353
|
+
`);
|
|
1354
|
+
return fallbackClient.execute(stmt);
|
|
1355
|
+
}
|
|
1356
|
+
throw new Error(errMsg);
|
|
1357
|
+
}
|
|
1358
|
+
if (response.db) {
|
|
1359
|
+
return deserializeResultSet(response.db);
|
|
1360
|
+
}
|
|
1361
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1362
|
+
return fallbackClient.execute(stmt);
|
|
1363
|
+
},
|
|
1364
|
+
async batch(stmts, mode) {
|
|
1365
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1366
|
+
return fallbackClient.batch(stmts, mode);
|
|
1367
|
+
}
|
|
1368
|
+
const statements = stmts.map(normalizeStatement);
|
|
1369
|
+
const response = await sendDaemonRequest({
|
|
1370
|
+
type: "db-batch",
|
|
1371
|
+
statements,
|
|
1372
|
+
mode: mode ?? "deferred"
|
|
1373
|
+
});
|
|
1374
|
+
if (response.error) {
|
|
1375
|
+
const errMsg = String(response.error);
|
|
1376
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1377
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1378
|
+
`);
|
|
1379
|
+
return fallbackClient.batch(stmts, mode);
|
|
1380
|
+
}
|
|
1381
|
+
throw new Error(errMsg);
|
|
1382
|
+
}
|
|
1383
|
+
const batchResults = response["db-batch"];
|
|
1384
|
+
if (batchResults) {
|
|
1385
|
+
return batchResults.map(deserializeResultSet);
|
|
1386
|
+
}
|
|
1387
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1388
|
+
return fallbackClient.batch(stmts, mode);
|
|
1389
|
+
},
|
|
1390
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1391
|
+
async transaction(mode) {
|
|
1392
|
+
return fallbackClient.transaction(mode);
|
|
1393
|
+
},
|
|
1394
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1395
|
+
async executeMultiple(sql) {
|
|
1396
|
+
return fallbackClient.executeMultiple(sql);
|
|
1397
|
+
},
|
|
1398
|
+
// migrate — delegate to fallback
|
|
1399
|
+
async migrate(stmts) {
|
|
1400
|
+
return fallbackClient.migrate(stmts);
|
|
1401
|
+
},
|
|
1402
|
+
// Sync mode — delegate to fallback
|
|
1403
|
+
sync() {
|
|
1404
|
+
return fallbackClient.sync();
|
|
1405
|
+
},
|
|
1406
|
+
close() {
|
|
1407
|
+
_useDaemon = false;
|
|
1408
|
+
},
|
|
1409
|
+
get closed() {
|
|
1410
|
+
return fallbackClient.closed;
|
|
1411
|
+
},
|
|
1412
|
+
get protocol() {
|
|
1413
|
+
return fallbackClient.protocol;
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
return {
|
|
1417
|
+
...client,
|
|
1418
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1419
|
+
_enableDaemon() {
|
|
1420
|
+
_useDaemon = true;
|
|
1421
|
+
},
|
|
1422
|
+
/** Check if daemon routing is active */
|
|
1423
|
+
_isDaemonActive() {
|
|
1424
|
+
return _useDaemon && isClientConnected();
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1429
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1430
|
+
const connected = await connectEmbedDaemon();
|
|
1431
|
+
if (!connected) {
|
|
1432
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1436
|
+
client._enableDaemon();
|
|
1437
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1438
|
+
return client;
|
|
1439
|
+
}
|
|
1440
|
+
var init_db_daemon_client = __esm({
|
|
1441
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1442
|
+
"use strict";
|
|
1443
|
+
init_exe_daemon_client();
|
|
1444
|
+
init_daemon_protocol();
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1154
1447
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1448
|
+
// src/lib/database.ts
|
|
1449
|
+
var database_exports = {};
|
|
1450
|
+
__export(database_exports, {
|
|
1451
|
+
disposeDatabase: () => disposeDatabase,
|
|
1452
|
+
disposeTurso: () => disposeTurso,
|
|
1453
|
+
ensureSchema: () => ensureSchema,
|
|
1454
|
+
getClient: () => getClient,
|
|
1455
|
+
getRawClient: () => getRawClient,
|
|
1456
|
+
initDaemonClient: () => initDaemonClient,
|
|
1457
|
+
initDatabase: () => initDatabase,
|
|
1458
|
+
initTurso: () => initTurso,
|
|
1459
|
+
isInitialized: () => isInitialized
|
|
1460
|
+
});
|
|
1461
|
+
import { createClient } from "@libsql/client";
|
|
1462
|
+
async function initDatabase(config2) {
|
|
1463
|
+
if (_client) {
|
|
1464
|
+
_client.close();
|
|
1465
|
+
_client = null;
|
|
1466
|
+
_resilientClient = null;
|
|
1467
|
+
}
|
|
1468
|
+
const opts = {
|
|
1469
|
+
url: `file:${config2.dbPath}`
|
|
1470
|
+
};
|
|
1471
|
+
if (config2.encryptionKey) {
|
|
1472
|
+
opts.encryptionKey = config2.encryptionKey;
|
|
1473
|
+
}
|
|
1474
|
+
_client = createClient(opts);
|
|
1475
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1476
|
+
}
|
|
1477
|
+
function isInitialized() {
|
|
1478
|
+
return _client !== null;
|
|
1479
|
+
}
|
|
1480
|
+
function getClient() {
|
|
1481
|
+
if (!_resilientClient) {
|
|
1482
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1483
|
+
}
|
|
1484
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1485
|
+
return _resilientClient;
|
|
1486
|
+
}
|
|
1487
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1488
|
+
return _daemonClient;
|
|
1489
|
+
}
|
|
1490
|
+
return _resilientClient;
|
|
1491
|
+
}
|
|
1492
|
+
async function initDaemonClient() {
|
|
1493
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1494
|
+
if (!_resilientClient) return;
|
|
1495
|
+
try {
|
|
1496
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1497
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1498
|
+
} catch (err) {
|
|
1499
|
+
process.stderr.write(
|
|
1500
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1501
|
+
`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
function getRawClient() {
|
|
1506
|
+
if (!_client) {
|
|
1507
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1508
|
+
}
|
|
1509
|
+
return _client;
|
|
1510
|
+
}
|
|
1511
|
+
async function ensureSchema() {
|
|
1512
|
+
const client = getRawClient();
|
|
1513
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1514
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1515
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1516
|
+
try {
|
|
1517
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1158
1520
|
await client.executeMultiple(`
|
|
1159
|
-
CREATE TABLE IF NOT EXISTS
|
|
1521
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1160
1522
|
id TEXT PRIMARY KEY,
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1523
|
+
agent_id TEXT NOT NULL,
|
|
1524
|
+
agent_role TEXT NOT NULL,
|
|
1525
|
+
session_id TEXT NOT NULL,
|
|
1526
|
+
timestamp TEXT NOT NULL,
|
|
1527
|
+
tool_name TEXT NOT NULL,
|
|
1528
|
+
project_name TEXT NOT NULL,
|
|
1529
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1530
|
+
raw_text TEXT NOT NULL,
|
|
1531
|
+
vector F32_BLOB(1024),
|
|
1532
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
1165
1533
|
);
|
|
1534
|
+
|
|
1535
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1536
|
+
ON memories(agent_id);
|
|
1537
|
+
|
|
1538
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1539
|
+
ON memories(timestamp);
|
|
1540
|
+
|
|
1541
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1542
|
+
ON memories(session_id);
|
|
1543
|
+
|
|
1544
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1545
|
+
ON memories(project_name);
|
|
1546
|
+
|
|
1547
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1548
|
+
ON memories(tool_name);
|
|
1549
|
+
|
|
1550
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1551
|
+
ON memories(version);
|
|
1552
|
+
|
|
1553
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1554
|
+
ON memories(agent_id, project_name);
|
|
1166
1555
|
`);
|
|
1167
1556
|
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
|
|
1557
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1558
|
+
raw_text,
|
|
1559
|
+
content='memories',
|
|
1560
|
+
content_rowid='rowid'
|
|
1178
1561
|
);
|
|
1179
1562
|
|
|
1180
|
-
CREATE
|
|
1181
|
-
|
|
1563
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1564
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1565
|
+
END;
|
|
1182
1566
|
|
|
1183
|
-
CREATE
|
|
1184
|
-
|
|
1567
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1568
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1569
|
+
END;
|
|
1185
1570
|
|
|
1186
|
-
CREATE
|
|
1187
|
-
|
|
1571
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1572
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1573
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1574
|
+
END;
|
|
1188
1575
|
`);
|
|
1189
1576
|
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
|
|
1577
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1578
|
+
key TEXT PRIMARY KEY,
|
|
1579
|
+
value TEXT NOT NULL
|
|
1201
1580
|
);
|
|
1202
1581
|
`);
|
|
1203
1582
|
await client.executeMultiple(`
|
|
1204
|
-
CREATE TABLE IF NOT EXISTS
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1583
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1584
|
+
id TEXT PRIMARY KEY,
|
|
1585
|
+
title TEXT NOT NULL,
|
|
1586
|
+
assigned_to TEXT NOT NULL,
|
|
1587
|
+
assigned_by TEXT NOT NULL,
|
|
1588
|
+
project_name TEXT NOT NULL,
|
|
1589
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1590
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1591
|
+
task_file TEXT,
|
|
1592
|
+
created_at TEXT NOT NULL,
|
|
1593
|
+
updated_at TEXT NOT NULL
|
|
1212
1594
|
);
|
|
1595
|
+
|
|
1596
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1597
|
+
ON tasks(assigned_to, status);
|
|
1213
1598
|
`);
|
|
1214
1599
|
await client.executeMultiple(`
|
|
1215
|
-
CREATE TABLE IF NOT EXISTS
|
|
1216
|
-
id
|
|
1217
|
-
|
|
1218
|
-
|
|
1600
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1601
|
+
id TEXT PRIMARY KEY,
|
|
1602
|
+
agent_id TEXT NOT NULL,
|
|
1603
|
+
project_name TEXT,
|
|
1604
|
+
domain TEXT,
|
|
1605
|
+
content TEXT NOT NULL,
|
|
1606
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1607
|
+
created_at TEXT NOT NULL,
|
|
1608
|
+
updated_at TEXT NOT NULL
|
|
1609
|
+
);
|
|
1610
|
+
|
|
1611
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1612
|
+
ON behaviors(agent_id, active);
|
|
1613
|
+
`);
|
|
1614
|
+
try {
|
|
1615
|
+
const coordinatorName = getCoordinatorName();
|
|
1616
|
+
const existing = await client.execute({
|
|
1617
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1618
|
+
args: [coordinatorName]
|
|
1619
|
+
});
|
|
1620
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1621
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1622
|
+
for (const [domain, content] of [
|
|
1623
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1624
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1625
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1626
|
+
]) {
|
|
1627
|
+
await client.execute({
|
|
1628
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1629
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1630
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
} catch {
|
|
1635
|
+
}
|
|
1636
|
+
try {
|
|
1637
|
+
await client.execute({
|
|
1638
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1639
|
+
args: []
|
|
1640
|
+
});
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
await client.execute({
|
|
1645
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1646
|
+
args: []
|
|
1647
|
+
});
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
await client.execute({
|
|
1652
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1653
|
+
args: []
|
|
1654
|
+
});
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
try {
|
|
1658
|
+
await client.execute({
|
|
1659
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1660
|
+
ON tasks(parent_task_id)
|
|
1661
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1662
|
+
args: []
|
|
1663
|
+
});
|
|
1664
|
+
} catch {
|
|
1665
|
+
}
|
|
1666
|
+
try {
|
|
1667
|
+
await client.execute({
|
|
1668
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1669
|
+
args: []
|
|
1670
|
+
});
|
|
1671
|
+
} catch {
|
|
1672
|
+
}
|
|
1673
|
+
try {
|
|
1674
|
+
await client.execute({
|
|
1675
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1676
|
+
args: []
|
|
1677
|
+
});
|
|
1678
|
+
} catch {
|
|
1679
|
+
}
|
|
1680
|
+
try {
|
|
1681
|
+
await client.execute({
|
|
1682
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1683
|
+
args: []
|
|
1684
|
+
});
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
try {
|
|
1688
|
+
await client.execute({
|
|
1689
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1690
|
+
args: []
|
|
1691
|
+
});
|
|
1692
|
+
} catch {
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
await client.execute({
|
|
1696
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1697
|
+
args: []
|
|
1698
|
+
});
|
|
1699
|
+
} catch {
|
|
1700
|
+
}
|
|
1701
|
+
try {
|
|
1702
|
+
await client.execute({
|
|
1703
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1704
|
+
args: []
|
|
1705
|
+
});
|
|
1706
|
+
} catch {
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
await client.execute({
|
|
1710
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1711
|
+
args: []
|
|
1712
|
+
});
|
|
1713
|
+
} catch {
|
|
1714
|
+
}
|
|
1715
|
+
try {
|
|
1716
|
+
await client.execute({
|
|
1717
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1718
|
+
args: []
|
|
1719
|
+
});
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1722
|
+
try {
|
|
1723
|
+
await client.execute({
|
|
1724
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1725
|
+
args: []
|
|
1726
|
+
});
|
|
1727
|
+
} catch {
|
|
1728
|
+
}
|
|
1729
|
+
try {
|
|
1730
|
+
await client.execute({
|
|
1731
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1732
|
+
args: []
|
|
1733
|
+
});
|
|
1734
|
+
} catch {
|
|
1735
|
+
}
|
|
1736
|
+
try {
|
|
1737
|
+
await client.execute({
|
|
1738
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1739
|
+
args: []
|
|
1740
|
+
});
|
|
1741
|
+
} catch {
|
|
1742
|
+
}
|
|
1743
|
+
try {
|
|
1744
|
+
await client.execute({
|
|
1745
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1746
|
+
args: []
|
|
1747
|
+
});
|
|
1748
|
+
} catch {
|
|
1749
|
+
}
|
|
1750
|
+
try {
|
|
1751
|
+
await client.execute({
|
|
1752
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1753
|
+
args: []
|
|
1754
|
+
});
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
await client.executeMultiple(`
|
|
1758
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1759
|
+
id TEXT PRIMARY KEY,
|
|
1760
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1761
|
+
source_memory_id TEXT NOT NULL,
|
|
1762
|
+
created_at TEXT NOT NULL
|
|
1763
|
+
);
|
|
1764
|
+
|
|
1765
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1766
|
+
ON consolidations(source_memory_id);
|
|
1767
|
+
|
|
1768
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1769
|
+
ON consolidations(consolidated_memory_id);
|
|
1770
|
+
`);
|
|
1771
|
+
await client.executeMultiple(`
|
|
1772
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1773
|
+
id TEXT PRIMARY KEY,
|
|
1774
|
+
text TEXT NOT NULL,
|
|
1775
|
+
created_at TEXT NOT NULL,
|
|
1776
|
+
due_date TEXT,
|
|
1777
|
+
completed_at TEXT
|
|
1778
|
+
);
|
|
1779
|
+
`);
|
|
1780
|
+
await client.executeMultiple(`
|
|
1781
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1782
|
+
id TEXT PRIMARY KEY,
|
|
1783
|
+
agent_id TEXT NOT NULL,
|
|
1784
|
+
agent_role TEXT NOT NULL,
|
|
1785
|
+
event TEXT NOT NULL,
|
|
1786
|
+
project TEXT NOT NULL,
|
|
1787
|
+
summary TEXT NOT NULL,
|
|
1788
|
+
task_file TEXT,
|
|
1789
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1790
|
+
created_at TEXT NOT NULL
|
|
1791
|
+
);
|
|
1792
|
+
|
|
1793
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1794
|
+
ON notifications(read);
|
|
1795
|
+
|
|
1796
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1797
|
+
ON notifications(agent_id);
|
|
1798
|
+
|
|
1799
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1800
|
+
ON notifications(task_file);
|
|
1801
|
+
`);
|
|
1802
|
+
await client.executeMultiple(`
|
|
1803
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1804
|
+
id TEXT PRIMARY KEY,
|
|
1805
|
+
cron TEXT NOT NULL,
|
|
1806
|
+
description TEXT NOT NULL,
|
|
1807
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1808
|
+
prompt TEXT,
|
|
1809
|
+
assigned_to TEXT,
|
|
1810
|
+
project_name TEXT,
|
|
1811
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1812
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1813
|
+
created_at TEXT NOT NULL
|
|
1814
|
+
);
|
|
1815
|
+
`);
|
|
1816
|
+
await client.executeMultiple(`
|
|
1817
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1818
|
+
device_id TEXT PRIMARY KEY,
|
|
1819
|
+
friendly_name TEXT NOT NULL,
|
|
1820
|
+
hostname TEXT NOT NULL,
|
|
1821
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1822
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1823
|
+
connected INTEGER DEFAULT 0,
|
|
1824
|
+
last_seen TEXT NOT NULL
|
|
1825
|
+
);
|
|
1826
|
+
`);
|
|
1827
|
+
await client.executeMultiple(`
|
|
1828
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1829
|
+
id TEXT PRIMARY KEY,
|
|
1830
|
+
from_agent TEXT NOT NULL,
|
|
1831
|
+
from_device TEXT NOT NULL DEFAULT 'local',
|
|
1219
1832
|
target_agent TEXT NOT NULL,
|
|
1220
1833
|
target_project TEXT,
|
|
1221
1834
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
@@ -1375,6 +1988,12 @@ async function ensureSchema() {
|
|
|
1375
1988
|
} catch {
|
|
1376
1989
|
}
|
|
1377
1990
|
}
|
|
1991
|
+
try {
|
|
1992
|
+
await client.execute(
|
|
1993
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1994
|
+
);
|
|
1995
|
+
} catch {
|
|
1996
|
+
}
|
|
1378
1997
|
await client.executeMultiple(`
|
|
1379
1998
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1380
1999
|
id TEXT PRIMARY KEY,
|
|
@@ -1427,7 +2046,30 @@ async function ensureSchema() {
|
|
|
1427
2046
|
entity_id TEXT NOT NULL,
|
|
1428
2047
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1429
2048
|
);
|
|
2049
|
+
|
|
2050
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
2051
|
+
name,
|
|
2052
|
+
content=entities,
|
|
2053
|
+
content_rowid=rowid
|
|
2054
|
+
);
|
|
2055
|
+
|
|
2056
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
2057
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2058
|
+
END;
|
|
2059
|
+
|
|
2060
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
2061
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2062
|
+
END;
|
|
2063
|
+
|
|
2064
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
2065
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2066
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2067
|
+
END;
|
|
1430
2068
|
`);
|
|
2069
|
+
try {
|
|
2070
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
2071
|
+
} catch {
|
|
2072
|
+
}
|
|
1431
2073
|
await client.executeMultiple(`
|
|
1432
2074
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1433
2075
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1608,10 +2250,37 @@ async function ensureSchema() {
|
|
|
1608
2250
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1609
2251
|
ON conversations(channel_id);
|
|
1610
2252
|
`);
|
|
2253
|
+
await client.executeMultiple(`
|
|
2254
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
2255
|
+
session_uuid TEXT PRIMARY KEY,
|
|
2256
|
+
agent_id TEXT NOT NULL,
|
|
2257
|
+
session_name TEXT,
|
|
2258
|
+
task_id TEXT,
|
|
2259
|
+
project_name TEXT,
|
|
2260
|
+
started_at TEXT NOT NULL
|
|
2261
|
+
);
|
|
2262
|
+
|
|
2263
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
2264
|
+
ON session_agent_map(agent_id);
|
|
2265
|
+
`);
|
|
1611
2266
|
try {
|
|
1612
|
-
await client.execute({
|
|
1613
|
-
|
|
1614
|
-
|
|
2267
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
2268
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
2269
|
+
await client.execute({
|
|
2270
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
2271
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
2272
|
+
FROM memories
|
|
2273
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
2274
|
+
GROUP BY session_id, agent_id`,
|
|
2275
|
+
args: []
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
} catch {
|
|
2279
|
+
}
|
|
2280
|
+
try {
|
|
2281
|
+
await client.execute({
|
|
2282
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
2283
|
+
args: []
|
|
1615
2284
|
});
|
|
1616
2285
|
} catch {
|
|
1617
2286
|
}
|
|
@@ -1741,15 +2410,41 @@ async function ensureSchema() {
|
|
|
1741
2410
|
});
|
|
1742
2411
|
} catch {
|
|
1743
2412
|
}
|
|
2413
|
+
for (const col of [
|
|
2414
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2415
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2416
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2417
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2418
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2419
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2420
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2421
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2422
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2423
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2424
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2425
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2426
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2427
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2428
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2429
|
+
]) {
|
|
2430
|
+
try {
|
|
2431
|
+
await client.execute(col);
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
1744
2435
|
}
|
|
1745
2436
|
async function disposeDatabase() {
|
|
2437
|
+
if (_daemonClient) {
|
|
2438
|
+
_daemonClient.close();
|
|
2439
|
+
_daemonClient = null;
|
|
2440
|
+
}
|
|
1746
2441
|
if (_client) {
|
|
1747
2442
|
_client.close();
|
|
1748
2443
|
_client = null;
|
|
1749
2444
|
_resilientClient = null;
|
|
1750
2445
|
}
|
|
1751
2446
|
}
|
|
1752
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2447
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1753
2448
|
var init_database = __esm({
|
|
1754
2449
|
"src/lib/database.ts"() {
|
|
1755
2450
|
"use strict";
|
|
@@ -1757,24 +2452,25 @@ var init_database = __esm({
|
|
|
1757
2452
|
init_employees();
|
|
1758
2453
|
_client = null;
|
|
1759
2454
|
_resilientClient = null;
|
|
2455
|
+
_daemonClient = null;
|
|
1760
2456
|
initTurso = initDatabase;
|
|
1761
2457
|
disposeTurso = disposeDatabase;
|
|
1762
2458
|
}
|
|
1763
2459
|
});
|
|
1764
2460
|
|
|
1765
2461
|
// src/lib/license.ts
|
|
1766
|
-
import { readFileSync as
|
|
1767
|
-
import { randomUUID as
|
|
1768
|
-
import
|
|
2462
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2463
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2464
|
+
import path8 from "path";
|
|
1769
2465
|
import { jwtVerify, importSPKI } from "jose";
|
|
1770
2466
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1771
2467
|
var init_license = __esm({
|
|
1772
2468
|
"src/lib/license.ts"() {
|
|
1773
2469
|
"use strict";
|
|
1774
2470
|
init_config();
|
|
1775
|
-
LICENSE_PATH =
|
|
1776
|
-
CACHE_PATH =
|
|
1777
|
-
DEVICE_ID_PATH =
|
|
2471
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2472
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2473
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
1778
2474
|
PLAN_LIMITS = {
|
|
1779
2475
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1780
2476
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1786,12 +2482,12 @@ var init_license = __esm({
|
|
|
1786
2482
|
});
|
|
1787
2483
|
|
|
1788
2484
|
// src/lib/plan-limits.ts
|
|
1789
|
-
import { readFileSync as
|
|
1790
|
-
import
|
|
2485
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
2486
|
+
import path9 from "path";
|
|
1791
2487
|
function getLicenseSync() {
|
|
1792
2488
|
try {
|
|
1793
|
-
if (!
|
|
1794
|
-
const raw = JSON.parse(
|
|
2489
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2490
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
|
|
1795
2491
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1796
2492
|
const parts = raw.token.split(".");
|
|
1797
2493
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1829,8 +2525,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1829
2525
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1830
2526
|
let count = 0;
|
|
1831
2527
|
try {
|
|
1832
|
-
if (
|
|
1833
|
-
const raw =
|
|
2528
|
+
if (existsSync8(filePath)) {
|
|
2529
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
1834
2530
|
const employees = JSON.parse(raw);
|
|
1835
2531
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1836
2532
|
}
|
|
@@ -1859,19 +2555,19 @@ var init_plan_limits = __esm({
|
|
|
1859
2555
|
this.name = "PlanLimitError";
|
|
1860
2556
|
}
|
|
1861
2557
|
};
|
|
1862
|
-
CACHE_PATH2 =
|
|
2558
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
1863
2559
|
}
|
|
1864
2560
|
});
|
|
1865
2561
|
|
|
1866
2562
|
// src/lib/notifications.ts
|
|
1867
2563
|
import crypto from "crypto";
|
|
1868
|
-
import
|
|
2564
|
+
import path10 from "path";
|
|
1869
2565
|
import os6 from "os";
|
|
1870
2566
|
import {
|
|
1871
|
-
readFileSync as
|
|
2567
|
+
readFileSync as readFileSync9,
|
|
1872
2568
|
readdirSync,
|
|
1873
|
-
unlinkSync as
|
|
1874
|
-
existsSync as
|
|
2569
|
+
unlinkSync as unlinkSync3,
|
|
2570
|
+
existsSync as existsSync9,
|
|
1875
2571
|
rmdirSync
|
|
1876
2572
|
} from "fs";
|
|
1877
2573
|
async function writeNotification(notification) {
|
|
@@ -2035,10 +2731,11 @@ var init_state_bus = __esm({
|
|
|
2035
2731
|
|
|
2036
2732
|
// src/lib/tasks-crud.ts
|
|
2037
2733
|
import crypto3 from "crypto";
|
|
2038
|
-
import
|
|
2734
|
+
import path11 from "path";
|
|
2735
|
+
import os7 from "os";
|
|
2039
2736
|
import { execSync as execSync5 } from "child_process";
|
|
2040
2737
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2041
|
-
import { existsSync as
|
|
2738
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
2042
2739
|
async function writeCheckpoint(input) {
|
|
2043
2740
|
const client = getClient();
|
|
2044
2741
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2079,6 +2776,35 @@ function extractParentFromContext(contextBody) {
|
|
|
2079
2776
|
function slugify(title) {
|
|
2080
2777
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2081
2778
|
}
|
|
2779
|
+
function buildKeywordIndex() {
|
|
2780
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2781
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2782
|
+
for (const kw of keywords) {
|
|
2783
|
+
const existing = idx.get(kw) ?? [];
|
|
2784
|
+
existing.push(role);
|
|
2785
|
+
idx.set(kw, existing);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return idx;
|
|
2789
|
+
}
|
|
2790
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2791
|
+
const employees = loadEmployeesSync();
|
|
2792
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2793
|
+
if (!employee) return void 0;
|
|
2794
|
+
const assigneeRole = employee.role;
|
|
2795
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2796
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2797
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2798
|
+
if (text.includes(keyword)) {
|
|
2799
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2803
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2804
|
+
if (assigneeRole === "COO") return void 0;
|
|
2805
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2806
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2807
|
+
}
|
|
2082
2808
|
async function resolveTask(client, identifier, scopeSession) {
|
|
2083
2809
|
const scope = sessionScopeFilter(scopeSession);
|
|
2084
2810
|
let result = await client.execute({
|
|
@@ -2128,7 +2854,14 @@ async function createTaskCore(input) {
|
|
|
2128
2854
|
const id = crypto3.randomUUID();
|
|
2129
2855
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2130
2856
|
const slug = slugify(input.title);
|
|
2131
|
-
|
|
2857
|
+
let earlySessionScope = null;
|
|
2858
|
+
try {
|
|
2859
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2860
|
+
earlySessionScope = resolveExeSession2();
|
|
2861
|
+
} catch {
|
|
2862
|
+
}
|
|
2863
|
+
const scope = earlySessionScope ?? "default";
|
|
2864
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
2132
2865
|
let blockedById = null;
|
|
2133
2866
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
2134
2867
|
if (input.blockedBy) {
|
|
@@ -2168,22 +2901,24 @@ async function createTaskCore(input) {
|
|
|
2168
2901
|
if (dupCheck.rows.length > 0) {
|
|
2169
2902
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
2170
2903
|
}
|
|
2904
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2905
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2906
|
+
if (laneWarning) {
|
|
2907
|
+
warning = warning ? `${warning}
|
|
2908
|
+
${laneWarning}` : laneWarning;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2171
2911
|
if (input.baseDir) {
|
|
2172
2912
|
try {
|
|
2173
|
-
await mkdir3(
|
|
2174
|
-
await mkdir3(
|
|
2913
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2914
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2175
2915
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2176
2916
|
await ensureGitignoreExe(input.baseDir);
|
|
2177
2917
|
} catch {
|
|
2178
2918
|
}
|
|
2179
2919
|
}
|
|
2180
2920
|
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
|
-
}
|
|
2921
|
+
const sessionScope = earlySessionScope;
|
|
2187
2922
|
await client.execute({
|
|
2188
2923
|
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
2924
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2210,6 +2945,43 @@ async function createTaskCore(input) {
|
|
|
2210
2945
|
now
|
|
2211
2946
|
]
|
|
2212
2947
|
});
|
|
2948
|
+
if (input.baseDir) {
|
|
2949
|
+
try {
|
|
2950
|
+
const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
|
|
2951
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2952
|
+
const mdDir = path11.dirname(mdPath);
|
|
2953
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2954
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2955
|
+
const mdContent = `# ${input.title}
|
|
2956
|
+
|
|
2957
|
+
**ID:** ${id}
|
|
2958
|
+
**Status:** ${initialStatus}
|
|
2959
|
+
**Priority:** ${input.priority}
|
|
2960
|
+
**Assigned by:** ${input.assignedBy}
|
|
2961
|
+
**Assigned to:** ${input.assignedTo}
|
|
2962
|
+
**Project:** ${input.projectName}
|
|
2963
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2964
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2965
|
+
**Reviewer:** ${reviewer}
|
|
2966
|
+
|
|
2967
|
+
## Context
|
|
2968
|
+
|
|
2969
|
+
${input.context}
|
|
2970
|
+
|
|
2971
|
+
## MANDATORY: When done
|
|
2972
|
+
|
|
2973
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2974
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2975
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2976
|
+
`;
|
|
2977
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2978
|
+
} catch (err) {
|
|
2979
|
+
process.stderr.write(
|
|
2980
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2981
|
+
`
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2213
2985
|
return {
|
|
2214
2986
|
id,
|
|
2215
2987
|
title: input.title,
|
|
@@ -2402,7 +3174,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2402
3174
|
return { row, taskFile, now, taskId };
|
|
2403
3175
|
}
|
|
2404
3176
|
}
|
|
2405
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
3177
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2406
3178
|
process.stderr.write(
|
|
2407
3179
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2408
3180
|
`
|
|
@@ -2467,9 +3239,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2467
3239
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2468
3240
|
}
|
|
2469
3241
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2470
|
-
const archPath =
|
|
3242
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2471
3243
|
try {
|
|
2472
|
-
if (
|
|
3244
|
+
if (existsSync10(archPath)) return;
|
|
2473
3245
|
const template = [
|
|
2474
3246
|
`# ${projectName} \u2014 System Architecture`,
|
|
2475
3247
|
"",
|
|
@@ -2502,10 +3274,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2502
3274
|
}
|
|
2503
3275
|
}
|
|
2504
3276
|
async function ensureGitignoreExe(baseDir) {
|
|
2505
|
-
const gitignorePath =
|
|
3277
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2506
3278
|
try {
|
|
2507
|
-
if (
|
|
2508
|
-
const content =
|
|
3279
|
+
if (existsSync10(gitignorePath)) {
|
|
3280
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2509
3281
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2510
3282
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2511
3283
|
} else {
|
|
@@ -2514,20 +3286,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2514
3286
|
} catch {
|
|
2515
3287
|
}
|
|
2516
3288
|
}
|
|
2517
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3289
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2518
3290
|
var init_tasks_crud = __esm({
|
|
2519
3291
|
"src/lib/tasks-crud.ts"() {
|
|
2520
3292
|
"use strict";
|
|
2521
3293
|
init_database();
|
|
2522
3294
|
init_task_scope();
|
|
3295
|
+
init_employees();
|
|
3296
|
+
LANE_KEYWORDS = {
|
|
3297
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
3298
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
3299
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
3300
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
3301
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
3302
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
3303
|
+
};
|
|
3304
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2523
3305
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2524
3306
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2525
3307
|
}
|
|
2526
3308
|
});
|
|
2527
3309
|
|
|
2528
3310
|
// src/lib/tasks-review.ts
|
|
2529
|
-
import
|
|
2530
|
-
import { existsSync as
|
|
3311
|
+
import path12 from "path";
|
|
3312
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2531
3313
|
async function countPendingReviews(sessionScope) {
|
|
2532
3314
|
const client = getClient();
|
|
2533
3315
|
if (sessionScope) {
|
|
@@ -2549,7 +3331,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2549
3331
|
const result2 = await client.execute({
|
|
2550
3332
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2551
3333
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2552
|
-
AND
|
|
3334
|
+
AND session_scope = ?`,
|
|
2553
3335
|
args: [sinceIso, sessionScope]
|
|
2554
3336
|
});
|
|
2555
3337
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2567,7 +3349,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2567
3349
|
const result2 = await client.execute({
|
|
2568
3350
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2569
3351
|
WHERE status = 'needs_review'
|
|
2570
|
-
AND
|
|
3352
|
+
AND session_scope = ?
|
|
2571
3353
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2572
3354
|
args: [sessionScope, limit]
|
|
2573
3355
|
});
|
|
@@ -2688,14 +3470,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2688
3470
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2689
3471
|
const agent = parts[1];
|
|
2690
3472
|
const slug = parts.slice(2).join("-");
|
|
2691
|
-
const
|
|
3473
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2692
3474
|
const result = await client.execute({
|
|
2693
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2694
|
-
args: [now,
|
|
3475
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3476
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2695
3477
|
});
|
|
2696
3478
|
if (result.rowsAffected > 0) {
|
|
2697
3479
|
process.stderr.write(
|
|
2698
|
-
`[review-cleanup] Cascaded original task to done
|
|
3480
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2699
3481
|
`
|
|
2700
3482
|
);
|
|
2701
3483
|
}
|
|
@@ -2708,11 +3490,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2708
3490
|
);
|
|
2709
3491
|
}
|
|
2710
3492
|
try {
|
|
2711
|
-
const cacheDir =
|
|
2712
|
-
if (
|
|
3493
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
3494
|
+
if (existsSync11(cacheDir)) {
|
|
2713
3495
|
for (const f of readdirSync2(cacheDir)) {
|
|
2714
3496
|
if (f.startsWith("review-notified-")) {
|
|
2715
|
-
|
|
3497
|
+
unlinkSync4(path12.join(cacheDir, f));
|
|
2716
3498
|
}
|
|
2717
3499
|
}
|
|
2718
3500
|
}
|
|
@@ -2733,7 +3515,7 @@ var init_tasks_review = __esm({
|
|
|
2733
3515
|
});
|
|
2734
3516
|
|
|
2735
3517
|
// src/lib/tasks-chain.ts
|
|
2736
|
-
import
|
|
3518
|
+
import path13 from "path";
|
|
2737
3519
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2738
3520
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2739
3521
|
const client = getClient();
|
|
@@ -2750,7 +3532,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2750
3532
|
});
|
|
2751
3533
|
for (const ur of unblockedRows.rows) {
|
|
2752
3534
|
try {
|
|
2753
|
-
const ubFile =
|
|
3535
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
2754
3536
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2755
3537
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2756
3538
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2819,7 +3601,7 @@ var init_tasks_chain = __esm({
|
|
|
2819
3601
|
|
|
2820
3602
|
// src/lib/project-name.ts
|
|
2821
3603
|
import { execSync as execSync6 } from "child_process";
|
|
2822
|
-
import
|
|
3604
|
+
import path14 from "path";
|
|
2823
3605
|
function getProjectName(cwd) {
|
|
2824
3606
|
const dir = cwd ?? process.cwd();
|
|
2825
3607
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2832,7 +3614,7 @@ function getProjectName(cwd) {
|
|
|
2832
3614
|
timeout: 2e3,
|
|
2833
3615
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2834
3616
|
}).trim();
|
|
2835
|
-
repoRoot =
|
|
3617
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
2836
3618
|
} catch {
|
|
2837
3619
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2838
3620
|
cwd: dir,
|
|
@@ -2841,11 +3623,11 @@ function getProjectName(cwd) {
|
|
|
2841
3623
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2842
3624
|
}).trim();
|
|
2843
3625
|
}
|
|
2844
|
-
_cached2 =
|
|
3626
|
+
_cached2 = path14.basename(repoRoot);
|
|
2845
3627
|
_cachedCwd = dir;
|
|
2846
3628
|
return _cached2;
|
|
2847
3629
|
} catch {
|
|
2848
|
-
_cached2 =
|
|
3630
|
+
_cached2 = path14.basename(dir);
|
|
2849
3631
|
_cachedCwd = dir;
|
|
2850
3632
|
return _cached2;
|
|
2851
3633
|
}
|
|
@@ -2877,7 +3659,7 @@ function findSessionForProject(projectName) {
|
|
|
2877
3659
|
const sessions = listSessions();
|
|
2878
3660
|
for (const s of sessions) {
|
|
2879
3661
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2880
|
-
if (proj === projectName &&
|
|
3662
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2881
3663
|
}
|
|
2882
3664
|
return null;
|
|
2883
3665
|
}
|
|
@@ -2923,7 +3705,7 @@ var init_session_scope = __esm({
|
|
|
2923
3705
|
|
|
2924
3706
|
// src/lib/tasks-notify.ts
|
|
2925
3707
|
async function dispatchTaskToEmployee(input) {
|
|
2926
|
-
if (
|
|
3708
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2927
3709
|
let crossProject = false;
|
|
2928
3710
|
if (input.projectName) {
|
|
2929
3711
|
try {
|
|
@@ -3380,8 +4162,8 @@ __export(tasks_exports, {
|
|
|
3380
4162
|
updateTaskStatus: () => updateTaskStatus,
|
|
3381
4163
|
writeCheckpoint: () => writeCheckpoint
|
|
3382
4164
|
});
|
|
3383
|
-
import
|
|
3384
|
-
import { writeFileSync as
|
|
4165
|
+
import path15 from "path";
|
|
4166
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3385
4167
|
async function createTask(input) {
|
|
3386
4168
|
const result = await createTaskCore(input);
|
|
3387
4169
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3400,14 +4182,14 @@ async function updateTask(input) {
|
|
|
3400
4182
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3401
4183
|
try {
|
|
3402
4184
|
const agent = String(row.assigned_to);
|
|
3403
|
-
const cacheDir =
|
|
3404
|
-
const cachePath =
|
|
4185
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
4186
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
3405
4187
|
if (input.status === "in_progress") {
|
|
3406
|
-
|
|
3407
|
-
|
|
4188
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
4189
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3408
4190
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3409
4191
|
try {
|
|
3410
|
-
|
|
4192
|
+
unlinkSync5(cachePath);
|
|
3411
4193
|
} catch {
|
|
3412
4194
|
}
|
|
3413
4195
|
}
|
|
@@ -3464,7 +4246,7 @@ async function updateTask(input) {
|
|
|
3464
4246
|
}
|
|
3465
4247
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3466
4248
|
if (isTerminal) {
|
|
3467
|
-
const isCoordinator =
|
|
4249
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3468
4250
|
if (!isCoordinator) {
|
|
3469
4251
|
notifyTaskDone();
|
|
3470
4252
|
}
|
|
@@ -3489,7 +4271,7 @@ async function updateTask(input) {
|
|
|
3489
4271
|
}
|
|
3490
4272
|
}
|
|
3491
4273
|
}
|
|
3492
|
-
if (input.status === "done" &&
|
|
4274
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3493
4275
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3494
4276
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3495
4277
|
taskId,
|
|
@@ -3505,7 +4287,7 @@ async function updateTask(input) {
|
|
|
3505
4287
|
});
|
|
3506
4288
|
}
|
|
3507
4289
|
let nextTask;
|
|
3508
|
-
if (isTerminal &&
|
|
4290
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3509
4291
|
try {
|
|
3510
4292
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3511
4293
|
} catch {
|
|
@@ -3849,7 +4631,7 @@ var init_capacity_monitor = __esm({
|
|
|
3849
4631
|
// src/lib/tmux-routing.ts
|
|
3850
4632
|
var tmux_routing_exports = {};
|
|
3851
4633
|
__export(tmux_routing_exports, {
|
|
3852
|
-
acquireSpawnLock: () =>
|
|
4634
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3853
4635
|
employeeSessionName: () => employeeSessionName,
|
|
3854
4636
|
ensureEmployee: () => ensureEmployee,
|
|
3855
4637
|
extractRootExe: () => extractRootExe,
|
|
@@ -3864,20 +4646,20 @@ __export(tmux_routing_exports, {
|
|
|
3864
4646
|
notifyParentExe: () => notifyParentExe,
|
|
3865
4647
|
parseParentExe: () => parseParentExe,
|
|
3866
4648
|
registerParentExe: () => registerParentExe,
|
|
3867
|
-
releaseSpawnLock: () =>
|
|
4649
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3868
4650
|
resolveExeSession: () => resolveExeSession,
|
|
3869
4651
|
sendIntercom: () => sendIntercom,
|
|
3870
4652
|
spawnEmployee: () => spawnEmployee,
|
|
3871
4653
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3872
4654
|
});
|
|
3873
4655
|
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
|
|
4656
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4657
|
+
import path16 from "path";
|
|
4658
|
+
import os8 from "os";
|
|
4659
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4660
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3879
4661
|
function spawnLockPath(sessionName) {
|
|
3880
|
-
return
|
|
4662
|
+
return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3881
4663
|
}
|
|
3882
4664
|
function isProcessAlive(pid) {
|
|
3883
4665
|
try {
|
|
@@ -3887,14 +4669,14 @@ function isProcessAlive(pid) {
|
|
|
3887
4669
|
return false;
|
|
3888
4670
|
}
|
|
3889
4671
|
}
|
|
3890
|
-
function
|
|
3891
|
-
if (!
|
|
3892
|
-
|
|
4672
|
+
function acquireSpawnLock2(sessionName) {
|
|
4673
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4674
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3893
4675
|
}
|
|
3894
4676
|
const lockFile = spawnLockPath(sessionName);
|
|
3895
|
-
if (
|
|
4677
|
+
if (existsSync12(lockFile)) {
|
|
3896
4678
|
try {
|
|
3897
|
-
const lock = JSON.parse(
|
|
4679
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
3898
4680
|
const age = Date.now() - lock.timestamp;
|
|
3899
4681
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3900
4682
|
return false;
|
|
@@ -3902,25 +4684,25 @@ function acquireSpawnLock(sessionName) {
|
|
|
3902
4684
|
} catch {
|
|
3903
4685
|
}
|
|
3904
4686
|
}
|
|
3905
|
-
|
|
4687
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3906
4688
|
return true;
|
|
3907
4689
|
}
|
|
3908
|
-
function
|
|
4690
|
+
function releaseSpawnLock2(sessionName) {
|
|
3909
4691
|
try {
|
|
3910
|
-
|
|
4692
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3911
4693
|
} catch {
|
|
3912
4694
|
}
|
|
3913
4695
|
}
|
|
3914
4696
|
function resolveBehaviorsExporterScript() {
|
|
3915
4697
|
try {
|
|
3916
|
-
const thisFile =
|
|
3917
|
-
const scriptPath =
|
|
3918
|
-
|
|
4698
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4699
|
+
const scriptPath = path16.join(
|
|
4700
|
+
path16.dirname(thisFile),
|
|
3919
4701
|
"..",
|
|
3920
4702
|
"bin",
|
|
3921
4703
|
"exe-export-behaviors.js"
|
|
3922
4704
|
);
|
|
3923
|
-
return
|
|
4705
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3924
4706
|
} catch {
|
|
3925
4707
|
return null;
|
|
3926
4708
|
}
|
|
@@ -3986,12 +4768,12 @@ function extractRootExe(name) {
|
|
|
3986
4768
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3987
4769
|
}
|
|
3988
4770
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3989
|
-
if (!
|
|
3990
|
-
|
|
4771
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4772
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3991
4773
|
}
|
|
3992
4774
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3993
|
-
const filePath =
|
|
3994
|
-
|
|
4775
|
+
const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4776
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3995
4777
|
parentExe: rootExe,
|
|
3996
4778
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3997
4779
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3999,7 +4781,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3999
4781
|
}
|
|
4000
4782
|
function getParentExe(sessionKey) {
|
|
4001
4783
|
try {
|
|
4002
|
-
const data = JSON.parse(
|
|
4784
|
+
const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4003
4785
|
return data.parentExe || null;
|
|
4004
4786
|
} catch {
|
|
4005
4787
|
return null;
|
|
@@ -4007,8 +4789,8 @@ function getParentExe(sessionKey) {
|
|
|
4007
4789
|
}
|
|
4008
4790
|
function getDispatchedBy(sessionKey) {
|
|
4009
4791
|
try {
|
|
4010
|
-
const data = JSON.parse(
|
|
4011
|
-
|
|
4792
|
+
const data = JSON.parse(readFileSync11(
|
|
4793
|
+
path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4012
4794
|
"utf8"
|
|
4013
4795
|
));
|
|
4014
4796
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4034,10 +4816,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
4034
4816
|
}
|
|
4035
4817
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4036
4818
|
const base = employeeSessionName(employeeName, exeSession);
|
|
4037
|
-
if (!isAlive(base) &&
|
|
4819
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4038
4820
|
for (let i = 2; i <= maxInstances; i++) {
|
|
4039
4821
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4040
|
-
if (!isAlive(candidate) &&
|
|
4822
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4041
4823
|
}
|
|
4042
4824
|
return null;
|
|
4043
4825
|
}
|
|
@@ -4069,32 +4851,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4069
4851
|
}
|
|
4070
4852
|
function readDebounceState() {
|
|
4071
4853
|
try {
|
|
4072
|
-
if (!
|
|
4073
|
-
|
|
4854
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4855
|
+
const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4856
|
+
const state = {};
|
|
4857
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4858
|
+
if (typeof val === "number") {
|
|
4859
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4860
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4861
|
+
state[key] = val;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
return state;
|
|
4074
4865
|
} catch {
|
|
4075
4866
|
return {};
|
|
4076
4867
|
}
|
|
4077
4868
|
}
|
|
4078
4869
|
function writeDebounceState(state) {
|
|
4079
4870
|
try {
|
|
4080
|
-
if (!
|
|
4081
|
-
|
|
4871
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4872
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4082
4873
|
} catch {
|
|
4083
4874
|
}
|
|
4084
4875
|
}
|
|
4085
4876
|
function isDebounced(targetSession) {
|
|
4086
4877
|
const state = readDebounceState();
|
|
4087
|
-
const
|
|
4088
|
-
|
|
4878
|
+
const entry = state[targetSession];
|
|
4879
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4880
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4881
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4882
|
+
state[targetSession].pending++;
|
|
4883
|
+
writeDebounceState(state);
|
|
4884
|
+
return true;
|
|
4885
|
+
}
|
|
4886
|
+
return false;
|
|
4089
4887
|
}
|
|
4090
4888
|
function recordDebounce(targetSession) {
|
|
4091
4889
|
const state = readDebounceState();
|
|
4092
|
-
state[targetSession]
|
|
4890
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4891
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
4093
4892
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
4094
4893
|
for (const key of Object.keys(state)) {
|
|
4095
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4894
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
4096
4895
|
}
|
|
4097
4896
|
writeDebounceState(state);
|
|
4897
|
+
return batched;
|
|
4098
4898
|
}
|
|
4099
4899
|
function logIntercom(msg) {
|
|
4100
4900
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -4139,7 +4939,7 @@ function sendIntercom(targetSession) {
|
|
|
4139
4939
|
return "skipped_exe";
|
|
4140
4940
|
}
|
|
4141
4941
|
if (isDebounced(targetSession)) {
|
|
4142
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4942
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
4143
4943
|
return "debounced";
|
|
4144
4944
|
}
|
|
4145
4945
|
try {
|
|
@@ -4151,14 +4951,14 @@ function sendIntercom(targetSession) {
|
|
|
4151
4951
|
const sessionState = getSessionState(targetSession);
|
|
4152
4952
|
if (sessionState === "no_claude") {
|
|
4153
4953
|
queueIntercom(targetSession, "claude not running in session");
|
|
4154
|
-
recordDebounce(targetSession);
|
|
4155
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4954
|
+
const batched2 = recordDebounce(targetSession);
|
|
4955
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
4156
4956
|
return "queued";
|
|
4157
4957
|
}
|
|
4158
4958
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
4159
4959
|
queueIntercom(targetSession, "session busy at send time");
|
|
4160
|
-
recordDebounce(targetSession);
|
|
4161
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4960
|
+
const batched2 = recordDebounce(targetSession);
|
|
4961
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
4162
4962
|
return "queued";
|
|
4163
4963
|
}
|
|
4164
4964
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -4166,8 +4966,8 @@ function sendIntercom(targetSession) {
|
|
|
4166
4966
|
transport.sendKeys(targetSession, "q");
|
|
4167
4967
|
}
|
|
4168
4968
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4169
|
-
recordDebounce(targetSession);
|
|
4170
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4969
|
+
const batched = recordDebounce(targetSession);
|
|
4970
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
4171
4971
|
return "delivered";
|
|
4172
4972
|
} catch {
|
|
4173
4973
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -4197,7 +4997,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4197
4997
|
return true;
|
|
4198
4998
|
}
|
|
4199
4999
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4200
|
-
if (
|
|
5000
|
+
if (isCoordinatorName(employeeName)) {
|
|
4201
5001
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4202
5002
|
}
|
|
4203
5003
|
try {
|
|
@@ -4269,26 +5069,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4269
5069
|
const transport = getTransport();
|
|
4270
5070
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4271
5071
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4272
|
-
const logDir =
|
|
4273
|
-
const logFile =
|
|
4274
|
-
if (!
|
|
4275
|
-
|
|
5072
|
+
const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
|
|
5073
|
+
const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5074
|
+
if (!existsSync12(logDir)) {
|
|
5075
|
+
mkdirSync6(logDir, { recursive: true });
|
|
4276
5076
|
}
|
|
4277
5077
|
transport.kill(sessionName);
|
|
4278
5078
|
let cleanupSuffix = "";
|
|
4279
5079
|
try {
|
|
4280
|
-
const thisFile =
|
|
4281
|
-
const cleanupScript =
|
|
4282
|
-
if (
|
|
5080
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
5081
|
+
const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5082
|
+
if (existsSync12(cleanupScript)) {
|
|
4283
5083
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4284
5084
|
}
|
|
4285
5085
|
} catch {
|
|
4286
5086
|
}
|
|
4287
5087
|
try {
|
|
4288
|
-
const claudeJsonPath =
|
|
5088
|
+
const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
|
|
4289
5089
|
let claudeJson = {};
|
|
4290
5090
|
try {
|
|
4291
|
-
claudeJson = JSON.parse(
|
|
5091
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
4292
5092
|
} catch {
|
|
4293
5093
|
}
|
|
4294
5094
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4296,17 +5096,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4296
5096
|
const trustDir = opts?.cwd ?? projectDir;
|
|
4297
5097
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4298
5098
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4299
|
-
|
|
5099
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4300
5100
|
} catch {
|
|
4301
5101
|
}
|
|
4302
5102
|
try {
|
|
4303
|
-
const settingsDir =
|
|
5103
|
+
const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
|
|
4304
5104
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4305
|
-
const projSettingsDir =
|
|
4306
|
-
const settingsPath =
|
|
5105
|
+
const projSettingsDir = path16.join(settingsDir, normalizedKey);
|
|
5106
|
+
const settingsPath = path16.join(projSettingsDir, "settings.json");
|
|
4307
5107
|
let settings = {};
|
|
4308
5108
|
try {
|
|
4309
|
-
settings = JSON.parse(
|
|
5109
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
4310
5110
|
} catch {
|
|
4311
5111
|
}
|
|
4312
5112
|
const perms = settings.permissions ?? {};
|
|
@@ -4334,21 +5134,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4334
5134
|
if (changed) {
|
|
4335
5135
|
perms.allow = allow;
|
|
4336
5136
|
settings.permissions = perms;
|
|
4337
|
-
|
|
4338
|
-
|
|
5137
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
5138
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4339
5139
|
}
|
|
4340
5140
|
} catch {
|
|
4341
5141
|
}
|
|
4342
5142
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4343
5143
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4344
|
-
const
|
|
5144
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
5145
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
5146
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
5147
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4345
5148
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4346
5149
|
let identityFlag = "";
|
|
4347
5150
|
let behaviorsFlag = "";
|
|
4348
5151
|
let legacyFallbackWarned = false;
|
|
4349
5152
|
if (!useExeAgent && !useBinSymlink) {
|
|
4350
|
-
const identityPath =
|
|
4351
|
-
|
|
5153
|
+
const identityPath = path16.join(
|
|
5154
|
+
os8.homedir(),
|
|
4352
5155
|
".exe-os",
|
|
4353
5156
|
"identity",
|
|
4354
5157
|
`${employeeName}.md`
|
|
@@ -4357,13 +5160,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4357
5160
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4358
5161
|
if (hasAgentFlag) {
|
|
4359
5162
|
identityFlag = ` --agent ${employeeName}`;
|
|
4360
|
-
} else if (
|
|
5163
|
+
} else if (existsSync12(identityPath)) {
|
|
4361
5164
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4362
5165
|
legacyFallbackWarned = true;
|
|
4363
5166
|
}
|
|
4364
5167
|
const behaviorsFile = exportBehaviorsSync(
|
|
4365
5168
|
employeeName,
|
|
4366
|
-
|
|
5169
|
+
path16.basename(spawnCwd),
|
|
4367
5170
|
sessionName
|
|
4368
5171
|
);
|
|
4369
5172
|
if (behaviorsFile) {
|
|
@@ -4378,16 +5181,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4378
5181
|
}
|
|
4379
5182
|
let sessionContextFlag = "";
|
|
4380
5183
|
try {
|
|
4381
|
-
const ctxDir =
|
|
4382
|
-
|
|
4383
|
-
const ctxFile =
|
|
5184
|
+
const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
5185
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
5186
|
+
const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4384
5187
|
const ctxContent = [
|
|
4385
5188
|
`## Session Context`,
|
|
4386
5189
|
`You are running in tmux session: ${sessionName}.`,
|
|
4387
5190
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4388
5191
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4389
5192
|
].join("\n");
|
|
4390
|
-
|
|
5193
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4391
5194
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4392
5195
|
} catch {
|
|
4393
5196
|
}
|
|
@@ -4401,9 +5204,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4401
5204
|
}
|
|
4402
5205
|
}
|
|
4403
5206
|
}
|
|
5207
|
+
if (useCodex) {
|
|
5208
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
5209
|
+
if (codexCfg?.apiKeyEnv) {
|
|
5210
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
5211
|
+
if (keyVal) {
|
|
5212
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
5216
|
+
}
|
|
5217
|
+
if (useOpencode) {
|
|
5218
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
5219
|
+
if (ocCfg?.apiKeyEnv) {
|
|
5220
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
5221
|
+
if (keyVal) {
|
|
5222
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
5226
|
+
}
|
|
5227
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
5228
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
5229
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
5230
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
4404
5233
|
let spawnCommand;
|
|
4405
5234
|
if (useExeAgent) {
|
|
4406
5235
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
5236
|
+
} else if (useCodex) {
|
|
5237
|
+
process.stderr.write(
|
|
5238
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
5239
|
+
`
|
|
5240
|
+
);
|
|
5241
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
5242
|
+
} else if (useOpencode) {
|
|
5243
|
+
const binName = `${employeeName}-opencode`;
|
|
5244
|
+
process.stderr.write(
|
|
5245
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
5246
|
+
`
|
|
5247
|
+
);
|
|
5248
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4407
5249
|
} else if (useBinSymlink) {
|
|
4408
5250
|
const binName = `${employeeName}-${ccProvider}`;
|
|
4409
5251
|
process.stderr.write(
|
|
@@ -4419,17 +5261,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4419
5261
|
command: spawnCommand
|
|
4420
5262
|
});
|
|
4421
5263
|
if (spawnResult.error) {
|
|
4422
|
-
|
|
5264
|
+
releaseSpawnLock2(sessionName);
|
|
4423
5265
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4424
5266
|
}
|
|
4425
5267
|
transport.pipeLog(sessionName, logFile);
|
|
4426
5268
|
try {
|
|
4427
5269
|
const mySession = getMySession();
|
|
4428
|
-
const dispatchInfo =
|
|
4429
|
-
|
|
5270
|
+
const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5271
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4430
5272
|
dispatchedBy: mySession,
|
|
4431
5273
|
rootExe: exeSession,
|
|
4432
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
5274
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
5275
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
5276
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4433
5277
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4434
5278
|
}));
|
|
4435
5279
|
} catch {
|
|
@@ -4447,6 +5291,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4447
5291
|
booted = true;
|
|
4448
5292
|
break;
|
|
4449
5293
|
}
|
|
5294
|
+
} else if (useCodex) {
|
|
5295
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
5296
|
+
booted = true;
|
|
5297
|
+
break;
|
|
5298
|
+
}
|
|
4450
5299
|
} else {
|
|
4451
5300
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4452
5301
|
booted = true;
|
|
@@ -4457,10 +5306,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4457
5306
|
}
|
|
4458
5307
|
}
|
|
4459
5308
|
if (!booted) {
|
|
4460
|
-
|
|
4461
|
-
|
|
5309
|
+
releaseSpawnLock2(sessionName);
|
|
5310
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
5311
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4462
5312
|
}
|
|
4463
|
-
if (!useExeAgent) {
|
|
5313
|
+
if (!useExeAgent && !useCodex) {
|
|
4464
5314
|
try {
|
|
4465
5315
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
4466
5316
|
} catch {
|
|
@@ -4474,7 +5324,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4474
5324
|
pid: 0,
|
|
4475
5325
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4476
5326
|
});
|
|
4477
|
-
|
|
5327
|
+
releaseSpawnLock2(sessionName);
|
|
4478
5328
|
return { sessionName };
|
|
4479
5329
|
}
|
|
4480
5330
|
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;
|
|
@@ -4487,17 +5337,19 @@ var init_tmux_routing = __esm({
|
|
|
4487
5337
|
init_cc_agent_support();
|
|
4488
5338
|
init_mcp_prefix();
|
|
4489
5339
|
init_provider_table();
|
|
5340
|
+
init_agent_config();
|
|
5341
|
+
init_runtime_table();
|
|
4490
5342
|
init_intercom_queue();
|
|
4491
5343
|
init_plan_limits();
|
|
4492
5344
|
init_employees();
|
|
4493
|
-
SPAWN_LOCK_DIR =
|
|
4494
|
-
SESSION_CACHE =
|
|
5345
|
+
SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
5346
|
+
SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4495
5347
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4496
5348
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4497
5349
|
VERIFY_PANE_LINES = 200;
|
|
4498
5350
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4499
|
-
INTERCOM_LOG2 =
|
|
4500
|
-
DEBOUNCE_FILE =
|
|
5351
|
+
INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
5352
|
+
DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4501
5353
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4502
5354
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4503
5355
|
}
|
|
@@ -4514,14 +5366,14 @@ var init_memory = __esm({
|
|
|
4514
5366
|
|
|
4515
5367
|
// src/lib/keychain.ts
|
|
4516
5368
|
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
|
|
5369
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5370
|
+
import path17 from "path";
|
|
5371
|
+
import os9 from "os";
|
|
4520
5372
|
function getKeyDir() {
|
|
4521
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
5373
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
|
|
4522
5374
|
}
|
|
4523
5375
|
function getKeyPath() {
|
|
4524
|
-
return
|
|
5376
|
+
return path17.join(getKeyDir(), "master.key");
|
|
4525
5377
|
}
|
|
4526
5378
|
async function tryKeytar() {
|
|
4527
5379
|
try {
|
|
@@ -4542,13 +5394,21 @@ async function getMasterKey() {
|
|
|
4542
5394
|
}
|
|
4543
5395
|
}
|
|
4544
5396
|
const keyPath = getKeyPath();
|
|
4545
|
-
if (!
|
|
5397
|
+
if (!existsSync13(keyPath)) {
|
|
5398
|
+
process.stderr.write(
|
|
5399
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
5400
|
+
`
|
|
5401
|
+
);
|
|
4546
5402
|
return null;
|
|
4547
5403
|
}
|
|
4548
5404
|
try {
|
|
4549
5405
|
const content = await readFile4(keyPath, "utf-8");
|
|
4550
5406
|
return Buffer.from(content.trim(), "base64");
|
|
4551
|
-
} catch {
|
|
5407
|
+
} catch (err) {
|
|
5408
|
+
process.stderr.write(
|
|
5409
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
5410
|
+
`
|
|
5411
|
+
);
|
|
4552
5412
|
return null;
|
|
4553
5413
|
}
|
|
4554
5414
|
}
|
|
@@ -4574,13 +5434,13 @@ __export(shard_manager_exports, {
|
|
|
4574
5434
|
listShards: () => listShards,
|
|
4575
5435
|
shardExists: () => shardExists
|
|
4576
5436
|
});
|
|
4577
|
-
import
|
|
4578
|
-
import { existsSync as
|
|
5437
|
+
import path18 from "path";
|
|
5438
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync3 } from "fs";
|
|
4579
5439
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4580
5440
|
function initShardManager(encryptionKey) {
|
|
4581
5441
|
_encryptionKey = encryptionKey;
|
|
4582
|
-
if (!
|
|
4583
|
-
|
|
5442
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5443
|
+
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4584
5444
|
}
|
|
4585
5445
|
_shardingEnabled = true;
|
|
4586
5446
|
}
|
|
@@ -4600,7 +5460,7 @@ function getShardClient(projectName) {
|
|
|
4600
5460
|
}
|
|
4601
5461
|
const cached = _shards.get(safeName);
|
|
4602
5462
|
if (cached) return cached;
|
|
4603
|
-
const dbPath =
|
|
5463
|
+
const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
|
|
4604
5464
|
const client = createClient2({
|
|
4605
5465
|
url: `file:${dbPath}`,
|
|
4606
5466
|
encryptionKey: _encryptionKey
|
|
@@ -4610,10 +5470,10 @@ function getShardClient(projectName) {
|
|
|
4610
5470
|
}
|
|
4611
5471
|
function shardExists(projectName) {
|
|
4612
5472
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4613
|
-
return
|
|
5473
|
+
return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
|
|
4614
5474
|
}
|
|
4615
5475
|
function listShards() {
|
|
4616
|
-
if (!
|
|
5476
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4617
5477
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4618
5478
|
}
|
|
4619
5479
|
async function ensureShardSchema(client) {
|
|
@@ -4799,7 +5659,7 @@ var init_shard_manager = __esm({
|
|
|
4799
5659
|
"src/lib/shard-manager.ts"() {
|
|
4800
5660
|
"use strict";
|
|
4801
5661
|
init_config();
|
|
4802
|
-
SHARDS_DIR =
|
|
5662
|
+
SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
|
|
4803
5663
|
_shards = /* @__PURE__ */ new Map();
|
|
4804
5664
|
_encryptionKey = null;
|
|
4805
5665
|
_shardingEnabled = false;
|
|
@@ -4924,7 +5784,7 @@ __export(global_procedures_exports, {
|
|
|
4924
5784
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4925
5785
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4926
5786
|
});
|
|
4927
|
-
import { randomUUID as
|
|
5787
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4928
5788
|
async function loadGlobalProcedures() {
|
|
4929
5789
|
const client = getClient();
|
|
4930
5790
|
const result = await client.execute({
|
|
@@ -4953,7 +5813,7 @@ ${sections.join("\n\n")}
|
|
|
4953
5813
|
`;
|
|
4954
5814
|
}
|
|
4955
5815
|
async function storeGlobalProcedure(input) {
|
|
4956
|
-
const id =
|
|
5816
|
+
const id = randomUUID4();
|
|
4957
5817
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4958
5818
|
const client = getClient();
|
|
4959
5819
|
await client.execute({
|
|
@@ -5004,6 +5864,7 @@ __export(store_exports, {
|
|
|
5004
5864
|
vectorToBlob: () => vectorToBlob,
|
|
5005
5865
|
writeMemory: () => writeMemory
|
|
5006
5866
|
});
|
|
5867
|
+
import { createHash } from "crypto";
|
|
5007
5868
|
function isBusyError2(err) {
|
|
5008
5869
|
if (err instanceof Error) {
|
|
5009
5870
|
const msg = err.message.toLowerCase();
|
|
@@ -5077,12 +5938,52 @@ function classifyTier(record) {
|
|
|
5077
5938
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
5078
5939
|
return 3;
|
|
5079
5940
|
}
|
|
5941
|
+
function inferFilePaths(record) {
|
|
5942
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5943
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5944
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5945
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5946
|
+
}
|
|
5947
|
+
function inferCommitHash(record) {
|
|
5948
|
+
if (record.tool_name !== "Bash") return null;
|
|
5949
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5950
|
+
return match ? match[1] : null;
|
|
5951
|
+
}
|
|
5952
|
+
function inferLanguageType(record) {
|
|
5953
|
+
const text = record.raw_text;
|
|
5954
|
+
if (!text || text.length < 10) return null;
|
|
5955
|
+
const trimmed = text.trimStart();
|
|
5956
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5957
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5958
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5959
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5960
|
+
return "mixed";
|
|
5961
|
+
}
|
|
5962
|
+
function inferDomain(record) {
|
|
5963
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5964
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5965
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5966
|
+
return null;
|
|
5967
|
+
}
|
|
5080
5968
|
async function writeMemory(record) {
|
|
5081
5969
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
5082
5970
|
throw new Error(
|
|
5083
5971
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
5084
5972
|
);
|
|
5085
5973
|
}
|
|
5974
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5975
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
try {
|
|
5979
|
+
const client = getClient();
|
|
5980
|
+
const existing = await client.execute({
|
|
5981
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5982
|
+
args: [contentHash, record.agent_id]
|
|
5983
|
+
});
|
|
5984
|
+
if (existing.rows.length > 0) return;
|
|
5985
|
+
} catch {
|
|
5986
|
+
}
|
|
5086
5987
|
const dbRow = {
|
|
5087
5988
|
id: record.id,
|
|
5088
5989
|
agent_id: record.agent_id,
|
|
@@ -5112,7 +6013,23 @@ async function writeMemory(record) {
|
|
|
5112
6013
|
supersedes_id: record.supersedes_id ?? null,
|
|
5113
6014
|
draft: record.draft ? 1 : 0,
|
|
5114
6015
|
memory_type: record.memory_type ?? "raw",
|
|
5115
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
6016
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
6017
|
+
content_hash: contentHash,
|
|
6018
|
+
intent: record.intent ?? null,
|
|
6019
|
+
outcome: record.outcome ?? null,
|
|
6020
|
+
domain: record.domain ?? inferDomain(record),
|
|
6021
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
6022
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
6023
|
+
chain_position: record.chain_position ?? null,
|
|
6024
|
+
review_status: record.review_status ?? null,
|
|
6025
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
6026
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
6027
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
6028
|
+
duration_ms: record.duration_ms ?? null,
|
|
6029
|
+
token_cost: record.token_cost ?? null,
|
|
6030
|
+
audience: record.audience ?? null,
|
|
6031
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
6032
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
5116
6033
|
};
|
|
5117
6034
|
_pendingRecords.push(dbRow);
|
|
5118
6035
|
orgBus.emit({
|
|
@@ -5170,80 +6087,85 @@ async function flushBatch() {
|
|
|
5170
6087
|
const draft = row.draft ? 1 : 0;
|
|
5171
6088
|
const memoryType = row.memory_type ?? "raw";
|
|
5172
6089
|
const trajectory = row.trajectory ?? null;
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
6090
|
+
const contentHash = row.content_hash ?? null;
|
|
6091
|
+
const intent = row.intent ?? null;
|
|
6092
|
+
const outcome = row.outcome ?? null;
|
|
6093
|
+
const domain = row.domain ?? null;
|
|
6094
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
6095
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
6096
|
+
const chainPosition = row.chain_position ?? null;
|
|
6097
|
+
const reviewStatus = row.review_status ?? null;
|
|
6098
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
6099
|
+
const filePaths = row.file_paths ?? null;
|
|
6100
|
+
const commitHash = row.commit_hash ?? null;
|
|
6101
|
+
const durationMs = row.duration_ms ?? null;
|
|
6102
|
+
const tokenCost = row.token_cost ?? null;
|
|
6103
|
+
const audience = row.audience ?? null;
|
|
6104
|
+
const languageType = row.language_type ?? null;
|
|
6105
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
6106
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
5183
6107
|
tool_name, project_name,
|
|
5184
6108
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5185
6109
|
confidence, last_accessed,
|
|
5186
6110
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5187
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
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
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
trajectory
|
|
5246
|
-
]
|
|
6111
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
6112
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
6113
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
6114
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
6115
|
+
const metaArgs = [
|
|
6116
|
+
intent,
|
|
6117
|
+
outcome,
|
|
6118
|
+
domain,
|
|
6119
|
+
referencedEntities,
|
|
6120
|
+
retrievalCount,
|
|
6121
|
+
chainPosition,
|
|
6122
|
+
reviewStatus,
|
|
6123
|
+
contextWindowPct,
|
|
6124
|
+
filePaths,
|
|
6125
|
+
commitHash,
|
|
6126
|
+
durationMs,
|
|
6127
|
+
tokenCost,
|
|
6128
|
+
audience,
|
|
6129
|
+
languageType,
|
|
6130
|
+
parentMemoryId
|
|
6131
|
+
];
|
|
6132
|
+
const baseArgs = [
|
|
6133
|
+
row.id,
|
|
6134
|
+
row.agent_id,
|
|
6135
|
+
row.agent_role,
|
|
6136
|
+
row.session_id,
|
|
6137
|
+
row.timestamp,
|
|
6138
|
+
row.tool_name,
|
|
6139
|
+
row.project_name,
|
|
6140
|
+
row.has_error,
|
|
6141
|
+
row.raw_text
|
|
6142
|
+
];
|
|
6143
|
+
const sharedArgs = [
|
|
6144
|
+
row.version,
|
|
6145
|
+
taskId,
|
|
6146
|
+
importance,
|
|
6147
|
+
status,
|
|
6148
|
+
confidence,
|
|
6149
|
+
lastAccessed,
|
|
6150
|
+
workspaceId,
|
|
6151
|
+
documentId,
|
|
6152
|
+
userId,
|
|
6153
|
+
charOffset,
|
|
6154
|
+
pageNumber,
|
|
6155
|
+
sourcePath,
|
|
6156
|
+
sourceType,
|
|
6157
|
+
tier,
|
|
6158
|
+
supersedesId,
|
|
6159
|
+
draft,
|
|
6160
|
+
memoryType,
|
|
6161
|
+
trajectory,
|
|
6162
|
+
contentHash
|
|
6163
|
+
];
|
|
6164
|
+
return {
|
|
6165
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
6166
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
6167
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6168
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
5247
6169
|
};
|
|
5248
6170
|
};
|
|
5249
6171
|
const globalClient = getClient();
|
|
@@ -5526,8 +6448,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
5526
6448
|
try {
|
|
5527
6449
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
5528
6450
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
5529
|
-
const { readFileSync:
|
|
5530
|
-
const source =
|
|
6451
|
+
const { readFileSync: readFileSync14 } = __require("fs");
|
|
6452
|
+
const source = readFileSync14(filePath, "utf8");
|
|
5531
6453
|
const lines = source.split("\n");
|
|
5532
6454
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
5533
6455
|
let matchLine = -1;
|
|
@@ -5593,9 +6515,9 @@ function extractBash(input, response) {
|
|
|
5593
6515
|
}
|
|
5594
6516
|
function extractGrep(input, response) {
|
|
5595
6517
|
const pattern = String(input.pattern ?? "");
|
|
5596
|
-
const
|
|
6518
|
+
const path21 = input.path ? String(input.path) : "";
|
|
5597
6519
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
5598
|
-
return `Searched for "${pattern}"${
|
|
6520
|
+
return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
|
|
5599
6521
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
5600
6522
|
}
|
|
5601
6523
|
function extractGlob(input, response) {
|
|
@@ -5953,491 +6875,146 @@ async function createTimelineActivity(personId, params) {
|
|
|
5953
6875
|
direction: params.direction,
|
|
5954
6876
|
accountId: params.accountId || null
|
|
5955
6877
|
},
|
|
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
|
-
}
|
|
6878
|
+
targetPersonId: personId
|
|
6879
|
+
};
|
|
6880
|
+
const query = `
|
|
6881
|
+
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
6882
|
+
createTimelineActivity(data: $input) {
|
|
6883
|
+
id
|
|
6350
6884
|
}
|
|
6351
|
-
} catch {
|
|
6352
6885
|
}
|
|
6886
|
+
`;
|
|
6887
|
+
const res = await gqlRequest(
|
|
6888
|
+
query,
|
|
6889
|
+
{ input }
|
|
6890
|
+
);
|
|
6891
|
+
if (res.errors && res.errors.length > 0) {
|
|
6892
|
+
console.error(
|
|
6893
|
+
"[crm-bridge] createTimelineActivity error:",
|
|
6894
|
+
res.errors[0].message
|
|
6895
|
+
);
|
|
6896
|
+
return false;
|
|
6353
6897
|
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
_connected = false;
|
|
6359
|
-
_buffer = "";
|
|
6360
|
-
try {
|
|
6361
|
-
unlinkSync6(PID_PATH);
|
|
6362
|
-
} catch {
|
|
6363
|
-
}
|
|
6898
|
+
return true;
|
|
6899
|
+
}
|
|
6900
|
+
async function pushConversationToCRM(params) {
|
|
6901
|
+
if (!config) return;
|
|
6364
6902
|
try {
|
|
6365
|
-
|
|
6366
|
-
|
|
6903
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6904
|
+
if (!personId) {
|
|
6905
|
+
personId = await createPerson(
|
|
6906
|
+
params.platform,
|
|
6907
|
+
params.senderId,
|
|
6908
|
+
params.senderName
|
|
6909
|
+
);
|
|
6910
|
+
}
|
|
6911
|
+
if (!personId) {
|
|
6912
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6913
|
+
return;
|
|
6914
|
+
}
|
|
6915
|
+
const ok = await createTimelineActivity(personId, {
|
|
6916
|
+
...params,
|
|
6917
|
+
direction: "conversation"
|
|
6918
|
+
});
|
|
6919
|
+
if (ok) {
|
|
6920
|
+
console.log(
|
|
6921
|
+
`[crm-bridge] Pushed ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6922
|
+
);
|
|
6923
|
+
}
|
|
6924
|
+
} catch (err) {
|
|
6925
|
+
console.error("[crm-bridge] Push failed:", err instanceof Error ? err.message : err);
|
|
6367
6926
|
}
|
|
6368
|
-
spawnDaemon();
|
|
6369
6927
|
}
|
|
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;
|
|
6928
|
+
async function pushInboundMessageToCRM(params) {
|
|
6929
|
+
if (!config) return;
|
|
6930
|
+
try {
|
|
6931
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6932
|
+
if (!personId) {
|
|
6933
|
+
personId = await createPerson(
|
|
6934
|
+
params.platform,
|
|
6935
|
+
params.senderId,
|
|
6936
|
+
params.senderName
|
|
6937
|
+
);
|
|
6387
6938
|
}
|
|
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);
|
|
6939
|
+
if (!personId) {
|
|
6940
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6941
|
+
return;
|
|
6401
6942
|
}
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6943
|
+
const ok = await createTimelineActivity(personId, {
|
|
6944
|
+
...params,
|
|
6945
|
+
direction: "inbound"
|
|
6946
|
+
});
|
|
6947
|
+
if (ok) {
|
|
6948
|
+
console.log(
|
|
6949
|
+
`[crm-bridge] Inbound ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
6950
|
+
);
|
|
6951
|
+
}
|
|
6952
|
+
} catch (err) {
|
|
6953
|
+
console.error("[crm-bridge] Inbound push failed:", err instanceof Error ? err.message : err);
|
|
6407
6954
|
}
|
|
6408
|
-
return null;
|
|
6409
6955
|
}
|
|
6410
|
-
function
|
|
6411
|
-
if (
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6956
|
+
async function pushGatewayEventToCRM(params) {
|
|
6957
|
+
if (!config) return;
|
|
6958
|
+
try {
|
|
6959
|
+
let personId = await findPersonByContact(params.platform, params.senderId);
|
|
6960
|
+
if (!personId) {
|
|
6961
|
+
personId = await createPerson(
|
|
6962
|
+
params.platform,
|
|
6963
|
+
params.senderId,
|
|
6964
|
+
params.senderName
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
if (!personId) {
|
|
6968
|
+
console.error("[crm-bridge] Failed to find or create person for", params.senderId);
|
|
6969
|
+
return;
|
|
6970
|
+
}
|
|
6971
|
+
const channelLabel = params.platform.charAt(0).toUpperCase() + params.platform.slice(1);
|
|
6972
|
+
const categoryLabel = params.dataCategory.replace(/_/g, " ");
|
|
6973
|
+
const input = {
|
|
6974
|
+
name: `${channelLabel} ${categoryLabel} \u2014 ${params.senderName || params.senderId}`,
|
|
6975
|
+
happensAt: params.timestamp,
|
|
6976
|
+
properties: {
|
|
6977
|
+
channel: params.platform,
|
|
6978
|
+
dataCategory: params.dataCategory,
|
|
6979
|
+
senderId: params.senderId,
|
|
6980
|
+
senderName: params.senderName || null,
|
|
6981
|
+
accountId: params.accountId || null,
|
|
6982
|
+
direction: "inbound",
|
|
6983
|
+
...params.eventData
|
|
6984
|
+
},
|
|
6985
|
+
targetPersonId: personId
|
|
6986
|
+
};
|
|
6987
|
+
const query = `
|
|
6988
|
+
mutation CreateTimelineActivity($input: TimelineActivityCreateInput!) {
|
|
6989
|
+
createTimelineActivity(data: $input) {
|
|
6990
|
+
id
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
`;
|
|
6994
|
+
const res = await gqlRequest(
|
|
6995
|
+
query,
|
|
6996
|
+
{ input }
|
|
6997
|
+
);
|
|
6998
|
+
if (res.errors && res.errors.length > 0) {
|
|
6999
|
+
console.error(
|
|
7000
|
+
"[crm-bridge] pushGatewayEvent error:",
|
|
7001
|
+
res.errors[0].message
|
|
7002
|
+
);
|
|
7003
|
+
return;
|
|
7004
|
+
}
|
|
7005
|
+
console.log(
|
|
7006
|
+
`[crm-bridge] Event ${params.dataCategory} ${params.platform}/${params.senderId} \u2192 person ${personId}`
|
|
7007
|
+
);
|
|
7008
|
+
} catch (err) {
|
|
7009
|
+
console.error("[crm-bridge] Event push failed:", err instanceof Error ? err.message : err);
|
|
6421
7010
|
}
|
|
6422
7011
|
}
|
|
6423
|
-
var
|
|
6424
|
-
var
|
|
6425
|
-
"src/
|
|
7012
|
+
var config, disabledLogged;
|
|
7013
|
+
var init_crm_bridge = __esm({
|
|
7014
|
+
"src/gateway/crm-bridge.ts"() {
|
|
6426
7015
|
"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;
|
|
7016
|
+
config = null;
|
|
7017
|
+
disabledLogged = false;
|
|
6441
7018
|
}
|
|
6442
7019
|
});
|
|
6443
7020
|
|
|
@@ -6478,10 +7055,10 @@ async function disposeEmbedder() {
|
|
|
6478
7055
|
async function embedDirect(text) {
|
|
6479
7056
|
const llamaCpp = await import("node-llama-cpp");
|
|
6480
7057
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6481
|
-
const { existsSync:
|
|
6482
|
-
const
|
|
6483
|
-
const modelPath =
|
|
6484
|
-
if (!
|
|
7058
|
+
const { existsSync: existsSync16 } = await import("fs");
|
|
7059
|
+
const path21 = await import("path");
|
|
7060
|
+
const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
7061
|
+
if (!existsSync16(modelPath)) {
|
|
6485
7062
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
6486
7063
|
}
|
|
6487
7064
|
const llama = await llamaCpp.getLlama();
|
|
@@ -6518,8 +7095,8 @@ __export(wiki_client_exports, {
|
|
|
6518
7095
|
listDocuments: () => listDocuments,
|
|
6519
7096
|
listWorkspaces: () => listWorkspaces
|
|
6520
7097
|
});
|
|
6521
|
-
async function wikiFetch(config2,
|
|
6522
|
-
const url = `${config2.baseUrl}/api/v1${
|
|
7098
|
+
async function wikiFetch(config2, path21, method = "GET", body) {
|
|
7099
|
+
const url = `${config2.baseUrl}/api/v1${path21}`;
|
|
6523
7100
|
const headers = {
|
|
6524
7101
|
Authorization: `Bearer ${config2.apiKey}`,
|
|
6525
7102
|
"Content-Type": "application/json"
|
|
@@ -6552,7 +7129,7 @@ async function wikiFetch(config2, path20, method = "GET", body) {
|
|
|
6552
7129
|
}
|
|
6553
7130
|
}
|
|
6554
7131
|
if (!response.ok) {
|
|
6555
|
-
throw new Error(`Wiki API ${method} ${
|
|
7132
|
+
throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
|
|
6556
7133
|
}
|
|
6557
7134
|
return response.json();
|
|
6558
7135
|
} finally {
|
|
@@ -6646,7 +7223,7 @@ var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
|
6646
7223
|
var init_wiki_client = __esm({
|
|
6647
7224
|
"src/lib/wiki-client.ts"() {
|
|
6648
7225
|
"use strict";
|
|
6649
|
-
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
7226
|
+
LOCAL_WIKI_URL = process.env.EXE_WIKI_URL || "http://localhost:3001";
|
|
6650
7227
|
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
6651
7228
|
}
|
|
6652
7229
|
});
|
|
@@ -7573,13 +8150,13 @@ __export(whatsapp_accounts_exports, {
|
|
|
7573
8150
|
getDefaultAccount: () => getDefaultAccount,
|
|
7574
8151
|
loadAccounts: () => loadAccounts
|
|
7575
8152
|
});
|
|
7576
|
-
import { readFileSync as
|
|
8153
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
7577
8154
|
import { join as join2 } from "path";
|
|
7578
8155
|
import { homedir as homedir2 } from "os";
|
|
7579
8156
|
function loadAccounts() {
|
|
7580
8157
|
if (cachedAccounts !== null) return cachedAccounts;
|
|
7581
8158
|
try {
|
|
7582
|
-
const raw =
|
|
8159
|
+
const raw = readFileSync12(CONFIG_PATH2, "utf8");
|
|
7583
8160
|
const parsed = JSON.parse(raw);
|
|
7584
8161
|
if (!Array.isArray(parsed)) {
|
|
7585
8162
|
console.warn("[whatsapp] Config is not an array, ignoring");
|
|
@@ -9554,11 +10131,11 @@ function createQuietRenderer() {
|
|
|
9554
10131
|
init_state_bus();
|
|
9555
10132
|
|
|
9556
10133
|
// src/runtime/session-manager.ts
|
|
9557
|
-
import { randomUUID as
|
|
10134
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
9558
10135
|
init_state_bus();
|
|
9559
10136
|
|
|
9560
10137
|
// src/runtime/exe-hooks.ts
|
|
9561
|
-
import { randomUUID as
|
|
10138
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
9562
10139
|
function createExeOSHooks(config2) {
|
|
9563
10140
|
let sessionRegistered = false;
|
|
9564
10141
|
return {
|
|
@@ -9617,7 +10194,7 @@ function createExeOSHooks(config2) {
|
|
|
9617
10194
|
const toolResponse = result.isError ? { error: result.content } : { output: result.content };
|
|
9618
10195
|
const rawText = extractSemanticText2(toolName, toolInput, toolResponse);
|
|
9619
10196
|
await writeMemory2({
|
|
9620
|
-
id:
|
|
10197
|
+
id: randomUUID5(),
|
|
9621
10198
|
agent_id: config2.agentId,
|
|
9622
10199
|
agent_role: "employee",
|
|
9623
10200
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9640,7 +10217,7 @@ function createExeOSHooks(config2) {
|
|
|
9640
10217
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9641
10218
|
if (summary) {
|
|
9642
10219
|
await writeMemory2({
|
|
9643
|
-
id:
|
|
10220
|
+
id: randomUUID5(),
|
|
9644
10221
|
agent_id: config2.agentId,
|
|
9645
10222
|
agent_role: "employee",
|
|
9646
10223
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9668,8 +10245,8 @@ function createExeOSHooks(config2) {
|
|
|
9668
10245
|
},
|
|
9669
10246
|
async onError(error, _context) {
|
|
9670
10247
|
try {
|
|
9671
|
-
const { classifyError:
|
|
9672
|
-
const classification =
|
|
10248
|
+
const { classifyError: classifyError3 } = await Promise.resolve().then(() => (init_error_detector(), error_detector_exports));
|
|
10249
|
+
const classification = classifyError3(error.message);
|
|
9673
10250
|
process.stderr.write(
|
|
9674
10251
|
`[exe-hooks] Error (${classification}): ${error.message.slice(0, 100)}
|
|
9675
10252
|
`
|
|
@@ -9713,7 +10290,7 @@ function createExeOSHooks(config2) {
|
|
|
9713
10290
|
await client.execute({
|
|
9714
10291
|
sql: `INSERT OR IGNORE INTO notifications (id, type, source_agent, message, task_file, created_at, read)
|
|
9715
10292
|
VALUES (?, 'system', ?, ?, NULL, ?, 0)`,
|
|
9716
|
-
args: [
|
|
10293
|
+
args: [randomUUID5(), config2.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
|
|
9717
10294
|
});
|
|
9718
10295
|
} catch {
|
|
9719
10296
|
}
|
|
@@ -9729,7 +10306,7 @@ function createExeOSHooks(config2) {
|
|
|
9729
10306
|
try {
|
|
9730
10307
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9731
10308
|
await writeMemory2({
|
|
9732
|
-
id:
|
|
10309
|
+
id: randomUUID5(),
|
|
9733
10310
|
agent_id: config2.agentId,
|
|
9734
10311
|
agent_role: "employee",
|
|
9735
10312
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9751,7 +10328,7 @@ function createExeOSHooks(config2) {
|
|
|
9751
10328
|
try {
|
|
9752
10329
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9753
10330
|
await writeMemory2({
|
|
9754
|
-
id:
|
|
10331
|
+
id: randomUUID5(),
|
|
9755
10332
|
agent_id: config2.agentId,
|
|
9756
10333
|
agent_role: "employee",
|
|
9757
10334
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9790,7 +10367,7 @@ function createExeOSHooks(config2) {
|
|
|
9790
10367
|
if (tasks.rows.length > 0) {
|
|
9791
10368
|
const taskList = tasks.rows.map((r) => `- [${String(r.status)}] ${String(r.title)} (${String(r.task_file)})`).join("\n");
|
|
9792
10369
|
await writeMemory2({
|
|
9793
|
-
id:
|
|
10370
|
+
id: randomUUID5(),
|
|
9794
10371
|
agent_id: config2.agentId,
|
|
9795
10372
|
agent_role: "employee",
|
|
9796
10373
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9816,7 +10393,7 @@ ${taskList}`,
|
|
|
9816
10393
|
try {
|
|
9817
10394
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
9818
10395
|
await writeMemory2({
|
|
9819
|
-
id:
|
|
10396
|
+
id: randomUUID5(),
|
|
9820
10397
|
agent_id: config2.agentId,
|
|
9821
10398
|
agent_role: "employee",
|
|
9822
10399
|
session_id: `api-${config2.agentId}`,
|
|
@@ -9843,7 +10420,7 @@ var SessionManager = class {
|
|
|
9843
10420
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
9844
10421
|
/** Start a new agent session for an employee */
|
|
9845
10422
|
startSession(employeeId, config2) {
|
|
9846
|
-
const sessionId =
|
|
10423
|
+
const sessionId = randomUUID6();
|
|
9847
10424
|
const abortController = new AbortController();
|
|
9848
10425
|
const session = {
|
|
9849
10426
|
info: {
|
|
@@ -10096,13 +10673,16 @@ __export(gateway_exports, {
|
|
|
10096
10673
|
FULL_ACCESS: () => FULL_ACCESS,
|
|
10097
10674
|
FailoverCascade: () => FailoverCascade,
|
|
10098
10675
|
FailoverExhaustedError: () => FailoverExhaustedError,
|
|
10676
|
+
FatalBotError: () => FatalBotError,
|
|
10099
10677
|
Gateway: () => Gateway,
|
|
10100
10678
|
IMessageAdapter: () => IMessageAdapter,
|
|
10679
|
+
MaxStepsError: () => MaxStepsError,
|
|
10101
10680
|
OllamaProvider: () => OllamaProvider,
|
|
10102
10681
|
OpenAICompatProvider: () => OpenAICompatProvider,
|
|
10103
10682
|
READ_ONLY: () => READ_ONLY,
|
|
10104
10683
|
READ_TOOLS: () => READ_TOOLS,
|
|
10105
10684
|
RateLimiter: () => RateLimiter,
|
|
10685
|
+
RecoverableBotError: () => RecoverableBotError,
|
|
10106
10686
|
SessionStore: () => SessionStore,
|
|
10107
10687
|
SignalAdapter: () => SignalAdapter,
|
|
10108
10688
|
SlackAdapter: () => SlackAdapter,
|
|
@@ -10114,6 +10694,7 @@ __export(gateway_exports, {
|
|
|
10114
10694
|
buildExecAssistantTools: () => buildExecAssistantTools,
|
|
10115
10695
|
buildPermissionContext: () => buildPermissionContext,
|
|
10116
10696
|
checkToolPermission: () => checkToolPermission,
|
|
10697
|
+
classifyError: () => classifyError2,
|
|
10117
10698
|
createCRMWebhookHandler: () => createCRMWebhookHandler,
|
|
10118
10699
|
createPerson: () => createPerson,
|
|
10119
10700
|
createReceptionist: () => createReceptionist,
|
|
@@ -10789,10 +11370,49 @@ function buildPermissionContext(platform, permissions) {
|
|
|
10789
11370
|
return `[${platform.toUpperCase()} \u2014 allowed: ${parts.join(", ")}]`;
|
|
10790
11371
|
}
|
|
10791
11372
|
|
|
11373
|
+
// src/gateway/bot-errors.ts
|
|
11374
|
+
var FatalBotError = class extends Error {
|
|
11375
|
+
constructor(message, cause) {
|
|
11376
|
+
super(message);
|
|
11377
|
+
this.cause = cause;
|
|
11378
|
+
this.name = "FatalBotError";
|
|
11379
|
+
}
|
|
11380
|
+
fatal = true;
|
|
11381
|
+
};
|
|
11382
|
+
var RecoverableBotError = class extends Error {
|
|
11383
|
+
constructor(message, toolName, cause) {
|
|
11384
|
+
super(message);
|
|
11385
|
+
this.toolName = toolName;
|
|
11386
|
+
this.cause = cause;
|
|
11387
|
+
this.name = "RecoverableBotError";
|
|
11388
|
+
}
|
|
11389
|
+
recoverable = true;
|
|
11390
|
+
};
|
|
11391
|
+
var MaxStepsError = class extends Error {
|
|
11392
|
+
constructor(stepsTaken, maxSteps) {
|
|
11393
|
+
super(
|
|
11394
|
+
`Reached maximum steps (${stepsTaken}/${maxSteps}). Returning partial result.`
|
|
11395
|
+
);
|
|
11396
|
+
this.stepsTaken = stepsTaken;
|
|
11397
|
+
this.maxSteps = maxSteps;
|
|
11398
|
+
this.name = "MaxStepsError";
|
|
11399
|
+
}
|
|
11400
|
+
};
|
|
11401
|
+
function classifyError2(err, toolName) {
|
|
11402
|
+
if (err instanceof FatalBotError) return err;
|
|
11403
|
+
if (err instanceof RecoverableBotError) return err;
|
|
11404
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11405
|
+
if (message.includes("401") || message.includes("403") || message.includes("authentication") || message.includes("rate_limit")) {
|
|
11406
|
+
return new FatalBotError(message, err);
|
|
11407
|
+
}
|
|
11408
|
+
return new RecoverableBotError(message, toolName, err);
|
|
11409
|
+
}
|
|
11410
|
+
|
|
10792
11411
|
// src/gateway/bot-runtime.ts
|
|
10793
11412
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
10794
11413
|
var MAX_TURNS = 10;
|
|
10795
11414
|
var MAX_HISTORY = 50;
|
|
11415
|
+
var DEFAULT_PLANNING_INTERVAL = 3;
|
|
10796
11416
|
function buildExecAssistantSystemPrompt(platform, permissions) {
|
|
10797
11417
|
const permContext = buildPermissionContext(platform, permissions);
|
|
10798
11418
|
return `You are the founder's executive assistant (agent_id: "ea").
|
|
@@ -10844,7 +11464,7 @@ var BotRuntime = class {
|
|
|
10844
11464
|
async processMessage(msg, permissions) {
|
|
10845
11465
|
const sessionKey = msg.chatType === "group" ? msg.channelId : msg.senderId;
|
|
10846
11466
|
const history = this.getHistory(sessionKey);
|
|
10847
|
-
history.push({ role: "user", content: msg.text });
|
|
11467
|
+
history.push({ role: "user", content: msg.text, frameType: "task" });
|
|
10848
11468
|
const systemPrompt = this.config.systemPrompt + "\n\n" + buildPermissionContext(msg.platform, permissions);
|
|
10849
11469
|
const allowedTools = filterToolsForPermissions(
|
|
10850
11470
|
this.config.tools,
|
|
@@ -10852,29 +11472,54 @@ var BotRuntime = class {
|
|
|
10852
11472
|
);
|
|
10853
11473
|
const model = this.config.model ?? DEFAULT_MODEL;
|
|
10854
11474
|
const maxTurns = this.config.maxTurns ?? MAX_TURNS;
|
|
11475
|
+
const planningInterval = this.config.planningInterval ?? DEFAULT_PLANNING_INTERVAL;
|
|
10855
11476
|
let turns = 0;
|
|
10856
11477
|
while (turns < maxTurns) {
|
|
10857
11478
|
turns++;
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
11479
|
+
if (planningInterval > 0 && turns > 1 && turns % planningInterval === 1 && maxTurns - turns >= 2) {
|
|
11480
|
+
history.push({
|
|
11481
|
+
role: "user",
|
|
11482
|
+
content: "[Planning checkpoint] Review what you know so far. What facts have you gathered? What is your plan for the remaining steps? Be concise.",
|
|
11483
|
+
frameType: "planning"
|
|
11484
|
+
});
|
|
11485
|
+
}
|
|
11486
|
+
let response;
|
|
11487
|
+
try {
|
|
11488
|
+
response = await this.client.messages.create({
|
|
11489
|
+
model,
|
|
11490
|
+
max_tokens: 4096,
|
|
11491
|
+
system: systemPrompt,
|
|
11492
|
+
messages: history.map((m) => ({
|
|
11493
|
+
role: m.role,
|
|
11494
|
+
content: m.content
|
|
11495
|
+
})),
|
|
11496
|
+
tools: allowedTools.map((t) => ({
|
|
11497
|
+
name: t.name,
|
|
11498
|
+
description: t.description,
|
|
11499
|
+
input_schema: t.input_schema
|
|
11500
|
+
}))
|
|
11501
|
+
});
|
|
11502
|
+
} catch (err) {
|
|
11503
|
+
const classified = classifyError2(err);
|
|
11504
|
+
if (classified instanceof FatalBotError) {
|
|
11505
|
+
const errorMsg = `Bot error: ${classified.message}`;
|
|
11506
|
+
history.push({ role: "assistant", content: errorMsg, frameType: "error" });
|
|
11507
|
+
this.trimHistory(sessionKey);
|
|
11508
|
+
return errorMsg;
|
|
11509
|
+
}
|
|
11510
|
+
history.push({
|
|
11511
|
+
role: "assistant",
|
|
11512
|
+
content: `[API error \u2014 retrying] ${classified.message}`,
|
|
11513
|
+
frameType: "error"
|
|
11514
|
+
});
|
|
11515
|
+
continue;
|
|
11516
|
+
}
|
|
10872
11517
|
const toolUseBlocks = response.content.filter(
|
|
10873
11518
|
(b) => b.type === "tool_use"
|
|
10874
11519
|
);
|
|
10875
11520
|
if (toolUseBlocks.length === 0) {
|
|
10876
11521
|
const textContent = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
10877
|
-
history.push({ role: "assistant", content: textContent });
|
|
11522
|
+
history.push({ role: "assistant", content: textContent, frameType: "assistant" });
|
|
10878
11523
|
this.trimHistory(sessionKey);
|
|
10879
11524
|
return textContent;
|
|
10880
11525
|
}
|
|
@@ -10884,9 +11529,11 @@ var BotRuntime = class {
|
|
|
10884
11529
|
);
|
|
10885
11530
|
history.push({
|
|
10886
11531
|
role: "assistant",
|
|
10887
|
-
content: response.content
|
|
11532
|
+
content: response.content,
|
|
11533
|
+
frameType: "assistant"
|
|
10888
11534
|
});
|
|
10889
11535
|
const toolResults = [];
|
|
11536
|
+
let hadFatalError = false;
|
|
10890
11537
|
for (const block of allowed) {
|
|
10891
11538
|
try {
|
|
10892
11539
|
const result = await this.config.toolExecutor(
|
|
@@ -10899,12 +11546,23 @@ var BotRuntime = class {
|
|
|
10899
11546
|
content: result
|
|
10900
11547
|
});
|
|
10901
11548
|
} catch (err) {
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
11549
|
+
const classified = classifyError2(err, block.name);
|
|
11550
|
+
if (classified instanceof FatalBotError) {
|
|
11551
|
+
toolResults.push({
|
|
11552
|
+
type: "tool_result",
|
|
11553
|
+
tool_use_id: block.id,
|
|
11554
|
+
content: `Fatal error: ${classified.message}`,
|
|
11555
|
+
is_error: true
|
|
11556
|
+
});
|
|
11557
|
+
hadFatalError = true;
|
|
11558
|
+
} else {
|
|
11559
|
+
toolResults.push({
|
|
11560
|
+
type: "tool_result",
|
|
11561
|
+
tool_use_id: block.id,
|
|
11562
|
+
content: `Error (recoverable): ${classified.message}`,
|
|
11563
|
+
is_error: true
|
|
11564
|
+
});
|
|
11565
|
+
}
|
|
10908
11566
|
}
|
|
10909
11567
|
}
|
|
10910
11568
|
for (const { block, check } of blocked) {
|
|
@@ -10917,10 +11575,22 @@ var BotRuntime = class {
|
|
|
10917
11575
|
}
|
|
10918
11576
|
history.push({
|
|
10919
11577
|
role: "user",
|
|
10920
|
-
content: toolResults
|
|
11578
|
+
content: toolResults,
|
|
11579
|
+
frameType: hadFatalError ? "error" : "tool_result"
|
|
10921
11580
|
});
|
|
11581
|
+
if (hadFatalError) {
|
|
11582
|
+
this.trimHistory(sessionKey);
|
|
11583
|
+
return `A fatal error occurred during tool execution. The bot loop has been stopped.`;
|
|
11584
|
+
}
|
|
10922
11585
|
}
|
|
10923
|
-
|
|
11586
|
+
const maxErr = new MaxStepsError(turns, maxTurns);
|
|
11587
|
+
history.push({
|
|
11588
|
+
role: "assistant",
|
|
11589
|
+
content: maxErr.message,
|
|
11590
|
+
frameType: "error"
|
|
11591
|
+
});
|
|
11592
|
+
this.trimHistory(sessionKey);
|
|
11593
|
+
return maxErr.message;
|
|
10924
11594
|
}
|
|
10925
11595
|
getHistory(sessionKey) {
|
|
10926
11596
|
if (!this.conversations.has(sessionKey)) {
|
|
@@ -10930,9 +11600,19 @@ var BotRuntime = class {
|
|
|
10930
11600
|
}
|
|
10931
11601
|
trimHistory(sessionKey) {
|
|
10932
11602
|
const history = this.conversations.get(sessionKey);
|
|
10933
|
-
if (history
|
|
10934
|
-
|
|
11603
|
+
if (!history || history.length <= MAX_HISTORY) return;
|
|
11604
|
+
const firstTaskIdx = history.findIndex((m) => m.frameType === "task");
|
|
11605
|
+
let trimmed = history.filter(
|
|
11606
|
+
(m, i) => m.frameType !== "planning" || i >= history.length - MAX_HISTORY
|
|
11607
|
+
);
|
|
11608
|
+
if (trimmed.length > MAX_HISTORY) {
|
|
11609
|
+
const tail = trimmed.slice(-MAX_HISTORY);
|
|
11610
|
+
if (firstTaskIdx >= 0 && !tail.includes(history[firstTaskIdx])) {
|
|
11611
|
+
tail[0] = history[firstTaskIdx];
|
|
11612
|
+
}
|
|
11613
|
+
trimmed = tail;
|
|
10935
11614
|
}
|
|
11615
|
+
this.conversations.set(sessionKey, trimmed);
|
|
10936
11616
|
}
|
|
10937
11617
|
/** Clear conversation history for a session */
|
|
10938
11618
|
clearHistory(sessionKey) {
|
|
@@ -11637,9 +12317,20 @@ var OllamaProvider = class {
|
|
|
11637
12317
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
11638
12318
|
import { homedir } from "os";
|
|
11639
12319
|
import { join } from "path";
|
|
11640
|
-
import { mkdirSync as
|
|
11641
|
-
var
|
|
12320
|
+
import { mkdirSync as mkdirSync8 } from "fs";
|
|
12321
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
12322
|
+
var MAX_BACKOFF_MS = 3e5;
|
|
12323
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
12324
|
+
var JITTER_FACTOR = 0.25;
|
|
11642
12325
|
var AUTH_DIR = join(homedir(), ".exe-os", "whatsapp-auth");
|
|
12326
|
+
function calculateBackoff(retryCount) {
|
|
12327
|
+
const base = Math.min(
|
|
12328
|
+
INITIAL_BACKOFF_MS * BACKOFF_MULTIPLIER ** retryCount,
|
|
12329
|
+
MAX_BACKOFF_MS
|
|
12330
|
+
);
|
|
12331
|
+
const jitter = base * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
12332
|
+
return Math.max(INITIAL_BACKOFF_MS, Math.round(base + jitter));
|
|
12333
|
+
}
|
|
11643
12334
|
var WhatsAppAdapter = class {
|
|
11644
12335
|
platform = "whatsapp";
|
|
11645
12336
|
sock = null;
|
|
@@ -11647,14 +12338,31 @@ var WhatsAppAdapter = class {
|
|
|
11647
12338
|
connected = false;
|
|
11648
12339
|
abortController = null;
|
|
11649
12340
|
authDir = AUTH_DIR;
|
|
12341
|
+
// Resilience state
|
|
12342
|
+
retryCount = 0;
|
|
12343
|
+
disconnectedAt = 0;
|
|
11650
12344
|
async connect(config2) {
|
|
11651
12345
|
this.authDir = config2.credentials.authDir ?? AUTH_DIR;
|
|
11652
|
-
|
|
12346
|
+
mkdirSync8(this.authDir, { recursive: true });
|
|
11653
12347
|
const baileys = await import("@whiskeysockets/baileys");
|
|
11654
12348
|
const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = baileys;
|
|
11655
12349
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
11656
12350
|
const { version } = await fetchLatestBaileysVersion();
|
|
11657
12351
|
this.abortController = new AbortController();
|
|
12352
|
+
let agent;
|
|
12353
|
+
const socksProxy = config2.credentials.socksProxy;
|
|
12354
|
+
if (socksProxy) {
|
|
12355
|
+
try {
|
|
12356
|
+
const modName = "socks-proxy-agent";
|
|
12357
|
+
const mod = await import(modName);
|
|
12358
|
+
const SocksProxyAgent = mod.SocksProxyAgent ?? mod.default;
|
|
12359
|
+
agent = new SocksProxyAgent(socksProxy);
|
|
12360
|
+
console.log(`[whatsapp] Using SOCKS proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")}`);
|
|
12361
|
+
} catch {
|
|
12362
|
+
console.error("[whatsapp] socks-proxy-agent not installed \u2014 run: npm i socks-proxy-agent");
|
|
12363
|
+
throw new Error("SOCKS proxy configured but socks-proxy-agent package not installed");
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
11658
12366
|
const sock = makeWASocket({
|
|
11659
12367
|
auth: {
|
|
11660
12368
|
creds: state.creds,
|
|
@@ -11664,7 +12372,8 @@ var WhatsAppAdapter = class {
|
|
|
11664
12372
|
printQRInTerminal: true,
|
|
11665
12373
|
browser: ["exe-os", "cli", "1.0"],
|
|
11666
12374
|
syncFullHistory: false,
|
|
11667
|
-
markOnlineOnConnect: false
|
|
12375
|
+
markOnlineOnConnect: false,
|
|
12376
|
+
...agent ? { agent } : {}
|
|
11668
12377
|
});
|
|
11669
12378
|
this.sock = sock;
|
|
11670
12379
|
sock.ev.on("creds.update", saveCreds);
|
|
@@ -11672,18 +12381,32 @@ var WhatsAppAdapter = class {
|
|
|
11672
12381
|
const { connection, lastDisconnect } = update;
|
|
11673
12382
|
if (connection === "close") {
|
|
11674
12383
|
this.connected = false;
|
|
12384
|
+
if (this.disconnectedAt === 0) this.disconnectedAt = Date.now();
|
|
11675
12385
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
11676
12386
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
11677
12387
|
if (shouldReconnect && !this.abortController?.signal.aborted) {
|
|
11678
|
-
|
|
11679
|
-
|
|
12388
|
+
const delay2 = calculateBackoff(this.retryCount);
|
|
12389
|
+
this.retryCount++;
|
|
12390
|
+
console.log(
|
|
12391
|
+
`[whatsapp] Connection closed (code=${statusCode}), retry #${this.retryCount} in ${(delay2 / 1e3).toFixed(1)}s` + (socksProxy ? ` (proxy: ${socksProxy.replace(/\/\/.*@/, "//***@")})` : "")
|
|
12392
|
+
);
|
|
12393
|
+
setTimeout(() => void this.connect(config2), delay2);
|
|
11680
12394
|
} else {
|
|
11681
12395
|
console.log("[whatsapp] Logged out \u2014 clear auth and re-scan QR");
|
|
11682
12396
|
}
|
|
11683
12397
|
}
|
|
11684
12398
|
if (connection === "open") {
|
|
12399
|
+
if (this.retryCount > 0) {
|
|
12400
|
+
const downtimeSec = this.disconnectedAt > 0 ? ((Date.now() - this.disconnectedAt) / 1e3).toFixed(1) : "?";
|
|
12401
|
+
console.log(
|
|
12402
|
+
`[whatsapp] Reconnected after ${this.retryCount} retries (${downtimeSec}s downtime)`
|
|
12403
|
+
);
|
|
12404
|
+
} else {
|
|
12405
|
+
console.log("[whatsapp] Connected via Baileys (linked device)");
|
|
12406
|
+
}
|
|
11685
12407
|
this.connected = true;
|
|
11686
|
-
|
|
12408
|
+
this.retryCount = 0;
|
|
12409
|
+
this.disconnectedAt = 0;
|
|
11687
12410
|
}
|
|
11688
12411
|
});
|
|
11689
12412
|
sock.ev.on("messages.upsert", (upsert) => {
|
|
@@ -12922,12 +13645,12 @@ var SlackAdapter = class {
|
|
|
12922
13645
|
// src/gateway/adapters/imessage.ts
|
|
12923
13646
|
import { execFile } from "child_process";
|
|
12924
13647
|
import { promisify } from "util";
|
|
12925
|
-
import
|
|
12926
|
-
import
|
|
13648
|
+
import os10 from "os";
|
|
13649
|
+
import path19 from "path";
|
|
12927
13650
|
var execFileAsync = promisify(execFile);
|
|
12928
13651
|
var POLL_INTERVAL_MS = 5e3;
|
|
12929
|
-
var MESSAGES_DB_PATH =
|
|
12930
|
-
process.env.HOME ??
|
|
13652
|
+
var MESSAGES_DB_PATH = path19.join(
|
|
13653
|
+
process.env.HOME ?? os10.homedir(),
|
|
12931
13654
|
"Library/Messages/chat.db"
|
|
12932
13655
|
);
|
|
12933
13656
|
var IMessageAdapter = class {
|
|
@@ -13770,11 +14493,11 @@ async function ensureCRMContact(info) {
|
|
|
13770
14493
|
}
|
|
13771
14494
|
|
|
13772
14495
|
// src/automation/trigger-engine.ts
|
|
13773
|
-
import { readFileSync as
|
|
14496
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
13774
14497
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
13775
|
-
import
|
|
13776
|
-
import
|
|
13777
|
-
var TRIGGERS_PATH =
|
|
14498
|
+
import path20 from "path";
|
|
14499
|
+
import os11 from "os";
|
|
14500
|
+
var TRIGGERS_PATH = path20.join(os11.homedir(), ".exe-os", "triggers.json");
|
|
13778
14501
|
var GRAPH_API_VERSION = "v21.0";
|
|
13779
14502
|
function substituteTemplate(template, record) {
|
|
13780
14503
|
return template.replace(
|
|
@@ -13828,9 +14551,9 @@ function evaluateConditions(conditions, record) {
|
|
|
13828
14551
|
return conditions.every((c) => evaluateCondition(c, record));
|
|
13829
14552
|
}
|
|
13830
14553
|
function loadTriggers(project) {
|
|
13831
|
-
if (!
|
|
14554
|
+
if (!existsSync15(TRIGGERS_PATH)) return [];
|
|
13832
14555
|
try {
|
|
13833
|
-
const raw =
|
|
14556
|
+
const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
|
|
13834
14557
|
const all = JSON.parse(raw);
|
|
13835
14558
|
if (!Array.isArray(all)) return [];
|
|
13836
14559
|
if (project) {
|