@askexenow/exe-os 0.8.83 → 0.8.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -274,6 +274,93 @@ var init_memory = __esm({
|
|
|
274
274
|
}
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
+
// src/lib/daemon-protocol.ts
|
|
278
|
+
function serializeValue(v) {
|
|
279
|
+
if (v === null || v === void 0) return null;
|
|
280
|
+
if (typeof v === "bigint") return Number(v);
|
|
281
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
282
|
+
if (v instanceof Uint8Array) {
|
|
283
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
284
|
+
}
|
|
285
|
+
if (ArrayBuffer.isView(v)) {
|
|
286
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
287
|
+
}
|
|
288
|
+
if (v instanceof ArrayBuffer) {
|
|
289
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
290
|
+
}
|
|
291
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
292
|
+
return String(v);
|
|
293
|
+
}
|
|
294
|
+
function deserializeValue(v) {
|
|
295
|
+
if (v === null) return null;
|
|
296
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
297
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
298
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
299
|
+
}
|
|
300
|
+
return v;
|
|
301
|
+
}
|
|
302
|
+
function deserializeArgs(args) {
|
|
303
|
+
return args.map(deserializeValue);
|
|
304
|
+
}
|
|
305
|
+
function serializeResultSet(rs) {
|
|
306
|
+
const rows = [];
|
|
307
|
+
for (const row of rs.rows) {
|
|
308
|
+
const obj = {};
|
|
309
|
+
for (let i = 0; i < rs.columns.length; i++) {
|
|
310
|
+
const col = rs.columns[i];
|
|
311
|
+
if (col !== void 0) {
|
|
312
|
+
obj[col] = serializeValue(row[i]);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
rows.push(obj);
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
columns: [...rs.columns],
|
|
319
|
+
columnTypes: [...rs.columnTypes ?? []],
|
|
320
|
+
rows,
|
|
321
|
+
rowsAffected: typeof rs.rowsAffected === "bigint" ? Number(rs.rowsAffected) : rs.rowsAffected ?? 0,
|
|
322
|
+
lastInsertRowid: rs.lastInsertRowid != null ? typeof rs.lastInsertRowid === "bigint" ? Number(rs.lastInsertRowid) : rs.lastInsertRowid : null
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function deserializeResultSet(srs) {
|
|
326
|
+
const rows = srs.rows.map((obj) => {
|
|
327
|
+
const values = srs.columns.map(
|
|
328
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
329
|
+
);
|
|
330
|
+
const row = values;
|
|
331
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
332
|
+
const col = srs.columns[i];
|
|
333
|
+
if (col !== void 0) {
|
|
334
|
+
row[col] = values[i] ?? null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
Object.defineProperty(row, "length", {
|
|
338
|
+
value: values.length,
|
|
339
|
+
enumerable: false
|
|
340
|
+
});
|
|
341
|
+
return row;
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
columns: srs.columns,
|
|
345
|
+
columnTypes: srs.columnTypes ?? [],
|
|
346
|
+
rows,
|
|
347
|
+
rowsAffected: srs.rowsAffected,
|
|
348
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
349
|
+
toJSON: () => ({
|
|
350
|
+
columns: srs.columns,
|
|
351
|
+
columnTypes: srs.columnTypes ?? [],
|
|
352
|
+
rows: srs.rows,
|
|
353
|
+
rowsAffected: srs.rowsAffected,
|
|
354
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
355
|
+
})
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
var init_daemon_protocol = __esm({
|
|
359
|
+
"src/lib/daemon-protocol.ts"() {
|
|
360
|
+
"use strict";
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
277
364
|
// src/lib/session-registry.ts
|
|
278
365
|
var session_registry_exports = {};
|
|
279
366
|
__export(session_registry_exports, {
|
|
@@ -729,6 +816,31 @@ var init_db_retry = __esm({
|
|
|
729
816
|
});
|
|
730
817
|
|
|
731
818
|
// src/lib/employees.ts
|
|
819
|
+
var employees_exports = {};
|
|
820
|
+
__export(employees_exports, {
|
|
821
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
822
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
823
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
824
|
+
addEmployee: () => addEmployee,
|
|
825
|
+
baseAgentName: () => baseAgentName,
|
|
826
|
+
canCoordinate: () => canCoordinate,
|
|
827
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
828
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
829
|
+
getEmployee: () => getEmployee,
|
|
830
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
831
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
832
|
+
hasRole: () => hasRole,
|
|
833
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
834
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
835
|
+
isMultiInstance: () => isMultiInstance,
|
|
836
|
+
loadEmployees: () => loadEmployees,
|
|
837
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
838
|
+
normalizeRole: () => normalizeRole,
|
|
839
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
840
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
841
|
+
saveEmployees: () => saveEmployees,
|
|
842
|
+
validateEmployeeName: () => validateEmployeeName
|
|
843
|
+
});
|
|
732
844
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
733
845
|
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
734
846
|
import { execSync as execSync4 } from "child_process";
|
|
@@ -750,6 +862,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
750
862
|
if (!agentName) return false;
|
|
751
863
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
752
864
|
}
|
|
865
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
866
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
867
|
+
}
|
|
868
|
+
function validateEmployeeName(name) {
|
|
869
|
+
if (!name) {
|
|
870
|
+
return { valid: false, error: "Name is required" };
|
|
871
|
+
}
|
|
872
|
+
if (name.length > 32) {
|
|
873
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
874
|
+
}
|
|
875
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
876
|
+
return {
|
|
877
|
+
valid: false,
|
|
878
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return { valid: true };
|
|
882
|
+
}
|
|
753
883
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
754
884
|
if (!existsSync4(employeesPath)) {
|
|
755
885
|
return [];
|
|
@@ -761,6 +891,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
761
891
|
return [];
|
|
762
892
|
}
|
|
763
893
|
}
|
|
894
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
895
|
+
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
896
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
897
|
+
}
|
|
764
898
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
765
899
|
if (!existsSync4(employeesPath)) return [];
|
|
766
900
|
try {
|
|
@@ -772,12 +906,110 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
772
906
|
function getEmployee(employees, name) {
|
|
773
907
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
774
908
|
}
|
|
909
|
+
function getEmployeeByRole(employees, role) {
|
|
910
|
+
const lower = role.toLowerCase();
|
|
911
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
912
|
+
}
|
|
913
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
914
|
+
const lower = role.toLowerCase();
|
|
915
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
916
|
+
}
|
|
917
|
+
function hasRole(agentName, role) {
|
|
918
|
+
const employees = loadEmployeesSync();
|
|
919
|
+
const emp = getEmployee(employees, agentName);
|
|
920
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
921
|
+
}
|
|
922
|
+
function baseAgentName(name, employees) {
|
|
923
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
924
|
+
if (!match) return name;
|
|
925
|
+
const base = match[1];
|
|
926
|
+
const roster = employees ?? loadEmployeesSync();
|
|
927
|
+
if (getEmployee(roster, base)) return base;
|
|
928
|
+
return name;
|
|
929
|
+
}
|
|
775
930
|
function isMultiInstance(agentName, employees) {
|
|
776
931
|
const roster = employees ?? loadEmployeesSync();
|
|
777
932
|
const emp = getEmployee(roster, agentName);
|
|
778
933
|
if (!emp) return false;
|
|
779
934
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
780
935
|
}
|
|
936
|
+
function addEmployee(employees, employee) {
|
|
937
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
938
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
939
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
940
|
+
}
|
|
941
|
+
return [...employees, normalized];
|
|
942
|
+
}
|
|
943
|
+
async function normalizeRosterCase(rosterPath) {
|
|
944
|
+
const employees = await loadEmployees(rosterPath);
|
|
945
|
+
let changed = false;
|
|
946
|
+
for (const emp of employees) {
|
|
947
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
948
|
+
const oldName = emp.name;
|
|
949
|
+
emp.name = emp.name.toLowerCase();
|
|
950
|
+
changed = true;
|
|
951
|
+
try {
|
|
952
|
+
const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
|
|
953
|
+
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
954
|
+
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
955
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
956
|
+
renameSync3(oldPath, newPath);
|
|
957
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
958
|
+
const content = readFileSync4(oldPath, "utf-8");
|
|
959
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
960
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
961
|
+
unlinkSync(oldPath);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (changed) {
|
|
969
|
+
await saveEmployees(employees, rosterPath);
|
|
970
|
+
}
|
|
971
|
+
return changed;
|
|
972
|
+
}
|
|
973
|
+
function findExeBin() {
|
|
974
|
+
try {
|
|
975
|
+
return execSync4(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
976
|
+
} catch {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function registerBinSymlinks(name) {
|
|
981
|
+
const created = [];
|
|
982
|
+
const skipped = [];
|
|
983
|
+
const errors = [];
|
|
984
|
+
const exeBinPath = findExeBin();
|
|
985
|
+
if (!exeBinPath) {
|
|
986
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
987
|
+
return { created, skipped, errors };
|
|
988
|
+
}
|
|
989
|
+
const binDir = path4.dirname(exeBinPath);
|
|
990
|
+
let target;
|
|
991
|
+
try {
|
|
992
|
+
target = readlinkSync(exeBinPath);
|
|
993
|
+
} catch {
|
|
994
|
+
errors.push("Could not read 'exe' symlink");
|
|
995
|
+
return { created, skipped, errors };
|
|
996
|
+
}
|
|
997
|
+
for (const suffix of ["", "-opencode"]) {
|
|
998
|
+
const linkName = `${name}${suffix}`;
|
|
999
|
+
const linkPath = path4.join(binDir, linkName);
|
|
1000
|
+
if (existsSync4(linkPath)) {
|
|
1001
|
+
skipped.push(linkName);
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
1005
|
+
symlinkSync(target, linkPath);
|
|
1006
|
+
created.push(linkName);
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return { created, skipped, errors };
|
|
1012
|
+
}
|
|
781
1013
|
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
782
1014
|
var init_employees = __esm({
|
|
783
1015
|
"src/lib/employees.ts"() {
|
|
@@ -790,284 +1022,773 @@ var init_employees = __esm({
|
|
|
790
1022
|
}
|
|
791
1023
|
});
|
|
792
1024
|
|
|
793
|
-
// src/lib/
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
import { createClient } from "@libsql/client";
|
|
806
|
-
async function initDatabase(config) {
|
|
807
|
-
if (_client) {
|
|
808
|
-
_client.close();
|
|
809
|
-
_client = null;
|
|
810
|
-
_resilientClient = null;
|
|
1025
|
+
// src/lib/exe-daemon-client.ts
|
|
1026
|
+
import net from "net";
|
|
1027
|
+
import { spawn } from "child_process";
|
|
1028
|
+
import { randomUUID } from "crypto";
|
|
1029
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1030
|
+
import path5 from "path";
|
|
1031
|
+
import { fileURLToPath } from "url";
|
|
1032
|
+
function handleData(chunk) {
|
|
1033
|
+
_buffer += chunk.toString();
|
|
1034
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
1035
|
+
_buffer = "";
|
|
1036
|
+
return;
|
|
811
1037
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1038
|
+
let newlineIdx;
|
|
1039
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1040
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
1041
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
1042
|
+
if (!line) continue;
|
|
1043
|
+
try {
|
|
1044
|
+
const response = JSON.parse(line);
|
|
1045
|
+
const id = response.id;
|
|
1046
|
+
if (!id) continue;
|
|
1047
|
+
const entry = _pending.get(id);
|
|
1048
|
+
if (entry) {
|
|
1049
|
+
clearTimeout(entry.timer);
|
|
1050
|
+
_pending.delete(id);
|
|
1051
|
+
entry.resolve(response);
|
|
1052
|
+
}
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
817
1055
|
}
|
|
818
|
-
_client = createClient(opts);
|
|
819
|
-
_resilientClient = wrapWithRetry(_client);
|
|
820
1056
|
}
|
|
821
|
-
function
|
|
822
|
-
|
|
1057
|
+
function cleanupStaleFiles() {
|
|
1058
|
+
if (existsSync5(PID_PATH)) {
|
|
1059
|
+
try {
|
|
1060
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1061
|
+
if (pid > 0) {
|
|
1062
|
+
try {
|
|
1063
|
+
process.kill(pid, 0);
|
|
1064
|
+
return;
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
1070
|
+
try {
|
|
1071
|
+
unlinkSync2(PID_PATH);
|
|
1072
|
+
} catch {
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
unlinkSync2(SOCKET_PATH);
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
823
1079
|
}
|
|
824
|
-
function
|
|
825
|
-
|
|
826
|
-
|
|
1080
|
+
function findPackageRoot() {
|
|
1081
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1082
|
+
const { root } = path5.parse(dir);
|
|
1083
|
+
while (dir !== root) {
|
|
1084
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
1085
|
+
dir = path5.dirname(dir);
|
|
827
1086
|
}
|
|
828
|
-
return
|
|
1087
|
+
return null;
|
|
829
1088
|
}
|
|
830
|
-
function
|
|
831
|
-
|
|
832
|
-
|
|
1089
|
+
function spawnDaemon() {
|
|
1090
|
+
const pkgRoot = findPackageRoot();
|
|
1091
|
+
if (!pkgRoot) {
|
|
1092
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1096
|
+
if (!existsSync5(daemonPath)) {
|
|
1097
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1098
|
+
`);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const resolvedPath = daemonPath;
|
|
1102
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1103
|
+
`);
|
|
1104
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
1105
|
+
let stderrFd = "ignore";
|
|
1106
|
+
try {
|
|
1107
|
+
stderrFd = openSync(logPath, "a");
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
1111
|
+
detached: true,
|
|
1112
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
1113
|
+
env: {
|
|
1114
|
+
...process.env,
|
|
1115
|
+
TMUX: void 0,
|
|
1116
|
+
// Daemon is global — must not inherit session scope
|
|
1117
|
+
TMUX_PANE: void 0,
|
|
1118
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
1119
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
1120
|
+
EXE_DAEMON_PID: PID_PATH
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
child.unref();
|
|
1124
|
+
if (typeof stderrFd === "number") {
|
|
1125
|
+
try {
|
|
1126
|
+
closeSync(stderrFd);
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
833
1129
|
}
|
|
834
|
-
return _client;
|
|
835
1130
|
}
|
|
836
|
-
|
|
837
|
-
const client = getRawClient();
|
|
838
|
-
await client.execute("PRAGMA journal_mode = WAL");
|
|
839
|
-
await client.execute("PRAGMA busy_timeout = 30000");
|
|
840
|
-
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1131
|
+
function acquireSpawnLock() {
|
|
841
1132
|
try {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
await client.executeMultiple(`
|
|
846
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
847
|
-
id TEXT PRIMARY KEY,
|
|
848
|
-
agent_id TEXT NOT NULL,
|
|
849
|
-
agent_role TEXT NOT NULL,
|
|
850
|
-
session_id TEXT NOT NULL,
|
|
851
|
-
timestamp TEXT NOT NULL,
|
|
852
|
-
tool_name TEXT NOT NULL,
|
|
853
|
-
project_name TEXT NOT NULL,
|
|
854
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
855
|
-
raw_text TEXT NOT NULL,
|
|
856
|
-
vector F32_BLOB(1024),
|
|
857
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
861
|
-
ON memories(agent_id);
|
|
862
|
-
|
|
863
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
864
|
-
ON memories(timestamp);
|
|
865
|
-
|
|
866
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
867
|
-
ON memories(session_id);
|
|
868
|
-
|
|
869
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
870
|
-
ON memories(project_name);
|
|
871
|
-
|
|
872
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
873
|
-
ON memories(tool_name);
|
|
874
|
-
|
|
875
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
876
|
-
ON memories(version);
|
|
877
|
-
|
|
878
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
879
|
-
ON memories(agent_id, project_name);
|
|
880
|
-
`);
|
|
881
|
-
await client.executeMultiple(`
|
|
882
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
883
|
-
raw_text,
|
|
884
|
-
content='memories',
|
|
885
|
-
content_rowid='rowid'
|
|
886
|
-
);
|
|
887
|
-
|
|
888
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
889
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
890
|
-
END;
|
|
891
|
-
|
|
892
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
893
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
894
|
-
END;
|
|
895
|
-
|
|
896
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
897
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
898
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
899
|
-
END;
|
|
900
|
-
`);
|
|
901
|
-
await client.executeMultiple(`
|
|
902
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
903
|
-
key TEXT PRIMARY KEY,
|
|
904
|
-
value TEXT NOT NULL
|
|
905
|
-
);
|
|
906
|
-
`);
|
|
907
|
-
await client.executeMultiple(`
|
|
908
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
909
|
-
id TEXT PRIMARY KEY,
|
|
910
|
-
title TEXT NOT NULL,
|
|
911
|
-
assigned_to TEXT NOT NULL,
|
|
912
|
-
assigned_by TEXT NOT NULL,
|
|
913
|
-
project_name TEXT NOT NULL,
|
|
914
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
915
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
916
|
-
task_file TEXT,
|
|
917
|
-
created_at TEXT NOT NULL,
|
|
918
|
-
updated_at TEXT NOT NULL
|
|
919
|
-
);
|
|
920
|
-
|
|
921
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
922
|
-
ON tasks(assigned_to, status);
|
|
923
|
-
`);
|
|
924
|
-
await client.executeMultiple(`
|
|
925
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
926
|
-
id TEXT PRIMARY KEY,
|
|
927
|
-
agent_id TEXT NOT NULL,
|
|
928
|
-
project_name TEXT,
|
|
929
|
-
domain TEXT,
|
|
930
|
-
content TEXT NOT NULL,
|
|
931
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
932
|
-
created_at TEXT NOT NULL,
|
|
933
|
-
updated_at TEXT NOT NULL
|
|
934
|
-
);
|
|
935
|
-
|
|
936
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
937
|
-
ON behaviors(agent_id, active);
|
|
938
|
-
`);
|
|
939
|
-
try {
|
|
940
|
-
const coordinatorName = getCoordinatorName();
|
|
941
|
-
const existing = await client.execute({
|
|
942
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
943
|
-
args: [coordinatorName]
|
|
944
|
-
});
|
|
945
|
-
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
946
|
-
const seededAt = "2026-03-25T00:00:00Z";
|
|
947
|
-
for (const [domain, content] of [
|
|
948
|
-
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
949
|
-
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
950
|
-
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
951
|
-
]) {
|
|
952
|
-
await client.execute({
|
|
953
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
954
|
-
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
955
|
-
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
}
|
|
1133
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1134
|
+
closeSync(fd);
|
|
1135
|
+
return true;
|
|
959
1136
|
} catch {
|
|
1137
|
+
try {
|
|
1138
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
1139
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
1140
|
+
try {
|
|
1141
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
1146
|
+
closeSync(fd);
|
|
1147
|
+
return true;
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
return false;
|
|
960
1154
|
}
|
|
1155
|
+
}
|
|
1156
|
+
function releaseSpawnLock() {
|
|
961
1157
|
try {
|
|
962
|
-
|
|
963
|
-
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
964
|
-
args: []
|
|
965
|
-
});
|
|
1158
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
966
1159
|
} catch {
|
|
967
1160
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1161
|
+
}
|
|
1162
|
+
function connectToSocket() {
|
|
1163
|
+
return new Promise((resolve) => {
|
|
1164
|
+
if (_socket && _connected) {
|
|
1165
|
+
resolve(true);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
1169
|
+
const connectTimeout = setTimeout(() => {
|
|
1170
|
+
socket.destroy();
|
|
1171
|
+
resolve(false);
|
|
1172
|
+
}, 2e3);
|
|
1173
|
+
socket.on("connect", () => {
|
|
1174
|
+
clearTimeout(connectTimeout);
|
|
1175
|
+
_socket = socket;
|
|
1176
|
+
_connected = true;
|
|
1177
|
+
_buffer = "";
|
|
1178
|
+
socket.on("data", handleData);
|
|
1179
|
+
socket.on("close", () => {
|
|
1180
|
+
_connected = false;
|
|
1181
|
+
_socket = null;
|
|
1182
|
+
for (const [id, entry] of _pending) {
|
|
1183
|
+
clearTimeout(entry.timer);
|
|
1184
|
+
_pending.delete(id);
|
|
1185
|
+
entry.resolve({ error: "Connection closed" });
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
socket.on("error", () => {
|
|
1189
|
+
_connected = false;
|
|
1190
|
+
_socket = null;
|
|
1191
|
+
});
|
|
1192
|
+
resolve(true);
|
|
972
1193
|
});
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
await client.execute({
|
|
977
|
-
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
978
|
-
args: []
|
|
1194
|
+
socket.on("error", () => {
|
|
1195
|
+
clearTimeout(connectTimeout);
|
|
1196
|
+
resolve(false);
|
|
979
1197
|
});
|
|
980
|
-
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
async function connectEmbedDaemon() {
|
|
1201
|
+
if (_socket && _connected) return true;
|
|
1202
|
+
if (await connectToSocket()) return true;
|
|
1203
|
+
if (acquireSpawnLock()) {
|
|
1204
|
+
try {
|
|
1205
|
+
cleanupStaleFiles();
|
|
1206
|
+
spawnDaemon();
|
|
1207
|
+
} finally {
|
|
1208
|
+
releaseSpawnLock();
|
|
1209
|
+
}
|
|
981
1210
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
});
|
|
989
|
-
} catch {
|
|
1211
|
+
const start = Date.now();
|
|
1212
|
+
let delay2 = 100;
|
|
1213
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1214
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1215
|
+
if (await connectToSocket()) return true;
|
|
1216
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
990
1217
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
function sendRequest(texts, priority) {
|
|
1221
|
+
return sendDaemonRequest({ texts, priority });
|
|
1222
|
+
}
|
|
1223
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
1224
|
+
return new Promise((resolve) => {
|
|
1225
|
+
if (!_socket || !_connected) {
|
|
1226
|
+
resolve({ error: "Not connected" });
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const id = randomUUID();
|
|
1230
|
+
const timer = setTimeout(() => {
|
|
1231
|
+
_pending.delete(id);
|
|
1232
|
+
resolve({ error: "Request timeout" });
|
|
1233
|
+
}, timeoutMs);
|
|
1234
|
+
_pending.set(id, { resolve, timer });
|
|
1235
|
+
try {
|
|
1236
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
1237
|
+
} catch {
|
|
1238
|
+
clearTimeout(timer);
|
|
1239
|
+
_pending.delete(id);
|
|
1240
|
+
resolve({ error: "Write failed" });
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
async function pingDaemon() {
|
|
1245
|
+
if (!_socket || !_connected) return null;
|
|
1246
|
+
const response = await sendDaemonRequest({ type: "health" }, 5e3);
|
|
1247
|
+
if (response.health) {
|
|
1248
|
+
return response.health;
|
|
997
1249
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
function killAndRespawnDaemon() {
|
|
1253
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1254
|
+
if (existsSync5(PID_PATH)) {
|
|
1255
|
+
try {
|
|
1256
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1257
|
+
if (pid > 0) {
|
|
1258
|
+
try {
|
|
1259
|
+
process.kill(pid, "SIGKILL");
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
} catch {
|
|
1264
|
+
}
|
|
1004
1265
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
args: []
|
|
1009
|
-
});
|
|
1010
|
-
} catch {
|
|
1266
|
+
if (_socket) {
|
|
1267
|
+
_socket.destroy();
|
|
1268
|
+
_socket = null;
|
|
1011
1269
|
}
|
|
1270
|
+
_connected = false;
|
|
1271
|
+
_buffer = "";
|
|
1012
1272
|
try {
|
|
1013
|
-
|
|
1014
|
-
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1015
|
-
args: []
|
|
1016
|
-
});
|
|
1273
|
+
unlinkSync2(PID_PATH);
|
|
1017
1274
|
} catch {
|
|
1018
1275
|
}
|
|
1019
1276
|
try {
|
|
1020
|
-
|
|
1021
|
-
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1022
|
-
args: []
|
|
1023
|
-
});
|
|
1277
|
+
unlinkSync2(SOCKET_PATH);
|
|
1024
1278
|
} catch {
|
|
1025
1279
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1280
|
+
spawnDaemon();
|
|
1281
|
+
}
|
|
1282
|
+
async function embedViaClient(text, priority = "high") {
|
|
1283
|
+
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
1284
|
+
_requestCount++;
|
|
1285
|
+
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
1286
|
+
const health = await pingDaemon();
|
|
1287
|
+
if (!health) {
|
|
1288
|
+
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
1289
|
+
`);
|
|
1290
|
+
killAndRespawnDaemon();
|
|
1291
|
+
const start = Date.now();
|
|
1292
|
+
let delay2 = 200;
|
|
1293
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1294
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1295
|
+
if (await connectToSocket()) break;
|
|
1296
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1297
|
+
}
|
|
1298
|
+
if (!_connected) return null;
|
|
1299
|
+
}
|
|
1032
1300
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1301
|
+
const result = await sendRequest([text], priority);
|
|
1302
|
+
if (!result.error && result.vectors?.[0]) return result.vectors[0];
|
|
1303
|
+
if (result.error) {
|
|
1304
|
+
process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
|
|
1305
|
+
`);
|
|
1306
|
+
killAndRespawnDaemon();
|
|
1307
|
+
const start = Date.now();
|
|
1308
|
+
let delay2 = 200;
|
|
1309
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1310
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1311
|
+
if (await connectToSocket()) break;
|
|
1312
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1313
|
+
}
|
|
1314
|
+
if (!_connected) return null;
|
|
1315
|
+
const retry = await sendRequest([text], priority);
|
|
1316
|
+
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
1317
|
+
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
1318
|
+
`);
|
|
1039
1319
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
function disconnectClient() {
|
|
1323
|
+
if (_socket) {
|
|
1324
|
+
_socket.destroy();
|
|
1325
|
+
_socket = null;
|
|
1326
|
+
}
|
|
1327
|
+
_connected = false;
|
|
1328
|
+
_buffer = "";
|
|
1329
|
+
for (const [id, entry] of _pending) {
|
|
1330
|
+
clearTimeout(entry.timer);
|
|
1331
|
+
_pending.delete(id);
|
|
1332
|
+
entry.resolve({ error: "Client disconnected" });
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
function isClientConnected() {
|
|
1336
|
+
return _connected;
|
|
1337
|
+
}
|
|
1338
|
+
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;
|
|
1339
|
+
var init_exe_daemon_client = __esm({
|
|
1340
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
1341
|
+
"use strict";
|
|
1342
|
+
init_config();
|
|
1343
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
1344
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
1345
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1346
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1347
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
1348
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
1349
|
+
_socket = null;
|
|
1350
|
+
_connected = false;
|
|
1351
|
+
_buffer = "";
|
|
1352
|
+
_requestCount = 0;
|
|
1353
|
+
HEALTH_CHECK_INTERVAL = 100;
|
|
1354
|
+
_pending = /* @__PURE__ */ new Map();
|
|
1355
|
+
MAX_BUFFER = 1e7;
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
// src/lib/db-daemon-client.ts
|
|
1360
|
+
var db_daemon_client_exports = {};
|
|
1361
|
+
__export(db_daemon_client_exports, {
|
|
1362
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
1363
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
1364
|
+
});
|
|
1365
|
+
function normalizeStatement(stmt) {
|
|
1366
|
+
if (typeof stmt === "string") {
|
|
1367
|
+
return { sql: stmt, args: [] };
|
|
1368
|
+
}
|
|
1369
|
+
const sql = stmt.sql;
|
|
1370
|
+
let args = [];
|
|
1371
|
+
if (Array.isArray(stmt.args)) {
|
|
1372
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
1373
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
1374
|
+
const named = {};
|
|
1375
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
1376
|
+
named[key] = serializeValue(val);
|
|
1377
|
+
}
|
|
1378
|
+
return { sql, args: named };
|
|
1379
|
+
}
|
|
1380
|
+
return { sql, args };
|
|
1381
|
+
}
|
|
1382
|
+
function createDaemonDbClient(fallbackClient) {
|
|
1383
|
+
let _useDaemon = false;
|
|
1384
|
+
const client = {
|
|
1385
|
+
async execute(stmt) {
|
|
1386
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1387
|
+
return fallbackClient.execute(stmt);
|
|
1388
|
+
}
|
|
1389
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
1390
|
+
const response = await sendDaemonRequest({
|
|
1391
|
+
type: "db-execute",
|
|
1392
|
+
sql,
|
|
1393
|
+
args
|
|
1394
|
+
});
|
|
1395
|
+
if (response.error) {
|
|
1396
|
+
const errMsg = String(response.error);
|
|
1397
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1398
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1399
|
+
`);
|
|
1400
|
+
return fallbackClient.execute(stmt);
|
|
1401
|
+
}
|
|
1402
|
+
throw new Error(errMsg);
|
|
1403
|
+
}
|
|
1404
|
+
if (response.db) {
|
|
1405
|
+
return deserializeResultSet(response.db);
|
|
1406
|
+
}
|
|
1407
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1408
|
+
return fallbackClient.execute(stmt);
|
|
1409
|
+
},
|
|
1410
|
+
async batch(stmts, mode) {
|
|
1411
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1412
|
+
return fallbackClient.batch(stmts, mode);
|
|
1413
|
+
}
|
|
1414
|
+
const statements = stmts.map(normalizeStatement);
|
|
1415
|
+
const response = await sendDaemonRequest({
|
|
1416
|
+
type: "db-batch",
|
|
1417
|
+
statements,
|
|
1418
|
+
mode: mode ?? "deferred"
|
|
1419
|
+
});
|
|
1420
|
+
if (response.error) {
|
|
1421
|
+
const errMsg = String(response.error);
|
|
1422
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1423
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1424
|
+
`);
|
|
1425
|
+
return fallbackClient.batch(stmts, mode);
|
|
1426
|
+
}
|
|
1427
|
+
throw new Error(errMsg);
|
|
1428
|
+
}
|
|
1429
|
+
const batchResults = response["db-batch"];
|
|
1430
|
+
if (batchResults) {
|
|
1431
|
+
return batchResults.map(deserializeResultSet);
|
|
1432
|
+
}
|
|
1433
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1434
|
+
return fallbackClient.batch(stmts, mode);
|
|
1435
|
+
},
|
|
1436
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1437
|
+
async transaction(mode) {
|
|
1438
|
+
return fallbackClient.transaction(mode);
|
|
1439
|
+
},
|
|
1440
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1441
|
+
async executeMultiple(sql) {
|
|
1442
|
+
return fallbackClient.executeMultiple(sql);
|
|
1443
|
+
},
|
|
1444
|
+
// migrate — delegate to fallback
|
|
1445
|
+
async migrate(stmts) {
|
|
1446
|
+
return fallbackClient.migrate(stmts);
|
|
1447
|
+
},
|
|
1448
|
+
// Sync mode — delegate to fallback
|
|
1449
|
+
sync() {
|
|
1450
|
+
return fallbackClient.sync();
|
|
1451
|
+
},
|
|
1452
|
+
close() {
|
|
1453
|
+
_useDaemon = false;
|
|
1454
|
+
},
|
|
1455
|
+
get closed() {
|
|
1456
|
+
return fallbackClient.closed;
|
|
1457
|
+
},
|
|
1458
|
+
get protocol() {
|
|
1459
|
+
return fallbackClient.protocol;
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
return {
|
|
1463
|
+
...client,
|
|
1464
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1465
|
+
_enableDaemon() {
|
|
1466
|
+
_useDaemon = true;
|
|
1467
|
+
},
|
|
1468
|
+
/** Check if daemon routing is active */
|
|
1469
|
+
_isDaemonActive() {
|
|
1470
|
+
return _useDaemon && isClientConnected();
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1475
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1476
|
+
const connected = await connectEmbedDaemon();
|
|
1477
|
+
if (!connected) {
|
|
1478
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1482
|
+
client._enableDaemon();
|
|
1483
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1484
|
+
return client;
|
|
1485
|
+
}
|
|
1486
|
+
var init_db_daemon_client = __esm({
|
|
1487
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1488
|
+
"use strict";
|
|
1489
|
+
init_exe_daemon_client();
|
|
1490
|
+
init_daemon_protocol();
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// src/lib/database.ts
|
|
1495
|
+
var database_exports = {};
|
|
1496
|
+
__export(database_exports, {
|
|
1497
|
+
disposeDatabase: () => disposeDatabase,
|
|
1498
|
+
disposeTurso: () => disposeTurso,
|
|
1499
|
+
ensureSchema: () => ensureSchema,
|
|
1500
|
+
getClient: () => getClient,
|
|
1501
|
+
getRawClient: () => getRawClient,
|
|
1502
|
+
initDaemonClient: () => initDaemonClient,
|
|
1503
|
+
initDatabase: () => initDatabase,
|
|
1504
|
+
initTurso: () => initTurso,
|
|
1505
|
+
isInitialized: () => isInitialized
|
|
1506
|
+
});
|
|
1507
|
+
import { createClient } from "@libsql/client";
|
|
1508
|
+
async function initDatabase(config) {
|
|
1509
|
+
if (_client) {
|
|
1510
|
+
_client.close();
|
|
1511
|
+
_client = null;
|
|
1512
|
+
_resilientClient = null;
|
|
1513
|
+
}
|
|
1514
|
+
const opts = {
|
|
1515
|
+
url: `file:${config.dbPath}`
|
|
1516
|
+
};
|
|
1517
|
+
if (config.encryptionKey) {
|
|
1518
|
+
opts.encryptionKey = config.encryptionKey;
|
|
1519
|
+
}
|
|
1520
|
+
_client = createClient(opts);
|
|
1521
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1522
|
+
}
|
|
1523
|
+
function isInitialized() {
|
|
1524
|
+
return _client !== null;
|
|
1525
|
+
}
|
|
1526
|
+
function getClient() {
|
|
1527
|
+
if (!_resilientClient) {
|
|
1528
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1529
|
+
}
|
|
1530
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1531
|
+
return _resilientClient;
|
|
1532
|
+
}
|
|
1533
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1534
|
+
return _daemonClient;
|
|
1535
|
+
}
|
|
1536
|
+
return _resilientClient;
|
|
1537
|
+
}
|
|
1538
|
+
async function initDaemonClient() {
|
|
1539
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1540
|
+
if (!_resilientClient) return;
|
|
1541
|
+
try {
|
|
1542
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1543
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
process.stderr.write(
|
|
1546
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1547
|
+
`
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function getRawClient() {
|
|
1552
|
+
if (!_client) {
|
|
1553
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1554
|
+
}
|
|
1555
|
+
return _client;
|
|
1556
|
+
}
|
|
1557
|
+
async function ensureSchema() {
|
|
1558
|
+
const client = getRawClient();
|
|
1559
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
1560
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1561
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
1562
|
+
try {
|
|
1563
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
await client.executeMultiple(`
|
|
1567
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1568
|
+
id TEXT PRIMARY KEY,
|
|
1569
|
+
agent_id TEXT NOT NULL,
|
|
1570
|
+
agent_role TEXT NOT NULL,
|
|
1571
|
+
session_id TEXT NOT NULL,
|
|
1572
|
+
timestamp TEXT NOT NULL,
|
|
1573
|
+
tool_name TEXT NOT NULL,
|
|
1574
|
+
project_name TEXT NOT NULL,
|
|
1575
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
1576
|
+
raw_text TEXT NOT NULL,
|
|
1577
|
+
vector F32_BLOB(1024),
|
|
1578
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
1579
|
+
);
|
|
1580
|
+
|
|
1581
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
1582
|
+
ON memories(agent_id);
|
|
1583
|
+
|
|
1584
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
1585
|
+
ON memories(timestamp);
|
|
1586
|
+
|
|
1587
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
1588
|
+
ON memories(session_id);
|
|
1589
|
+
|
|
1590
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
1591
|
+
ON memories(project_name);
|
|
1592
|
+
|
|
1593
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
1594
|
+
ON memories(tool_name);
|
|
1595
|
+
|
|
1596
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
1597
|
+
ON memories(version);
|
|
1598
|
+
|
|
1599
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
1600
|
+
ON memories(agent_id, project_name);
|
|
1601
|
+
`);
|
|
1602
|
+
await client.executeMultiple(`
|
|
1603
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1604
|
+
raw_text,
|
|
1605
|
+
content='memories',
|
|
1606
|
+
content_rowid='rowid'
|
|
1607
|
+
);
|
|
1608
|
+
|
|
1609
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
1610
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1611
|
+
END;
|
|
1612
|
+
|
|
1613
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
1614
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1615
|
+
END;
|
|
1616
|
+
|
|
1617
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
1618
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1619
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1620
|
+
END;
|
|
1621
|
+
`);
|
|
1622
|
+
await client.executeMultiple(`
|
|
1623
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
1624
|
+
key TEXT PRIMARY KEY,
|
|
1625
|
+
value TEXT NOT NULL
|
|
1626
|
+
);
|
|
1627
|
+
`);
|
|
1628
|
+
await client.executeMultiple(`
|
|
1629
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1630
|
+
id TEXT PRIMARY KEY,
|
|
1631
|
+
title TEXT NOT NULL,
|
|
1632
|
+
assigned_to TEXT NOT NULL,
|
|
1633
|
+
assigned_by TEXT NOT NULL,
|
|
1634
|
+
project_name TEXT NOT NULL,
|
|
1635
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
1636
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
1637
|
+
task_file TEXT,
|
|
1638
|
+
created_at TEXT NOT NULL,
|
|
1639
|
+
updated_at TEXT NOT NULL
|
|
1640
|
+
);
|
|
1641
|
+
|
|
1642
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
1643
|
+
ON tasks(assigned_to, status);
|
|
1644
|
+
`);
|
|
1645
|
+
await client.executeMultiple(`
|
|
1646
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1647
|
+
id TEXT PRIMARY KEY,
|
|
1648
|
+
agent_id TEXT NOT NULL,
|
|
1649
|
+
project_name TEXT,
|
|
1650
|
+
domain TEXT,
|
|
1651
|
+
content TEXT NOT NULL,
|
|
1652
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1653
|
+
created_at TEXT NOT NULL,
|
|
1654
|
+
updated_at TEXT NOT NULL
|
|
1655
|
+
);
|
|
1656
|
+
|
|
1657
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1658
|
+
ON behaviors(agent_id, active);
|
|
1659
|
+
`);
|
|
1660
|
+
try {
|
|
1661
|
+
const coordinatorName = getCoordinatorName();
|
|
1662
|
+
const existing = await client.execute({
|
|
1663
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1664
|
+
args: [coordinatorName]
|
|
1044
1665
|
});
|
|
1666
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1667
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1668
|
+
for (const [domain, content] of [
|
|
1669
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1670
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1671
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1672
|
+
]) {
|
|
1673
|
+
await client.execute({
|
|
1674
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1675
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1676
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1045
1680
|
} catch {
|
|
1046
1681
|
}
|
|
1047
1682
|
try {
|
|
1048
1683
|
await client.execute({
|
|
1049
|
-
sql: `ALTER TABLE
|
|
1684
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1050
1685
|
args: []
|
|
1051
1686
|
});
|
|
1052
1687
|
} catch {
|
|
1053
1688
|
}
|
|
1054
1689
|
try {
|
|
1055
1690
|
await client.execute({
|
|
1056
|
-
sql: `ALTER TABLE
|
|
1691
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1057
1692
|
args: []
|
|
1058
1693
|
});
|
|
1059
1694
|
} catch {
|
|
1060
1695
|
}
|
|
1061
1696
|
try {
|
|
1062
1697
|
await client.execute({
|
|
1063
|
-
sql: `ALTER TABLE
|
|
1698
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1064
1699
|
args: []
|
|
1065
1700
|
});
|
|
1066
1701
|
} catch {
|
|
1067
1702
|
}
|
|
1068
1703
|
try {
|
|
1069
1704
|
await client.execute({
|
|
1070
|
-
sql: `
|
|
1705
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1706
|
+
ON tasks(parent_task_id)
|
|
1707
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1708
|
+
args: []
|
|
1709
|
+
});
|
|
1710
|
+
} catch {
|
|
1711
|
+
}
|
|
1712
|
+
try {
|
|
1713
|
+
await client.execute({
|
|
1714
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1715
|
+
args: []
|
|
1716
|
+
});
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
await client.execute({
|
|
1721
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1722
|
+
args: []
|
|
1723
|
+
});
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
try {
|
|
1727
|
+
await client.execute({
|
|
1728
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1729
|
+
args: []
|
|
1730
|
+
});
|
|
1731
|
+
} catch {
|
|
1732
|
+
}
|
|
1733
|
+
try {
|
|
1734
|
+
await client.execute({
|
|
1735
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1736
|
+
args: []
|
|
1737
|
+
});
|
|
1738
|
+
} catch {
|
|
1739
|
+
}
|
|
1740
|
+
try {
|
|
1741
|
+
await client.execute({
|
|
1742
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1743
|
+
args: []
|
|
1744
|
+
});
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
await client.execute({
|
|
1749
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1750
|
+
args: []
|
|
1751
|
+
});
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
try {
|
|
1755
|
+
await client.execute({
|
|
1756
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1757
|
+
args: []
|
|
1758
|
+
});
|
|
1759
|
+
} catch {
|
|
1760
|
+
}
|
|
1761
|
+
try {
|
|
1762
|
+
await client.execute({
|
|
1763
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1764
|
+
args: []
|
|
1765
|
+
});
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
await client.execute({
|
|
1770
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1771
|
+
args: []
|
|
1772
|
+
});
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
try {
|
|
1776
|
+
await client.execute({
|
|
1777
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1778
|
+
args: []
|
|
1779
|
+
});
|
|
1780
|
+
} catch {
|
|
1781
|
+
}
|
|
1782
|
+
try {
|
|
1783
|
+
await client.execute({
|
|
1784
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1785
|
+
args: []
|
|
1786
|
+
});
|
|
1787
|
+
} catch {
|
|
1788
|
+
}
|
|
1789
|
+
try {
|
|
1790
|
+
await client.execute({
|
|
1791
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1071
1792
|
args: []
|
|
1072
1793
|
});
|
|
1073
1794
|
} catch {
|
|
@@ -1313,6 +2034,12 @@ async function ensureSchema() {
|
|
|
1313
2034
|
} catch {
|
|
1314
2035
|
}
|
|
1315
2036
|
}
|
|
2037
|
+
try {
|
|
2038
|
+
await client.execute(
|
|
2039
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
2040
|
+
);
|
|
2041
|
+
} catch {
|
|
2042
|
+
}
|
|
1316
2043
|
await client.executeMultiple(`
|
|
1317
2044
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1318
2045
|
id TEXT PRIMARY KEY,
|
|
@@ -1365,7 +2092,30 @@ async function ensureSchema() {
|
|
|
1365
2092
|
entity_id TEXT NOT NULL,
|
|
1366
2093
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1367
2094
|
);
|
|
2095
|
+
|
|
2096
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
2097
|
+
name,
|
|
2098
|
+
content=entities,
|
|
2099
|
+
content_rowid=rowid
|
|
2100
|
+
);
|
|
2101
|
+
|
|
2102
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
2103
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2104
|
+
END;
|
|
2105
|
+
|
|
2106
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
2107
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2108
|
+
END;
|
|
2109
|
+
|
|
2110
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
2111
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
2112
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
2113
|
+
END;
|
|
1368
2114
|
`);
|
|
2115
|
+
try {
|
|
2116
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
1369
2119
|
await client.executeMultiple(`
|
|
1370
2120
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1371
2121
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1546,6 +2296,33 @@ async function ensureSchema() {
|
|
|
1546
2296
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1547
2297
|
ON conversations(channel_id);
|
|
1548
2298
|
`);
|
|
2299
|
+
await client.executeMultiple(`
|
|
2300
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
2301
|
+
session_uuid TEXT PRIMARY KEY,
|
|
2302
|
+
agent_id TEXT NOT NULL,
|
|
2303
|
+
session_name TEXT,
|
|
2304
|
+
task_id TEXT,
|
|
2305
|
+
project_name TEXT,
|
|
2306
|
+
started_at TEXT NOT NULL
|
|
2307
|
+
);
|
|
2308
|
+
|
|
2309
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
2310
|
+
ON session_agent_map(agent_id);
|
|
2311
|
+
`);
|
|
2312
|
+
try {
|
|
2313
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
2314
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
2315
|
+
await client.execute({
|
|
2316
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
2317
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
2318
|
+
FROM memories
|
|
2319
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
2320
|
+
GROUP BY session_id, agent_id`,
|
|
2321
|
+
args: []
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
1549
2326
|
try {
|
|
1550
2327
|
await client.execute({
|
|
1551
2328
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1679,15 +2456,41 @@ async function ensureSchema() {
|
|
|
1679
2456
|
});
|
|
1680
2457
|
} catch {
|
|
1681
2458
|
}
|
|
2459
|
+
for (const col of [
|
|
2460
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2461
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2462
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2463
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2464
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2465
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2466
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2467
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2468
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2469
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2470
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2471
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2472
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2473
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2474
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2475
|
+
]) {
|
|
2476
|
+
try {
|
|
2477
|
+
await client.execute(col);
|
|
2478
|
+
} catch {
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
1682
2481
|
}
|
|
1683
2482
|
async function disposeDatabase() {
|
|
2483
|
+
if (_daemonClient) {
|
|
2484
|
+
_daemonClient.close();
|
|
2485
|
+
_daemonClient = null;
|
|
2486
|
+
}
|
|
1684
2487
|
if (_client) {
|
|
1685
2488
|
_client.close();
|
|
1686
2489
|
_client = null;
|
|
1687
2490
|
_resilientClient = null;
|
|
1688
2491
|
}
|
|
1689
2492
|
}
|
|
1690
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2493
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1691
2494
|
var init_database = __esm({
|
|
1692
2495
|
"src/lib/database.ts"() {
|
|
1693
2496
|
"use strict";
|
|
@@ -1695,24 +2498,25 @@ var init_database = __esm({
|
|
|
1695
2498
|
init_employees();
|
|
1696
2499
|
_client = null;
|
|
1697
2500
|
_resilientClient = null;
|
|
2501
|
+
_daemonClient = null;
|
|
1698
2502
|
initTurso = initDatabase;
|
|
1699
2503
|
disposeTurso = disposeDatabase;
|
|
1700
2504
|
}
|
|
1701
2505
|
});
|
|
1702
2506
|
|
|
1703
2507
|
// src/lib/license.ts
|
|
1704
|
-
import { readFileSync as
|
|
1705
|
-
import { randomUUID } from "crypto";
|
|
1706
|
-
import
|
|
2508
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2509
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2510
|
+
import path6 from "path";
|
|
1707
2511
|
import { jwtVerify, importSPKI } from "jose";
|
|
1708
2512
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1709
2513
|
var init_license = __esm({
|
|
1710
2514
|
"src/lib/license.ts"() {
|
|
1711
2515
|
"use strict";
|
|
1712
2516
|
init_config();
|
|
1713
|
-
LICENSE_PATH =
|
|
1714
|
-
CACHE_PATH =
|
|
1715
|
-
DEVICE_ID_PATH =
|
|
2517
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
2518
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
2519
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1716
2520
|
PLAN_LIMITS = {
|
|
1717
2521
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1718
2522
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1724,12 +2528,12 @@ var init_license = __esm({
|
|
|
1724
2528
|
});
|
|
1725
2529
|
|
|
1726
2530
|
// src/lib/plan-limits.ts
|
|
1727
|
-
import { readFileSync as
|
|
1728
|
-
import
|
|
2531
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
2532
|
+
import path7 from "path";
|
|
1729
2533
|
function getLicenseSync() {
|
|
1730
2534
|
try {
|
|
1731
|
-
if (!
|
|
1732
|
-
const raw = JSON.parse(
|
|
2535
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
2536
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1733
2537
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1734
2538
|
const parts = raw.token.split(".");
|
|
1735
2539
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1767,8 +2571,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1767
2571
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1768
2572
|
let count = 0;
|
|
1769
2573
|
try {
|
|
1770
|
-
if (
|
|
1771
|
-
const raw =
|
|
2574
|
+
if (existsSync7(filePath)) {
|
|
2575
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1772
2576
|
const employees = JSON.parse(raw);
|
|
1773
2577
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1774
2578
|
}
|
|
@@ -1797,19 +2601,19 @@ var init_plan_limits = __esm({
|
|
|
1797
2601
|
this.name = "PlanLimitError";
|
|
1798
2602
|
}
|
|
1799
2603
|
};
|
|
1800
|
-
CACHE_PATH2 =
|
|
2604
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1801
2605
|
}
|
|
1802
2606
|
});
|
|
1803
2607
|
|
|
1804
2608
|
// src/lib/notifications.ts
|
|
1805
2609
|
import crypto from "crypto";
|
|
1806
|
-
import
|
|
2610
|
+
import path8 from "path";
|
|
1807
2611
|
import os5 from "os";
|
|
1808
2612
|
import {
|
|
1809
|
-
readFileSync as
|
|
2613
|
+
readFileSync as readFileSync8,
|
|
1810
2614
|
readdirSync,
|
|
1811
|
-
unlinkSync as
|
|
1812
|
-
existsSync as
|
|
2615
|
+
unlinkSync as unlinkSync3,
|
|
2616
|
+
existsSync as existsSync8,
|
|
1813
2617
|
rmdirSync
|
|
1814
2618
|
} from "fs";
|
|
1815
2619
|
async function writeNotification(notification) {
|
|
@@ -1958,6 +2762,11 @@ var init_session_kill_telemetry = __esm({
|
|
|
1958
2762
|
});
|
|
1959
2763
|
|
|
1960
2764
|
// src/lib/task-scope.ts
|
|
2765
|
+
var task_scope_exports = {};
|
|
2766
|
+
__export(task_scope_exports, {
|
|
2767
|
+
getCurrentSessionScope: () => getCurrentSessionScope,
|
|
2768
|
+
sessionScopeFilter: () => sessionScopeFilter
|
|
2769
|
+
});
|
|
1961
2770
|
function getCurrentSessionScope() {
|
|
1962
2771
|
try {
|
|
1963
2772
|
return resolveExeSession();
|
|
@@ -2038,10 +2847,11 @@ var init_state_bus = __esm({
|
|
|
2038
2847
|
|
|
2039
2848
|
// src/lib/tasks-crud.ts
|
|
2040
2849
|
import crypto3 from "crypto";
|
|
2041
|
-
import
|
|
2850
|
+
import path9 from "path";
|
|
2851
|
+
import os6 from "os";
|
|
2042
2852
|
import { execSync as execSync5 } from "child_process";
|
|
2043
2853
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2044
|
-
import { existsSync as
|
|
2854
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
2045
2855
|
async function writeCheckpoint(input) {
|
|
2046
2856
|
const client = getClient();
|
|
2047
2857
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2082,6 +2892,35 @@ function extractParentFromContext(contextBody) {
|
|
|
2082
2892
|
function slugify(title) {
|
|
2083
2893
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2084
2894
|
}
|
|
2895
|
+
function buildKeywordIndex() {
|
|
2896
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2897
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2898
|
+
for (const kw of keywords) {
|
|
2899
|
+
const existing = idx.get(kw) ?? [];
|
|
2900
|
+
existing.push(role);
|
|
2901
|
+
idx.set(kw, existing);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
return idx;
|
|
2905
|
+
}
|
|
2906
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2907
|
+
const employees = loadEmployeesSync();
|
|
2908
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2909
|
+
if (!employee) return void 0;
|
|
2910
|
+
const assigneeRole = employee.role;
|
|
2911
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2912
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2913
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2914
|
+
if (text.includes(keyword)) {
|
|
2915
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2919
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2920
|
+
if (assigneeRole === "COO") return void 0;
|
|
2921
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2922
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2923
|
+
}
|
|
2085
2924
|
async function resolveTask(client, identifier, scopeSession) {
|
|
2086
2925
|
const scope = sessionScopeFilter(scopeSession);
|
|
2087
2926
|
let result = await client.execute({
|
|
@@ -2131,7 +2970,14 @@ async function createTaskCore(input) {
|
|
|
2131
2970
|
const id = crypto3.randomUUID();
|
|
2132
2971
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2133
2972
|
const slug = slugify(input.title);
|
|
2134
|
-
|
|
2973
|
+
let earlySessionScope = null;
|
|
2974
|
+
try {
|
|
2975
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2976
|
+
earlySessionScope = resolveExeSession2();
|
|
2977
|
+
} catch {
|
|
2978
|
+
}
|
|
2979
|
+
const scope = earlySessionScope ?? "default";
|
|
2980
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
2135
2981
|
let blockedById = null;
|
|
2136
2982
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
2137
2983
|
if (input.blockedBy) {
|
|
@@ -2171,22 +3017,24 @@ async function createTaskCore(input) {
|
|
|
2171
3017
|
if (dupCheck.rows.length > 0) {
|
|
2172
3018
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
2173
3019
|
}
|
|
3020
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
3021
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
3022
|
+
if (laneWarning) {
|
|
3023
|
+
warning = warning ? `${warning}
|
|
3024
|
+
${laneWarning}` : laneWarning;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
2174
3027
|
if (input.baseDir) {
|
|
2175
3028
|
try {
|
|
2176
|
-
await mkdir3(
|
|
2177
|
-
await mkdir3(
|
|
3029
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3030
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2178
3031
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2179
3032
|
await ensureGitignoreExe(input.baseDir);
|
|
2180
3033
|
} catch {
|
|
2181
3034
|
}
|
|
2182
3035
|
}
|
|
2183
3036
|
const complexity = input.complexity ?? "standard";
|
|
2184
|
-
|
|
2185
|
-
try {
|
|
2186
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2187
|
-
sessionScope = resolveExeSession2();
|
|
2188
|
-
} catch {
|
|
2189
|
-
}
|
|
3037
|
+
const sessionScope = earlySessionScope;
|
|
2190
3038
|
await client.execute({
|
|
2191
3039
|
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)
|
|
2192
3040
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2213,6 +3061,39 @@ async function createTaskCore(input) {
|
|
|
2213
3061
|
now
|
|
2214
3062
|
]
|
|
2215
3063
|
});
|
|
3064
|
+
if (input.baseDir) {
|
|
3065
|
+
try {
|
|
3066
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
3067
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
3068
|
+
const mdDir = path9.dirname(mdPath);
|
|
3069
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
3070
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3071
|
+
const mdContent = `# ${input.title}
|
|
3072
|
+
|
|
3073
|
+
**ID:** ${id}
|
|
3074
|
+
**Status:** ${initialStatus}
|
|
3075
|
+
**Priority:** ${input.priority}
|
|
3076
|
+
**Assigned by:** ${input.assignedBy}
|
|
3077
|
+
**Assigned to:** ${input.assignedTo}
|
|
3078
|
+
**Project:** ${input.projectName}
|
|
3079
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
3080
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
3081
|
+
**Reviewer:** ${reviewer}
|
|
3082
|
+
|
|
3083
|
+
## Context
|
|
3084
|
+
|
|
3085
|
+
${input.context}
|
|
3086
|
+
|
|
3087
|
+
## MANDATORY: When done
|
|
3088
|
+
|
|
3089
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3090
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3091
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3092
|
+
`;
|
|
3093
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
3094
|
+
} catch {
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
2216
3097
|
return {
|
|
2217
3098
|
id,
|
|
2218
3099
|
title: input.title,
|
|
@@ -2405,7 +3286,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2405
3286
|
return { row, taskFile, now, taskId };
|
|
2406
3287
|
}
|
|
2407
3288
|
}
|
|
2408
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
3289
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2409
3290
|
process.stderr.write(
|
|
2410
3291
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2411
3292
|
`
|
|
@@ -2470,9 +3351,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2470
3351
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2471
3352
|
}
|
|
2472
3353
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2473
|
-
const archPath =
|
|
3354
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2474
3355
|
try {
|
|
2475
|
-
if (
|
|
3356
|
+
if (existsSync9(archPath)) return;
|
|
2476
3357
|
const template = [
|
|
2477
3358
|
`# ${projectName} \u2014 System Architecture`,
|
|
2478
3359
|
"",
|
|
@@ -2505,10 +3386,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2505
3386
|
}
|
|
2506
3387
|
}
|
|
2507
3388
|
async function ensureGitignoreExe(baseDir) {
|
|
2508
|
-
const gitignorePath =
|
|
3389
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
2509
3390
|
try {
|
|
2510
|
-
if (
|
|
2511
|
-
const content =
|
|
3391
|
+
if (existsSync9(gitignorePath)) {
|
|
3392
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2512
3393
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2513
3394
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2514
3395
|
} else {
|
|
@@ -2517,12 +3398,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2517
3398
|
} catch {
|
|
2518
3399
|
}
|
|
2519
3400
|
}
|
|
2520
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3401
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2521
3402
|
var init_tasks_crud = __esm({
|
|
2522
3403
|
"src/lib/tasks-crud.ts"() {
|
|
2523
3404
|
"use strict";
|
|
2524
3405
|
init_database();
|
|
2525
3406
|
init_task_scope();
|
|
3407
|
+
init_employees();
|
|
3408
|
+
LANE_KEYWORDS = {
|
|
3409
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
3410
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
3411
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
3412
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
3413
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
3414
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
3415
|
+
};
|
|
3416
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2526
3417
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2527
3418
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2528
3419
|
}
|
|
@@ -2539,8 +3430,8 @@ __export(tasks_review_exports, {
|
|
|
2539
3430
|
getReviewChecklist: () => getReviewChecklist,
|
|
2540
3431
|
listPendingReviews: () => listPendingReviews
|
|
2541
3432
|
});
|
|
2542
|
-
import
|
|
2543
|
-
import { existsSync as
|
|
3433
|
+
import path10 from "path";
|
|
3434
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2544
3435
|
async function countPendingReviews(sessionScope) {
|
|
2545
3436
|
const client = getClient();
|
|
2546
3437
|
if (sessionScope) {
|
|
@@ -2562,7 +3453,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2562
3453
|
const result2 = await client.execute({
|
|
2563
3454
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2564
3455
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2565
|
-
AND
|
|
3456
|
+
AND session_scope = ?`,
|
|
2566
3457
|
args: [sinceIso, sessionScope]
|
|
2567
3458
|
});
|
|
2568
3459
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2580,7 +3471,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2580
3471
|
const result2 = await client.execute({
|
|
2581
3472
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2582
3473
|
WHERE status = 'needs_review'
|
|
2583
|
-
AND
|
|
3474
|
+
AND session_scope = ?
|
|
2584
3475
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2585
3476
|
args: [sessionScope, limit]
|
|
2586
3477
|
});
|
|
@@ -2681,7 +3572,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
2681
3572
|
const taskFile = String(row.task_file);
|
|
2682
3573
|
const employees = await loadEmployees();
|
|
2683
3574
|
const coordinatorName = getCoordinatorName(employees);
|
|
2684
|
-
if (
|
|
3575
|
+
if (isCoordinatorName(String(row.assigned_to), employees)) return;
|
|
2685
3576
|
if (String(row.title).startsWith("Review:")) return;
|
|
2686
3577
|
const fileName = taskFile.split("/").pop() ?? "";
|
|
2687
3578
|
if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
|
|
@@ -2790,14 +3681,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2790
3681
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2791
3682
|
const agent = parts[1];
|
|
2792
3683
|
const slug = parts.slice(2).join("-");
|
|
2793
|
-
const
|
|
3684
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2794
3685
|
const result = await client.execute({
|
|
2795
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2796
|
-
args: [now,
|
|
3686
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3687
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2797
3688
|
});
|
|
2798
3689
|
if (result.rowsAffected > 0) {
|
|
2799
3690
|
process.stderr.write(
|
|
2800
|
-
`[review-cleanup] Cascaded original task to done
|
|
3691
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2801
3692
|
`
|
|
2802
3693
|
);
|
|
2803
3694
|
}
|
|
@@ -2810,11 +3701,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2810
3701
|
);
|
|
2811
3702
|
}
|
|
2812
3703
|
try {
|
|
2813
|
-
const cacheDir =
|
|
2814
|
-
if (
|
|
3704
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
3705
|
+
if (existsSync10(cacheDir)) {
|
|
2815
3706
|
for (const f of readdirSync2(cacheDir)) {
|
|
2816
3707
|
if (f.startsWith("review-notified-")) {
|
|
2817
|
-
|
|
3708
|
+
unlinkSync4(path10.join(cacheDir, f));
|
|
2818
3709
|
}
|
|
2819
3710
|
}
|
|
2820
3711
|
}
|
|
@@ -2835,7 +3726,7 @@ var init_tasks_review = __esm({
|
|
|
2835
3726
|
});
|
|
2836
3727
|
|
|
2837
3728
|
// src/lib/tasks-chain.ts
|
|
2838
|
-
import
|
|
3729
|
+
import path11 from "path";
|
|
2839
3730
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2840
3731
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2841
3732
|
const client = getClient();
|
|
@@ -2852,7 +3743,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2852
3743
|
});
|
|
2853
3744
|
for (const ur of unblockedRows.rows) {
|
|
2854
3745
|
try {
|
|
2855
|
-
const ubFile =
|
|
3746
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
2856
3747
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2857
3748
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2858
3749
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2921,7 +3812,7 @@ var init_tasks_chain = __esm({
|
|
|
2921
3812
|
|
|
2922
3813
|
// src/lib/project-name.ts
|
|
2923
3814
|
import { execSync as execSync6 } from "child_process";
|
|
2924
|
-
import
|
|
3815
|
+
import path12 from "path";
|
|
2925
3816
|
function getProjectName(cwd) {
|
|
2926
3817
|
const dir = cwd ?? process.cwd();
|
|
2927
3818
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2934,7 +3825,7 @@ function getProjectName(cwd) {
|
|
|
2934
3825
|
timeout: 2e3,
|
|
2935
3826
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2936
3827
|
}).trim();
|
|
2937
|
-
repoRoot =
|
|
3828
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
2938
3829
|
} catch {
|
|
2939
3830
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2940
3831
|
cwd: dir,
|
|
@@ -2943,11 +3834,11 @@ function getProjectName(cwd) {
|
|
|
2943
3834
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2944
3835
|
}).trim();
|
|
2945
3836
|
}
|
|
2946
|
-
_cached2 =
|
|
3837
|
+
_cached2 = path12.basename(repoRoot);
|
|
2947
3838
|
_cachedCwd = dir;
|
|
2948
3839
|
return _cached2;
|
|
2949
3840
|
} catch {
|
|
2950
|
-
_cached2 =
|
|
3841
|
+
_cached2 = path12.basename(dir);
|
|
2951
3842
|
_cachedCwd = dir;
|
|
2952
3843
|
return _cached2;
|
|
2953
3844
|
}
|
|
@@ -2979,7 +3870,7 @@ function findSessionForProject(projectName) {
|
|
|
2979
3870
|
const sessions = listSessions();
|
|
2980
3871
|
for (const s of sessions) {
|
|
2981
3872
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2982
|
-
if (proj === projectName &&
|
|
3873
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2983
3874
|
}
|
|
2984
3875
|
return null;
|
|
2985
3876
|
}
|
|
@@ -3025,7 +3916,7 @@ var init_session_scope = __esm({
|
|
|
3025
3916
|
|
|
3026
3917
|
// src/lib/tasks-notify.ts
|
|
3027
3918
|
async function dispatchTaskToEmployee(input) {
|
|
3028
|
-
if (
|
|
3919
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
3029
3920
|
let crossProject = false;
|
|
3030
3921
|
if (input.projectName) {
|
|
3031
3922
|
try {
|
|
@@ -3420,8 +4311,8 @@ __export(tasks_exports, {
|
|
|
3420
4311
|
updateTaskStatus: () => updateTaskStatus,
|
|
3421
4312
|
writeCheckpoint: () => writeCheckpoint
|
|
3422
4313
|
});
|
|
3423
|
-
import
|
|
3424
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
4314
|
+
import path13 from "path";
|
|
4315
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3425
4316
|
async function createTask(input) {
|
|
3426
4317
|
const result = await createTaskCore(input);
|
|
3427
4318
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3440,14 +4331,14 @@ async function updateTask(input) {
|
|
|
3440
4331
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3441
4332
|
try {
|
|
3442
4333
|
const agent = String(row.assigned_to);
|
|
3443
|
-
const cacheDir =
|
|
3444
|
-
const cachePath =
|
|
4334
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
4335
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
3445
4336
|
if (input.status === "in_progress") {
|
|
3446
4337
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3447
4338
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3448
4339
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3449
4340
|
try {
|
|
3450
|
-
|
|
4341
|
+
unlinkSync5(cachePath);
|
|
3451
4342
|
} catch {
|
|
3452
4343
|
}
|
|
3453
4344
|
}
|
|
@@ -3504,7 +4395,7 @@ async function updateTask(input) {
|
|
|
3504
4395
|
}
|
|
3505
4396
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3506
4397
|
if (isTerminal) {
|
|
3507
|
-
const isCoordinator =
|
|
4398
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3508
4399
|
if (!isCoordinator) {
|
|
3509
4400
|
notifyTaskDone();
|
|
3510
4401
|
}
|
|
@@ -3529,7 +4420,7 @@ async function updateTask(input) {
|
|
|
3529
4420
|
}
|
|
3530
4421
|
}
|
|
3531
4422
|
}
|
|
3532
|
-
if (input.status === "done" &&
|
|
4423
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3533
4424
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3534
4425
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3535
4426
|
taskId,
|
|
@@ -3545,7 +4436,7 @@ async function updateTask(input) {
|
|
|
3545
4436
|
});
|
|
3546
4437
|
}
|
|
3547
4438
|
let nextTask;
|
|
3548
|
-
if (isTerminal &&
|
|
4439
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3549
4440
|
try {
|
|
3550
4441
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3551
4442
|
} catch {
|
|
@@ -3889,7 +4780,7 @@ var init_capacity_monitor = __esm({
|
|
|
3889
4780
|
// src/lib/tmux-routing.ts
|
|
3890
4781
|
var tmux_routing_exports = {};
|
|
3891
4782
|
__export(tmux_routing_exports, {
|
|
3892
|
-
acquireSpawnLock: () =>
|
|
4783
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3893
4784
|
employeeSessionName: () => employeeSessionName,
|
|
3894
4785
|
ensureEmployee: () => ensureEmployee,
|
|
3895
4786
|
extractRootExe: () => extractRootExe,
|
|
@@ -3904,20 +4795,20 @@ __export(tmux_routing_exports, {
|
|
|
3904
4795
|
notifyParentExe: () => notifyParentExe,
|
|
3905
4796
|
parseParentExe: () => parseParentExe,
|
|
3906
4797
|
registerParentExe: () => registerParentExe,
|
|
3907
|
-
releaseSpawnLock: () =>
|
|
4798
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3908
4799
|
resolveExeSession: () => resolveExeSession,
|
|
3909
4800
|
sendIntercom: () => sendIntercom,
|
|
3910
4801
|
spawnEmployee: () => spawnEmployee,
|
|
3911
4802
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3912
4803
|
});
|
|
3913
4804
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3914
|
-
import { readFileSync as
|
|
3915
|
-
import
|
|
3916
|
-
import
|
|
3917
|
-
import { fileURLToPath } from "url";
|
|
3918
|
-
import { unlinkSync as
|
|
4805
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4806
|
+
import path14 from "path";
|
|
4807
|
+
import os7 from "os";
|
|
4808
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4809
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3919
4810
|
function spawnLockPath(sessionName) {
|
|
3920
|
-
return
|
|
4811
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3921
4812
|
}
|
|
3922
4813
|
function isProcessAlive(pid) {
|
|
3923
4814
|
try {
|
|
@@ -3927,14 +4818,14 @@ function isProcessAlive(pid) {
|
|
|
3927
4818
|
return false;
|
|
3928
4819
|
}
|
|
3929
4820
|
}
|
|
3930
|
-
function
|
|
3931
|
-
if (!
|
|
4821
|
+
function acquireSpawnLock2(sessionName) {
|
|
4822
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3932
4823
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3933
4824
|
}
|
|
3934
4825
|
const lockFile = spawnLockPath(sessionName);
|
|
3935
|
-
if (
|
|
4826
|
+
if (existsSync11(lockFile)) {
|
|
3936
4827
|
try {
|
|
3937
|
-
const lock = JSON.parse(
|
|
4828
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3938
4829
|
const age = Date.now() - lock.timestamp;
|
|
3939
4830
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3940
4831
|
return false;
|
|
@@ -3945,22 +4836,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3945
4836
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3946
4837
|
return true;
|
|
3947
4838
|
}
|
|
3948
|
-
function
|
|
4839
|
+
function releaseSpawnLock2(sessionName) {
|
|
3949
4840
|
try {
|
|
3950
|
-
|
|
4841
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3951
4842
|
} catch {
|
|
3952
4843
|
}
|
|
3953
4844
|
}
|
|
3954
4845
|
function resolveBehaviorsExporterScript() {
|
|
3955
4846
|
try {
|
|
3956
|
-
const thisFile =
|
|
3957
|
-
const scriptPath =
|
|
3958
|
-
|
|
4847
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4848
|
+
const scriptPath = path14.join(
|
|
4849
|
+
path14.dirname(thisFile),
|
|
3959
4850
|
"..",
|
|
3960
4851
|
"bin",
|
|
3961
4852
|
"exe-export-behaviors.js"
|
|
3962
4853
|
);
|
|
3963
|
-
return
|
|
4854
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3964
4855
|
} catch {
|
|
3965
4856
|
return null;
|
|
3966
4857
|
}
|
|
@@ -4026,11 +4917,11 @@ function extractRootExe(name) {
|
|
|
4026
4917
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4027
4918
|
}
|
|
4028
4919
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4029
|
-
if (!
|
|
4920
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
4030
4921
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
4031
4922
|
}
|
|
4032
4923
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4033
|
-
const filePath =
|
|
4924
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4034
4925
|
writeFileSync6(filePath, JSON.stringify({
|
|
4035
4926
|
parentExe: rootExe,
|
|
4036
4927
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -4039,7 +4930,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
4039
4930
|
}
|
|
4040
4931
|
function getParentExe(sessionKey) {
|
|
4041
4932
|
try {
|
|
4042
|
-
const data = JSON.parse(
|
|
4933
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4043
4934
|
return data.parentExe || null;
|
|
4044
4935
|
} catch {
|
|
4045
4936
|
return null;
|
|
@@ -4047,8 +4938,8 @@ function getParentExe(sessionKey) {
|
|
|
4047
4938
|
}
|
|
4048
4939
|
function getDispatchedBy(sessionKey) {
|
|
4049
4940
|
try {
|
|
4050
|
-
const data = JSON.parse(
|
|
4051
|
-
|
|
4941
|
+
const data = JSON.parse(readFileSync10(
|
|
4942
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4052
4943
|
"utf8"
|
|
4053
4944
|
));
|
|
4054
4945
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -4074,10 +4965,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
4074
4965
|
}
|
|
4075
4966
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4076
4967
|
const base = employeeSessionName(employeeName, exeSession);
|
|
4077
|
-
if (!isAlive(base) &&
|
|
4968
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4078
4969
|
for (let i = 2; i <= maxInstances; i++) {
|
|
4079
4970
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4080
|
-
if (!isAlive(candidate) &&
|
|
4971
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4081
4972
|
}
|
|
4082
4973
|
return null;
|
|
4083
4974
|
}
|
|
@@ -4109,15 +5000,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
4109
5000
|
}
|
|
4110
5001
|
function readDebounceState() {
|
|
4111
5002
|
try {
|
|
4112
|
-
if (!
|
|
4113
|
-
return JSON.parse(
|
|
5003
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
5004
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
4114
5005
|
} catch {
|
|
4115
5006
|
return {};
|
|
4116
5007
|
}
|
|
4117
5008
|
}
|
|
4118
5009
|
function writeDebounceState(state) {
|
|
4119
5010
|
try {
|
|
4120
|
-
if (!
|
|
5011
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
4121
5012
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4122
5013
|
} catch {
|
|
4123
5014
|
}
|
|
@@ -4237,7 +5128,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4237
5128
|
return true;
|
|
4238
5129
|
}
|
|
4239
5130
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4240
|
-
if (
|
|
5131
|
+
if (isCoordinatorName(employeeName)) {
|
|
4241
5132
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4242
5133
|
}
|
|
4243
5134
|
try {
|
|
@@ -4309,26 +5200,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4309
5200
|
const transport = getTransport();
|
|
4310
5201
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4311
5202
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4312
|
-
const logDir =
|
|
4313
|
-
const logFile =
|
|
4314
|
-
if (!
|
|
5203
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
5204
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5205
|
+
if (!existsSync11(logDir)) {
|
|
4315
5206
|
mkdirSync5(logDir, { recursive: true });
|
|
4316
5207
|
}
|
|
4317
5208
|
transport.kill(sessionName);
|
|
4318
5209
|
let cleanupSuffix = "";
|
|
4319
5210
|
try {
|
|
4320
|
-
const thisFile =
|
|
4321
|
-
const cleanupScript =
|
|
4322
|
-
if (
|
|
5211
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
5212
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5213
|
+
if (existsSync11(cleanupScript)) {
|
|
4323
5214
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4324
5215
|
}
|
|
4325
5216
|
} catch {
|
|
4326
5217
|
}
|
|
4327
5218
|
try {
|
|
4328
|
-
const claudeJsonPath =
|
|
5219
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
4329
5220
|
let claudeJson = {};
|
|
4330
5221
|
try {
|
|
4331
|
-
claudeJson = JSON.parse(
|
|
5222
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
4332
5223
|
} catch {
|
|
4333
5224
|
}
|
|
4334
5225
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4340,13 +5231,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4340
5231
|
} catch {
|
|
4341
5232
|
}
|
|
4342
5233
|
try {
|
|
4343
|
-
const settingsDir =
|
|
5234
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
4344
5235
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4345
|
-
const projSettingsDir =
|
|
4346
|
-
const settingsPath =
|
|
5236
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
5237
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
4347
5238
|
let settings = {};
|
|
4348
5239
|
try {
|
|
4349
|
-
settings = JSON.parse(
|
|
5240
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
4350
5241
|
} catch {
|
|
4351
5242
|
}
|
|
4352
5243
|
const perms = settings.permissions ?? {};
|
|
@@ -4387,8 +5278,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4387
5278
|
let behaviorsFlag = "";
|
|
4388
5279
|
let legacyFallbackWarned = false;
|
|
4389
5280
|
if (!useExeAgent && !useBinSymlink) {
|
|
4390
|
-
const identityPath =
|
|
4391
|
-
|
|
5281
|
+
const identityPath = path14.join(
|
|
5282
|
+
os7.homedir(),
|
|
4392
5283
|
".exe-os",
|
|
4393
5284
|
"identity",
|
|
4394
5285
|
`${employeeName}.md`
|
|
@@ -4397,13 +5288,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4397
5288
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4398
5289
|
if (hasAgentFlag) {
|
|
4399
5290
|
identityFlag = ` --agent ${employeeName}`;
|
|
4400
|
-
} else if (
|
|
5291
|
+
} else if (existsSync11(identityPath)) {
|
|
4401
5292
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4402
5293
|
legacyFallbackWarned = true;
|
|
4403
5294
|
}
|
|
4404
5295
|
const behaviorsFile = exportBehaviorsSync(
|
|
4405
5296
|
employeeName,
|
|
4406
|
-
|
|
5297
|
+
path14.basename(spawnCwd),
|
|
4407
5298
|
sessionName
|
|
4408
5299
|
);
|
|
4409
5300
|
if (behaviorsFile) {
|
|
@@ -4418,9 +5309,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4418
5309
|
}
|
|
4419
5310
|
let sessionContextFlag = "";
|
|
4420
5311
|
try {
|
|
4421
|
-
const ctxDir =
|
|
5312
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4422
5313
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4423
|
-
const ctxFile =
|
|
5314
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4424
5315
|
const ctxContent = [
|
|
4425
5316
|
`## Session Context`,
|
|
4426
5317
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4459,13 +5350,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4459
5350
|
command: spawnCommand
|
|
4460
5351
|
});
|
|
4461
5352
|
if (spawnResult.error) {
|
|
4462
|
-
|
|
5353
|
+
releaseSpawnLock2(sessionName);
|
|
4463
5354
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4464
5355
|
}
|
|
4465
5356
|
transport.pipeLog(sessionName, logFile);
|
|
4466
5357
|
try {
|
|
4467
5358
|
const mySession = getMySession();
|
|
4468
|
-
const dispatchInfo =
|
|
5359
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4469
5360
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4470
5361
|
dispatchedBy: mySession,
|
|
4471
5362
|
rootExe: exeSession,
|
|
@@ -4497,7 +5388,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4497
5388
|
}
|
|
4498
5389
|
}
|
|
4499
5390
|
if (!booted) {
|
|
4500
|
-
|
|
5391
|
+
releaseSpawnLock2(sessionName);
|
|
4501
5392
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4502
5393
|
}
|
|
4503
5394
|
if (!useExeAgent) {
|
|
@@ -4514,7 +5405,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4514
5405
|
pid: 0,
|
|
4515
5406
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4516
5407
|
});
|
|
4517
|
-
|
|
5408
|
+
releaseSpawnLock2(sessionName);
|
|
4518
5409
|
return { sessionName };
|
|
4519
5410
|
}
|
|
4520
5411
|
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;
|
|
@@ -4530,14 +5421,14 @@ var init_tmux_routing = __esm({
|
|
|
4530
5421
|
init_intercom_queue();
|
|
4531
5422
|
init_plan_limits();
|
|
4532
5423
|
init_employees();
|
|
4533
|
-
SPAWN_LOCK_DIR =
|
|
4534
|
-
SESSION_CACHE =
|
|
5424
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
5425
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4535
5426
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4536
5427
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4537
5428
|
VERIFY_PANE_LINES = 200;
|
|
4538
5429
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4539
|
-
INTERCOM_LOG2 =
|
|
4540
|
-
DEBOUNCE_FILE =
|
|
5430
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
5431
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4541
5432
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4542
5433
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4543
5434
|
}
|
|
@@ -4821,9 +5712,9 @@ __export(agent_signals_exports, {
|
|
|
4821
5712
|
hasOpenTasks: () => hasOpenTasks,
|
|
4822
5713
|
hasUnreadInbox: () => hasUnreadInbox
|
|
4823
5714
|
});
|
|
4824
|
-
import { readFileSync as
|
|
4825
|
-
import
|
|
4826
|
-
import
|
|
5715
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
5716
|
+
import os8 from "os";
|
|
5717
|
+
import path15 from "path";
|
|
4827
5718
|
async function hasOpenTasks(client, agentId) {
|
|
4828
5719
|
try {
|
|
4829
5720
|
const scope = sessionScopeFilter(null);
|
|
@@ -4865,10 +5756,10 @@ async function hasUnreadInbox(client, agentId) {
|
|
|
4865
5756
|
return CONSERVATIVE_ON_ERROR;
|
|
4866
5757
|
}
|
|
4867
5758
|
}
|
|
4868
|
-
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog =
|
|
4869
|
-
if (!
|
|
5759
|
+
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path15.join(os8.homedir(), ".exe-os", "intercom.log")) {
|
|
5760
|
+
if (!existsSync12(intercomLog)) return false;
|
|
4870
5761
|
try {
|
|
4871
|
-
const raw =
|
|
5762
|
+
const raw = readFileSync11(intercomLog, "utf8");
|
|
4872
5763
|
const lines = raw.split("\n");
|
|
4873
5764
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
4874
5765
|
const line = lines[i];
|
|
@@ -4910,6 +5801,8 @@ var init_agent_signals = __esm({
|
|
|
4910
5801
|
// src/lib/daemon-orchestration.ts
|
|
4911
5802
|
var daemon_orchestration_exports = {};
|
|
4912
5803
|
__export(daemon_orchestration_exports, {
|
|
5804
|
+
AUTO_WAKE_COOLDOWN_MS: () => AUTO_WAKE_COOLDOWN_MS,
|
|
5805
|
+
AUTO_WAKE_MAX_RETRIES: () => AUTO_WAKE_MAX_RETRIES,
|
|
4913
5806
|
IDLE_KILL_INTERCOM_ACK_WINDOW_MS: () => IDLE_KILL_INTERCOM_ACK_WINDOW_MS,
|
|
4914
5807
|
IDLE_NUDGE_DEDUP_MS: () => IDLE_NUDGE_DEDUP_MS,
|
|
4915
5808
|
ORPHAN_PATTERNS: () => ORPHAN_PATTERNS,
|
|
@@ -4917,8 +5810,10 @@ __export(daemon_orchestration_exports, {
|
|
|
4917
5810
|
REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
|
|
4918
5811
|
SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
|
|
4919
5812
|
SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
|
|
5813
|
+
_resetAutoWakeState: () => _resetAutoWakeState,
|
|
4920
5814
|
checkSessionTTL: () => checkSessionTTL,
|
|
4921
5815
|
classifyTtlKillReason: () => classifyTtlKillReason,
|
|
5816
|
+
createAutoWakeRealDeps: () => createAutoWakeRealDeps,
|
|
4922
5817
|
createIdleKillRealDeps: () => createIdleKillRealDeps,
|
|
4923
5818
|
createIdleNudgeRealDeps: () => createIdleNudgeRealDeps,
|
|
4924
5819
|
createOrphanReaperRealDeps: () => createOrphanReaperRealDeps,
|
|
@@ -4927,15 +5822,17 @@ __export(daemon_orchestration_exports, {
|
|
|
4927
5822
|
loadNudgeState: () => loadNudgeState,
|
|
4928
5823
|
pollIdleEmployees: () => pollIdleEmployees,
|
|
4929
5824
|
pollIdleKill: () => pollIdleKill,
|
|
5825
|
+
pollOrphanedTasks: () => pollOrphanedTasks,
|
|
4930
5826
|
pollReviewNudge: () => pollReviewNudge,
|
|
4931
5827
|
reapOrphanedMcpProcesses: () => reapOrphanedMcpProcesses,
|
|
4932
5828
|
saveNudgeState: () => saveNudgeState,
|
|
5829
|
+
shouldAutoWake: () => shouldAutoWake,
|
|
4933
5830
|
shouldKillIdleSession: () => shouldKillIdleSession,
|
|
4934
5831
|
shouldKillSession: () => shouldKillSession,
|
|
4935
5832
|
shouldNudgeEmployee: () => shouldNudgeEmployee
|
|
4936
5833
|
});
|
|
4937
5834
|
import { execSync as execSync9 } from "child_process";
|
|
4938
|
-
import { existsSync as
|
|
5835
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
4939
5836
|
import { homedir } from "os";
|
|
4940
5837
|
import { join } from "path";
|
|
4941
5838
|
function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
|
|
@@ -4965,7 +5862,7 @@ function shouldKillIdleSession(input) {
|
|
|
4965
5862
|
}
|
|
4966
5863
|
async function pollIdleEmployees(deps, lastNudge) {
|
|
4967
5864
|
const registered = deps.listRegisteredSessions().filter(
|
|
4968
|
-
(s) =>
|
|
5865
|
+
(s) => !isCoordinatorName(s.agentId)
|
|
4969
5866
|
);
|
|
4970
5867
|
if (registered.length === 0) return [];
|
|
4971
5868
|
let liveSessions;
|
|
@@ -4994,7 +5891,7 @@ async function pollIdleEmployees(deps, lastNudge) {
|
|
|
4994
5891
|
}
|
|
4995
5892
|
function checkSessionTTL(deps) {
|
|
4996
5893
|
const registered = deps.listRegisteredSessions().filter(
|
|
4997
|
-
(s) =>
|
|
5894
|
+
(s) => !isCoordinatorName(s.agentId)
|
|
4998
5895
|
);
|
|
4999
5896
|
if (registered.length === 0) return [];
|
|
5000
5897
|
let liveSessions;
|
|
@@ -5030,7 +5927,7 @@ function checkSessionTTL(deps) {
|
|
|
5030
5927
|
async function pollIdleKill(deps, idleTickCounts, opts) {
|
|
5031
5928
|
if (!opts.enabled) return [];
|
|
5032
5929
|
const registered = deps.listRegisteredSessions().filter(
|
|
5033
|
-
(s) =>
|
|
5930
|
+
(s) => !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
|
|
5034
5931
|
);
|
|
5035
5932
|
if (registered.length === 0) return [];
|
|
5036
5933
|
let liveSessions;
|
|
@@ -5126,8 +6023,8 @@ async function pollReviewNudge(deps, state) {
|
|
|
5126
6023
|
function loadNudgeState() {
|
|
5127
6024
|
const state = { lastNudge: /* @__PURE__ */ new Map() };
|
|
5128
6025
|
try {
|
|
5129
|
-
if (!
|
|
5130
|
-
const raw = JSON.parse(
|
|
6026
|
+
if (!existsSync13(NUDGE_STATE_PATH)) return state;
|
|
6027
|
+
const raw = JSON.parse(readFileSync12(NUDGE_STATE_PATH, "utf8"));
|
|
5131
6028
|
if (Array.isArray(raw)) {
|
|
5132
6029
|
for (const [key, val] of raw) {
|
|
5133
6030
|
if (key && typeof val?.at === "number" && typeof val?.count === "number") {
|
|
@@ -5268,6 +6165,134 @@ function createIdleKillRealDeps(getClient2, intercomAckWindowMs) {
|
|
|
5268
6165
|
}
|
|
5269
6166
|
};
|
|
5270
6167
|
}
|
|
6168
|
+
function _resetAutoWakeState() {
|
|
6169
|
+
_autoWakeLastSpawn.clear();
|
|
6170
|
+
_autoWakeTaskRetries.clear();
|
|
6171
|
+
}
|
|
6172
|
+
function shouldAutoWake(input) {
|
|
6173
|
+
if (isCoordinatorName(input.agentId)) return false;
|
|
6174
|
+
if (input.hasRunningSession) return false;
|
|
6175
|
+
if (input.nowMs - input.lastSpawnMs < input.cooldownMs) return false;
|
|
6176
|
+
return true;
|
|
6177
|
+
}
|
|
6178
|
+
async function pollOrphanedTasks(deps, nowMs = Date.now()) {
|
|
6179
|
+
let liveSessions;
|
|
6180
|
+
try {
|
|
6181
|
+
liveSessions = deps.listTmuxSessions();
|
|
6182
|
+
} catch {
|
|
6183
|
+
return [];
|
|
6184
|
+
}
|
|
6185
|
+
const liveAgents = /* @__PURE__ */ new Set();
|
|
6186
|
+
for (const session of liveSessions) {
|
|
6187
|
+
const agent = deps.parseAgentFromSession(session);
|
|
6188
|
+
if (agent) liveAgents.add(agent);
|
|
6189
|
+
}
|
|
6190
|
+
let tasksByAgent;
|
|
6191
|
+
try {
|
|
6192
|
+
tasksByAgent = await deps.queryTasksByAgent();
|
|
6193
|
+
} catch {
|
|
6194
|
+
return [];
|
|
6195
|
+
}
|
|
6196
|
+
const agentTasks = /* @__PURE__ */ new Map();
|
|
6197
|
+
for (const t of tasksByAgent) {
|
|
6198
|
+
const existing = agentTasks.get(t.agentId) ?? [];
|
|
6199
|
+
existing.push({ taskId: t.taskId, priority: t.priority });
|
|
6200
|
+
agentTasks.set(t.agentId, existing);
|
|
6201
|
+
}
|
|
6202
|
+
const woken = [];
|
|
6203
|
+
for (const [agentId, tasks] of agentTasks) {
|
|
6204
|
+
if (!shouldAutoWake({
|
|
6205
|
+
agentId,
|
|
6206
|
+
hasRunningSession: liveAgents.has(agentId),
|
|
6207
|
+
lastSpawnMs: _autoWakeLastSpawn.get(agentId) ?? 0,
|
|
6208
|
+
nowMs,
|
|
6209
|
+
cooldownMs: AUTO_WAKE_COOLDOWN_MS
|
|
6210
|
+
})) {
|
|
6211
|
+
continue;
|
|
6212
|
+
}
|
|
6213
|
+
const topTask = tasks[0];
|
|
6214
|
+
const retries = _autoWakeTaskRetries.get(topTask.taskId) ?? 0;
|
|
6215
|
+
if (retries >= AUTO_WAKE_MAX_RETRIES) {
|
|
6216
|
+
try {
|
|
6217
|
+
await deps.markTaskBlocked(
|
|
6218
|
+
topTask.taskId,
|
|
6219
|
+
`Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 marking blocked for manual intervention`
|
|
6220
|
+
);
|
|
6221
|
+
process.stderr.write(
|
|
6222
|
+
`[auto-wake] ${agentId} task ${topTask.taskId} exceeded ${AUTO_WAKE_MAX_RETRIES} retries \u2014 marked blocked
|
|
6223
|
+
`
|
|
6224
|
+
);
|
|
6225
|
+
} catch {
|
|
6226
|
+
}
|
|
6227
|
+
continue;
|
|
6228
|
+
}
|
|
6229
|
+
process.stderr.write(
|
|
6230
|
+
`[auto-wake] ${agentId} has ${tasks.length} pending task(s) but no session \u2014 spawning (attempt ${retries + 1} for task ${topTask.taskId})
|
|
6231
|
+
`
|
|
6232
|
+
);
|
|
6233
|
+
try {
|
|
6234
|
+
const result = deps.ensureEmployee(agentId);
|
|
6235
|
+
if (result.status === "failed") {
|
|
6236
|
+
process.stderr.write(
|
|
6237
|
+
`[auto-wake] Failed to spawn ${agentId}: ${result.error ?? "unknown"}
|
|
6238
|
+
`
|
|
6239
|
+
);
|
|
6240
|
+
continue;
|
|
6241
|
+
}
|
|
6242
|
+
_autoWakeLastSpawn.set(agentId, nowMs);
|
|
6243
|
+
_autoWakeTaskRetries.set(topTask.taskId, retries + 1);
|
|
6244
|
+
woken.push(agentId);
|
|
6245
|
+
} catch (err) {
|
|
6246
|
+
process.stderr.write(
|
|
6247
|
+
`[auto-wake] Error spawning ${agentId}: ${err instanceof Error ? err.message : String(err)}
|
|
6248
|
+
`
|
|
6249
|
+
);
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
return woken;
|
|
6253
|
+
}
|
|
6254
|
+
function createAutoWakeRealDeps(getClient2, sessionScope, projectDir) {
|
|
6255
|
+
return {
|
|
6256
|
+
listTmuxSessions: () => {
|
|
6257
|
+
const { listTmuxSessions: listTmuxSessions2 } = (init_tmux_status(), __toCommonJS(tmux_status_exports));
|
|
6258
|
+
return listTmuxSessions2();
|
|
6259
|
+
},
|
|
6260
|
+
queryTasksByAgent: async () => {
|
|
6261
|
+
const client = getClient2();
|
|
6262
|
+
const scope = sessionScopeFilter(sessionScope);
|
|
6263
|
+
const result = await client.execute({
|
|
6264
|
+
sql: `SELECT assigned_to, id, priority FROM tasks
|
|
6265
|
+
WHERE status IN ('open', 'in_progress')${scope.sql}
|
|
6266
|
+
ORDER BY
|
|
6267
|
+
CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
|
|
6268
|
+
created_at ASC`,
|
|
6269
|
+
args: [...scope.args]
|
|
6270
|
+
});
|
|
6271
|
+
return result.rows.map((r) => ({
|
|
6272
|
+
agentId: String(r.assigned_to),
|
|
6273
|
+
taskId: String(r.id),
|
|
6274
|
+
priority: String(r.priority)
|
|
6275
|
+
}));
|
|
6276
|
+
},
|
|
6277
|
+
ensureEmployee: (agentName) => {
|
|
6278
|
+
const { ensureEmployee: ensureEmployee2 } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
|
|
6279
|
+
return ensureEmployee2(agentName, sessionScope, projectDir);
|
|
6280
|
+
},
|
|
6281
|
+
markTaskBlocked: async (taskId, reason) => {
|
|
6282
|
+
const client = getClient2();
|
|
6283
|
+
await client.execute({
|
|
6284
|
+
sql: `UPDATE tasks SET status = 'blocked', result = ?, updated_at = ? WHERE id = ?`,
|
|
6285
|
+
args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
|
|
6286
|
+
});
|
|
6287
|
+
},
|
|
6288
|
+
parseAgentFromSession: (sessionName) => {
|
|
6289
|
+
const { baseAgentName: baseAgentName2 } = (init_employees(), __toCommonJS(employees_exports));
|
|
6290
|
+
if (!sessionName.includes("-")) return null;
|
|
6291
|
+
const agentPart = sessionName.split("-")[0];
|
|
6292
|
+
return baseAgentName2(agentPart);
|
|
6293
|
+
}
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
5271
6296
|
function reapOrphanedMcpProcesses(deps) {
|
|
5272
6297
|
const lines = deps.listProcesses();
|
|
5273
6298
|
const reaped = [];
|
|
@@ -5318,19 +6343,23 @@ function createOrphanReaperRealDeps() {
|
|
|
5318
6343
|
selfPid: process.pid
|
|
5319
6344
|
};
|
|
5320
6345
|
}
|
|
5321
|
-
var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
|
|
6346
|
+
var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
|
|
5322
6347
|
var init_daemon_orchestration = __esm({
|
|
5323
6348
|
"src/lib/daemon-orchestration.ts"() {
|
|
5324
6349
|
"use strict";
|
|
5325
6350
|
init_tmux_routing();
|
|
5326
6351
|
init_task_scope();
|
|
5327
6352
|
init_employees();
|
|
5328
|
-
IDLE_NUDGE_DEDUP_MS = 6e4;
|
|
5329
|
-
SESSION_TTL_HOURS = 4;
|
|
5330
|
-
SESSION_CONTEXT_THRESHOLD_PCT = 50;
|
|
5331
|
-
IDLE_KILL_INTERCOM_ACK_WINDOW_MS = 1e4;
|
|
6353
|
+
IDLE_NUDGE_DEDUP_MS = Number(process.env.EXE_NUDGE_INTERVAL_MS) || 6e4;
|
|
6354
|
+
SESSION_TTL_HOURS = Number(process.env.EXE_SESSION_TTL_HOURS) || 4;
|
|
6355
|
+
SESSION_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_CONTEXT_KILL_PCT) || 50;
|
|
6356
|
+
IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDOW_MS) || 1e4;
|
|
5332
6357
|
REVIEW_NUDGE_COOLDOWN_MS = 3e5;
|
|
5333
6358
|
NUDGE_STATE_PATH = join(homedir(), ".exe-os", "review-nudge-state.json");
|
|
6359
|
+
AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
6360
|
+
AUTO_WAKE_MAX_RETRIES = 3;
|
|
6361
|
+
_autoWakeLastSpawn = /* @__PURE__ */ new Map();
|
|
6362
|
+
_autoWakeTaskRetries = /* @__PURE__ */ new Map();
|
|
5334
6363
|
ORPHAN_SIGKILL_DELAY_MS = 5e3;
|
|
5335
6364
|
ORPHAN_PATTERNS = [
|
|
5336
6365
|
"exe-os/dist/mcp/server.js",
|
|
@@ -5351,14 +6380,14 @@ __export(keychain_exports, {
|
|
|
5351
6380
|
setMasterKey: () => setMasterKey
|
|
5352
6381
|
});
|
|
5353
6382
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
5354
|
-
import { existsSync as
|
|
5355
|
-
import
|
|
5356
|
-
import
|
|
6383
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6384
|
+
import path16 from "path";
|
|
6385
|
+
import os9 from "os";
|
|
5357
6386
|
function getKeyDir() {
|
|
5358
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
6387
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
|
|
5359
6388
|
}
|
|
5360
6389
|
function getKeyPath() {
|
|
5361
|
-
return
|
|
6390
|
+
return path16.join(getKeyDir(), "master.key");
|
|
5362
6391
|
}
|
|
5363
6392
|
async function tryKeytar() {
|
|
5364
6393
|
try {
|
|
@@ -5379,13 +6408,21 @@ async function getMasterKey() {
|
|
|
5379
6408
|
}
|
|
5380
6409
|
}
|
|
5381
6410
|
const keyPath = getKeyPath();
|
|
5382
|
-
if (!
|
|
6411
|
+
if (!existsSync14(keyPath)) {
|
|
6412
|
+
process.stderr.write(
|
|
6413
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
6414
|
+
`
|
|
6415
|
+
);
|
|
5383
6416
|
return null;
|
|
5384
6417
|
}
|
|
5385
6418
|
try {
|
|
5386
6419
|
const content = await readFile4(keyPath, "utf-8");
|
|
5387
6420
|
return Buffer.from(content.trim(), "base64");
|
|
5388
|
-
} catch {
|
|
6421
|
+
} catch (err) {
|
|
6422
|
+
process.stderr.write(
|
|
6423
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
6424
|
+
`
|
|
6425
|
+
);
|
|
5389
6426
|
return null;
|
|
5390
6427
|
}
|
|
5391
6428
|
}
|
|
@@ -5414,7 +6451,7 @@ async function deleteMasterKey() {
|
|
|
5414
6451
|
}
|
|
5415
6452
|
}
|
|
5416
6453
|
const keyPath = getKeyPath();
|
|
5417
|
-
if (
|
|
6454
|
+
if (existsSync14(keyPath)) {
|
|
5418
6455
|
await unlink(keyPath);
|
|
5419
6456
|
}
|
|
5420
6457
|
}
|
|
@@ -5469,12 +6506,12 @@ __export(shard_manager_exports, {
|
|
|
5469
6506
|
listShards: () => listShards,
|
|
5470
6507
|
shardExists: () => shardExists
|
|
5471
6508
|
});
|
|
5472
|
-
import
|
|
5473
|
-
import { existsSync as
|
|
6509
|
+
import path17 from "path";
|
|
6510
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
5474
6511
|
import { createClient as createClient2 } from "@libsql/client";
|
|
5475
6512
|
function initShardManager(encryptionKey) {
|
|
5476
6513
|
_encryptionKey = encryptionKey;
|
|
5477
|
-
if (!
|
|
6514
|
+
if (!existsSync15(SHARDS_DIR)) {
|
|
5478
6515
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
5479
6516
|
}
|
|
5480
6517
|
_shardingEnabled = true;
|
|
@@ -5495,7 +6532,7 @@ function getShardClient(projectName) {
|
|
|
5495
6532
|
}
|
|
5496
6533
|
const cached = _shards.get(safeName);
|
|
5497
6534
|
if (cached) return cached;
|
|
5498
|
-
const dbPath =
|
|
6535
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
5499
6536
|
const client = createClient2({
|
|
5500
6537
|
url: `file:${dbPath}`,
|
|
5501
6538
|
encryptionKey: _encryptionKey
|
|
@@ -5505,10 +6542,10 @@ function getShardClient(projectName) {
|
|
|
5505
6542
|
}
|
|
5506
6543
|
function shardExists(projectName) {
|
|
5507
6544
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5508
|
-
return
|
|
6545
|
+
return existsSync15(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
5509
6546
|
}
|
|
5510
6547
|
function listShards() {
|
|
5511
|
-
if (!
|
|
6548
|
+
if (!existsSync15(SHARDS_DIR)) return [];
|
|
5512
6549
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
5513
6550
|
}
|
|
5514
6551
|
async function ensureShardSchema(client) {
|
|
@@ -5694,7 +6731,7 @@ var init_shard_manager = __esm({
|
|
|
5694
6731
|
"src/lib/shard-manager.ts"() {
|
|
5695
6732
|
"use strict";
|
|
5696
6733
|
init_config();
|
|
5697
|
-
SHARDS_DIR =
|
|
6734
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
5698
6735
|
_shards = /* @__PURE__ */ new Map();
|
|
5699
6736
|
_encryptionKey = null;
|
|
5700
6737
|
_shardingEnabled = false;
|
|
@@ -5819,7 +6856,7 @@ __export(global_procedures_exports, {
|
|
|
5819
6856
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
5820
6857
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
5821
6858
|
});
|
|
5822
|
-
import { randomUUID as
|
|
6859
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5823
6860
|
async function loadGlobalProcedures() {
|
|
5824
6861
|
const client = getClient();
|
|
5825
6862
|
const result = await client.execute({
|
|
@@ -5848,7 +6885,7 @@ ${sections.join("\n\n")}
|
|
|
5848
6885
|
`;
|
|
5849
6886
|
}
|
|
5850
6887
|
async function storeGlobalProcedure(input) {
|
|
5851
|
-
const id =
|
|
6888
|
+
const id = randomUUID3();
|
|
5852
6889
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5853
6890
|
const client = getClient();
|
|
5854
6891
|
await client.execute({
|
|
@@ -5899,6 +6936,7 @@ __export(store_exports, {
|
|
|
5899
6936
|
vectorToBlob: () => vectorToBlob,
|
|
5900
6937
|
writeMemory: () => writeMemory
|
|
5901
6938
|
});
|
|
6939
|
+
import { createHash } from "crypto";
|
|
5902
6940
|
function isBusyError2(err) {
|
|
5903
6941
|
if (err instanceof Error) {
|
|
5904
6942
|
const msg = err.message.toLowerCase();
|
|
@@ -5972,12 +7010,52 @@ function classifyTier(record) {
|
|
|
5972
7010
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
5973
7011
|
return 3;
|
|
5974
7012
|
}
|
|
7013
|
+
function inferFilePaths(record) {
|
|
7014
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
7015
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
7016
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
7017
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
7018
|
+
}
|
|
7019
|
+
function inferCommitHash(record) {
|
|
7020
|
+
if (record.tool_name !== "Bash") return null;
|
|
7021
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
7022
|
+
return match ? match[1] : null;
|
|
7023
|
+
}
|
|
7024
|
+
function inferLanguageType(record) {
|
|
7025
|
+
const text = record.raw_text;
|
|
7026
|
+
if (!text || text.length < 10) return null;
|
|
7027
|
+
const trimmed = text.trimStart();
|
|
7028
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
7029
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
7030
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
7031
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
7032
|
+
return "mixed";
|
|
7033
|
+
}
|
|
7034
|
+
function inferDomain(record) {
|
|
7035
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
7036
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
7037
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
7038
|
+
return null;
|
|
7039
|
+
}
|
|
5975
7040
|
async function writeMemory(record) {
|
|
5976
7041
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
5977
7042
|
throw new Error(
|
|
5978
7043
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
5979
7044
|
);
|
|
5980
7045
|
}
|
|
7046
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
7047
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
try {
|
|
7051
|
+
const client = getClient();
|
|
7052
|
+
const existing = await client.execute({
|
|
7053
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
7054
|
+
args: [contentHash, record.agent_id]
|
|
7055
|
+
});
|
|
7056
|
+
if (existing.rows.length > 0) return;
|
|
7057
|
+
} catch {
|
|
7058
|
+
}
|
|
5981
7059
|
const dbRow = {
|
|
5982
7060
|
id: record.id,
|
|
5983
7061
|
agent_id: record.agent_id,
|
|
@@ -6007,7 +7085,23 @@ async function writeMemory(record) {
|
|
|
6007
7085
|
supersedes_id: record.supersedes_id ?? null,
|
|
6008
7086
|
draft: record.draft ? 1 : 0,
|
|
6009
7087
|
memory_type: record.memory_type ?? "raw",
|
|
6010
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
7088
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
7089
|
+
content_hash: contentHash,
|
|
7090
|
+
intent: record.intent ?? null,
|
|
7091
|
+
outcome: record.outcome ?? null,
|
|
7092
|
+
domain: record.domain ?? inferDomain(record),
|
|
7093
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
7094
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
7095
|
+
chain_position: record.chain_position ?? null,
|
|
7096
|
+
review_status: record.review_status ?? null,
|
|
7097
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
7098
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
7099
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
7100
|
+
duration_ms: record.duration_ms ?? null,
|
|
7101
|
+
token_cost: record.token_cost ?? null,
|
|
7102
|
+
audience: record.audience ?? null,
|
|
7103
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
7104
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
6011
7105
|
};
|
|
6012
7106
|
_pendingRecords.push(dbRow);
|
|
6013
7107
|
orgBus.emit({
|
|
@@ -6065,80 +7159,85 @@ async function flushBatch() {
|
|
|
6065
7159
|
const draft = row.draft ? 1 : 0;
|
|
6066
7160
|
const memoryType = row.memory_type ?? "raw";
|
|
6067
7161
|
const trajectory = row.trajectory ?? null;
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
7162
|
+
const contentHash = row.content_hash ?? null;
|
|
7163
|
+
const intent = row.intent ?? null;
|
|
7164
|
+
const outcome = row.outcome ?? null;
|
|
7165
|
+
const domain = row.domain ?? null;
|
|
7166
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
7167
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
7168
|
+
const chainPosition = row.chain_position ?? null;
|
|
7169
|
+
const reviewStatus = row.review_status ?? null;
|
|
7170
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
7171
|
+
const filePaths = row.file_paths ?? null;
|
|
7172
|
+
const commitHash = row.commit_hash ?? null;
|
|
7173
|
+
const durationMs = row.duration_ms ?? null;
|
|
7174
|
+
const tokenCost = row.token_cost ?? null;
|
|
7175
|
+
const audience = row.audience ?? null;
|
|
7176
|
+
const languageType = row.language_type ?? null;
|
|
7177
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
7178
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
6078
7179
|
tool_name, project_name,
|
|
6079
7180
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
6080
7181
|
confidence, last_accessed,
|
|
6081
7182
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
6082
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
trajectory
|
|
6141
|
-
]
|
|
7183
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
7184
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
7185
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
7186
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
7187
|
+
const metaArgs = [
|
|
7188
|
+
intent,
|
|
7189
|
+
outcome,
|
|
7190
|
+
domain,
|
|
7191
|
+
referencedEntities,
|
|
7192
|
+
retrievalCount,
|
|
7193
|
+
chainPosition,
|
|
7194
|
+
reviewStatus,
|
|
7195
|
+
contextWindowPct,
|
|
7196
|
+
filePaths,
|
|
7197
|
+
commitHash,
|
|
7198
|
+
durationMs,
|
|
7199
|
+
tokenCost,
|
|
7200
|
+
audience,
|
|
7201
|
+
languageType,
|
|
7202
|
+
parentMemoryId
|
|
7203
|
+
];
|
|
7204
|
+
const baseArgs = [
|
|
7205
|
+
row.id,
|
|
7206
|
+
row.agent_id,
|
|
7207
|
+
row.agent_role,
|
|
7208
|
+
row.session_id,
|
|
7209
|
+
row.timestamp,
|
|
7210
|
+
row.tool_name,
|
|
7211
|
+
row.project_name,
|
|
7212
|
+
row.has_error,
|
|
7213
|
+
row.raw_text
|
|
7214
|
+
];
|
|
7215
|
+
const sharedArgs = [
|
|
7216
|
+
row.version,
|
|
7217
|
+
taskId,
|
|
7218
|
+
importance,
|
|
7219
|
+
status,
|
|
7220
|
+
confidence,
|
|
7221
|
+
lastAccessed,
|
|
7222
|
+
workspaceId,
|
|
7223
|
+
documentId,
|
|
7224
|
+
userId,
|
|
7225
|
+
charOffset,
|
|
7226
|
+
pageNumber,
|
|
7227
|
+
sourcePath,
|
|
7228
|
+
sourceType,
|
|
7229
|
+
tier,
|
|
7230
|
+
supersedesId,
|
|
7231
|
+
draft,
|
|
7232
|
+
memoryType,
|
|
7233
|
+
trajectory,
|
|
7234
|
+
contentHash
|
|
7235
|
+
];
|
|
7236
|
+
return {
|
|
7237
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
7238
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
7239
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7240
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
6142
7241
|
};
|
|
6143
7242
|
};
|
|
6144
7243
|
const globalClient = getClient();
|
|
@@ -6505,20 +7604,11 @@ function createRealDeps(getClient2) {
|
|
|
6505
7604
|
},
|
|
6506
7605
|
countPendingReviews: async () => {
|
|
6507
7606
|
const client = getClient2();
|
|
6508
|
-
const
|
|
6509
|
-
const rpScope = sessionScopeFilter(void 0, "r");
|
|
7607
|
+
const rpScope = sessionScopeFilter();
|
|
6510
7608
|
const result = await client.execute({
|
|
6511
|
-
sql: `SELECT COUNT(*) as count FROM tasks
|
|
6512
|
-
WHERE
|
|
6513
|
-
|
|
6514
|
-
AND r.title LIKE 'Review:%'${rpScope.sql}
|
|
6515
|
-
AND NOT EXISTS (
|
|
6516
|
-
SELECT 1 FROM tasks t
|
|
6517
|
-
WHERE t.id = r.parent_task_id
|
|
6518
|
-
AND t.status = 'done'
|
|
6519
|
-
AND t.updated_at > r.created_at
|
|
6520
|
-
)`,
|
|
6521
|
-
args: [coordinatorName, ...rpScope.args]
|
|
7609
|
+
sql: `SELECT COUNT(*) as count FROM tasks
|
|
7610
|
+
WHERE status = 'needs_review'${rpScope.sql}`,
|
|
7611
|
+
args: [...rpScope.args]
|
|
6522
7612
|
});
|
|
6523
7613
|
return Number(result.rows[0]?.count ?? 0);
|
|
6524
7614
|
},
|
|
@@ -6611,7 +7701,7 @@ __export(consolidation_exports, {
|
|
|
6611
7701
|
selectUnconsolidated: () => selectUnconsolidated,
|
|
6612
7702
|
storeConsolidation: () => storeConsolidation
|
|
6613
7703
|
});
|
|
6614
|
-
import { randomUUID as
|
|
7704
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6615
7705
|
async function selectUnconsolidated(client, limit = 200) {
|
|
6616
7706
|
const result = await client.execute({
|
|
6617
7707
|
sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
|
|
@@ -6707,7 +7797,7 @@ async function consolidateCluster(cluster, model) {
|
|
|
6707
7797
|
return textBlock?.text ?? "";
|
|
6708
7798
|
}
|
|
6709
7799
|
async function storeConsolidation(client, cluster, synthesisText, embedFn) {
|
|
6710
|
-
const consolidatedId =
|
|
7800
|
+
const consolidatedId = randomUUID4();
|
|
6711
7801
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6712
7802
|
const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
|
|
6713
7803
|
|
|
@@ -6732,7 +7822,7 @@ ${synthesisText}`;
|
|
|
6732
7822
|
const linkStmts = sourceIds.map((sourceId) => ({
|
|
6733
7823
|
sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
|
|
6734
7824
|
VALUES (?, ?, ?, ?)`,
|
|
6735
|
-
args: [
|
|
7825
|
+
args: [randomUUID4(), consolidatedId, sourceId, now]
|
|
6736
7826
|
}));
|
|
6737
7827
|
const placeholders = sourceIds.map(() => "?").join(",");
|
|
6738
7828
|
const markStmt = {
|
|
@@ -6847,7 +7937,7 @@ async function runConsolidation(client, options) {
|
|
|
6847
7937
|
if (clustersProcessed >= options.maxCalls) break;
|
|
6848
7938
|
if (cluster.memories.length < 3) continue;
|
|
6849
7939
|
try {
|
|
6850
|
-
const isCoordinator =
|
|
7940
|
+
const isCoordinator = isCoordinatorName(cluster.agentId);
|
|
6851
7941
|
if (isCoordinator) {
|
|
6852
7942
|
const synthesis = await consolidateCluster(cluster, options.model);
|
|
6853
7943
|
if (!synthesis.trim()) continue;
|
|
@@ -6867,450 +7957,105 @@ async function runConsolidation(client, options) {
|
|
|
6867
7957
|
if (sourceIds.length > 0) {
|
|
6868
7958
|
const placeholders = sourceIds.map(() => "?").join(",");
|
|
6869
7959
|
await client.execute({
|
|
6870
|
-
sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
|
|
6871
|
-
args: sourceIds
|
|
6872
|
-
});
|
|
6873
|
-
}
|
|
6874
|
-
} else {
|
|
6875
|
-
const dedupCount = await dedupCluster(client, cluster, options.embedFn);
|
|
6876
|
-
memoriesConsolidated += dedupCount;
|
|
6877
|
-
if (dedupCount === 0) continue;
|
|
6878
|
-
}
|
|
6879
|
-
clustersProcessed++;
|
|
6880
|
-
memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
|
|
6881
|
-
} catch (err) {
|
|
6882
|
-
process.stderr.write(
|
|
6883
|
-
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
6884
|
-
`
|
|
6885
|
-
);
|
|
6886
|
-
}
|
|
6887
|
-
}
|
|
6888
|
-
return { clustersProcessed, memoriesConsolidated };
|
|
6889
|
-
}
|
|
6890
|
-
async function dedupCluster(client, cluster, embedFn) {
|
|
6891
|
-
if (!embedFn || cluster.memories.length < 2) return 0;
|
|
6892
|
-
const vectors = [];
|
|
6893
|
-
for (const mem of cluster.memories) {
|
|
6894
|
-
try {
|
|
6895
|
-
const v = await embedFn(mem.raw_text.slice(0, 500));
|
|
6896
|
-
vectors.push({ id: mem.id, vector: v });
|
|
6897
|
-
} catch {
|
|
6898
|
-
}
|
|
6899
|
-
}
|
|
6900
|
-
if (vectors.length < 2) return 0;
|
|
6901
|
-
const toArchive = /* @__PURE__ */ new Set();
|
|
6902
|
-
for (let i = 0; i < vectors.length; i++) {
|
|
6903
|
-
if (toArchive.has(vectors[i].id)) continue;
|
|
6904
|
-
for (let j = i + 1; j < vectors.length; j++) {
|
|
6905
|
-
if (toArchive.has(vectors[j].id)) continue;
|
|
6906
|
-
const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
|
|
6907
|
-
if (sim > 0.95) {
|
|
6908
|
-
toArchive.add(vectors[j].id);
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
6911
|
-
}
|
|
6912
|
-
if (toArchive.size === 0) return 0;
|
|
6913
|
-
const ids = [...toArchive];
|
|
6914
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
6915
|
-
await client.execute({
|
|
6916
|
-
sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
|
|
6917
|
-
args: ids
|
|
6918
|
-
});
|
|
6919
|
-
const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
|
|
6920
|
-
if (survivors.length > 0) {
|
|
6921
|
-
const survivorPlaceholders = survivors.map(() => "?").join(",");
|
|
6922
|
-
await client.execute({
|
|
6923
|
-
sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
|
|
6924
|
-
args: survivors
|
|
6925
|
-
});
|
|
6926
|
-
}
|
|
6927
|
-
return ids.length;
|
|
6928
|
-
}
|
|
6929
|
-
function cosineSimilarity(a, b) {
|
|
6930
|
-
let dot = 0, normA = 0, normB = 0;
|
|
6931
|
-
for (let i = 0; i < a.length; i++) {
|
|
6932
|
-
dot += a[i] * b[i];
|
|
6933
|
-
normA += a[i] * a[i];
|
|
6934
|
-
normB += b[i] * b[i];
|
|
6935
|
-
}
|
|
6936
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
6937
|
-
return denom === 0 ? 0 : dot / denom;
|
|
6938
|
-
}
|
|
6939
|
-
async function isUserIdle(client, idleMinutes = 30) {
|
|
6940
|
-
const result = await client.execute({
|
|
6941
|
-
sql: `SELECT MAX(timestamp) as last_activity
|
|
6942
|
-
FROM memories
|
|
6943
|
-
WHERE tool_name != 'consolidation'
|
|
6944
|
-
AND timestamp >= datetime('now', '-1 day')`,
|
|
6945
|
-
args: []
|
|
6946
|
-
});
|
|
6947
|
-
const lastActivity = result.rows[0]?.last_activity;
|
|
6948
|
-
if (!lastActivity) return true;
|
|
6949
|
-
const lastMs = new Date(lastActivity).getTime();
|
|
6950
|
-
const now = Date.now();
|
|
6951
|
-
return now - lastMs >= idleMinutes * 60 * 1e3;
|
|
6952
|
-
}
|
|
6953
|
-
async function countUnconsolidated(client) {
|
|
6954
|
-
const result = await client.execute({
|
|
6955
|
-
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
6956
|
-
WHERE consolidated = 0
|
|
6957
|
-
AND timestamp >= datetime('now', '-7 days')`,
|
|
6958
|
-
args: []
|
|
6959
|
-
});
|
|
6960
|
-
return Number(result.rows[0]?.cnt ?? 0);
|
|
6961
|
-
}
|
|
6962
|
-
var WIKI_FETCH_TIMEOUT_MS;
|
|
6963
|
-
var init_consolidation = __esm({
|
|
6964
|
-
"src/lib/consolidation.ts"() {
|
|
6965
|
-
"use strict";
|
|
6966
|
-
init_store();
|
|
6967
|
-
init_employees();
|
|
6968
|
-
WIKI_FETCH_TIMEOUT_MS = 1e4;
|
|
6969
|
-
}
|
|
6970
|
-
});
|
|
6971
|
-
|
|
6972
|
-
// src/lib/exe-daemon-client.ts
|
|
6973
|
-
import net from "net";
|
|
6974
|
-
import { spawn } from "child_process";
|
|
6975
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
6976
|
-
import { existsSync as existsSync15, unlinkSync as unlinkSync6, readFileSync as readFileSync12, openSync, closeSync, statSync } from "fs";
|
|
6977
|
-
import path17 from "path";
|
|
6978
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6979
|
-
function handleData(chunk) {
|
|
6980
|
-
_buffer += chunk.toString();
|
|
6981
|
-
if (_buffer.length > MAX_BUFFER) {
|
|
6982
|
-
_buffer = "";
|
|
6983
|
-
return;
|
|
6984
|
-
}
|
|
6985
|
-
let newlineIdx;
|
|
6986
|
-
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
6987
|
-
const line = _buffer.slice(0, newlineIdx).trim();
|
|
6988
|
-
_buffer = _buffer.slice(newlineIdx + 1);
|
|
6989
|
-
if (!line) continue;
|
|
6990
|
-
try {
|
|
6991
|
-
const response = JSON.parse(line);
|
|
6992
|
-
const entry = _pending.get(response.id);
|
|
6993
|
-
if (entry) {
|
|
6994
|
-
clearTimeout(entry.timer);
|
|
6995
|
-
_pending.delete(response.id);
|
|
6996
|
-
entry.resolve(response);
|
|
6997
|
-
}
|
|
6998
|
-
} catch {
|
|
6999
|
-
}
|
|
7000
|
-
}
|
|
7001
|
-
}
|
|
7002
|
-
function cleanupStaleFiles() {
|
|
7003
|
-
if (existsSync15(PID_PATH)) {
|
|
7004
|
-
try {
|
|
7005
|
-
const pid = parseInt(readFileSync12(PID_PATH, "utf8").trim(), 10);
|
|
7006
|
-
if (pid > 0) {
|
|
7007
|
-
try {
|
|
7008
|
-
process.kill(pid, 0);
|
|
7009
|
-
return;
|
|
7010
|
-
} catch {
|
|
7011
|
-
}
|
|
7012
|
-
}
|
|
7013
|
-
} catch {
|
|
7014
|
-
}
|
|
7015
|
-
try {
|
|
7016
|
-
unlinkSync6(PID_PATH);
|
|
7017
|
-
} catch {
|
|
7018
|
-
}
|
|
7019
|
-
try {
|
|
7020
|
-
unlinkSync6(SOCKET_PATH);
|
|
7021
|
-
} catch {
|
|
7022
|
-
}
|
|
7023
|
-
}
|
|
7024
|
-
}
|
|
7025
|
-
function findPackageRoot() {
|
|
7026
|
-
let dir = path17.dirname(fileURLToPath2(import.meta.url));
|
|
7027
|
-
const { root } = path17.parse(dir);
|
|
7028
|
-
while (dir !== root) {
|
|
7029
|
-
if (existsSync15(path17.join(dir, "package.json"))) return dir;
|
|
7030
|
-
dir = path17.dirname(dir);
|
|
7031
|
-
}
|
|
7032
|
-
return null;
|
|
7033
|
-
}
|
|
7034
|
-
function spawnDaemon() {
|
|
7035
|
-
const pkgRoot = findPackageRoot();
|
|
7036
|
-
if (!pkgRoot) {
|
|
7037
|
-
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
7038
|
-
return;
|
|
7039
|
-
}
|
|
7040
|
-
const daemonPath = path17.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
7041
|
-
if (!existsSync15(daemonPath)) {
|
|
7042
|
-
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
7043
|
-
`);
|
|
7044
|
-
return;
|
|
7045
|
-
}
|
|
7046
|
-
const resolvedPath = daemonPath;
|
|
7047
|
-
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
7048
|
-
`);
|
|
7049
|
-
const logPath = path17.join(path17.dirname(SOCKET_PATH), "exed.log");
|
|
7050
|
-
let stderrFd = "ignore";
|
|
7051
|
-
try {
|
|
7052
|
-
stderrFd = openSync(logPath, "a");
|
|
7053
|
-
} catch {
|
|
7054
|
-
}
|
|
7055
|
-
const child = spawn(process.execPath, [resolvedPath], {
|
|
7056
|
-
detached: true,
|
|
7057
|
-
stdio: ["ignore", "ignore", stderrFd],
|
|
7058
|
-
env: {
|
|
7059
|
-
...process.env,
|
|
7060
|
-
TMUX: void 0,
|
|
7061
|
-
// Daemon is global — must not inherit session scope
|
|
7062
|
-
TMUX_PANE: void 0,
|
|
7063
|
-
// Prevents resolveExeSession() from scoping to one session
|
|
7064
|
-
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
7065
|
-
EXE_DAEMON_PID: PID_PATH
|
|
7066
|
-
}
|
|
7067
|
-
});
|
|
7068
|
-
child.unref();
|
|
7069
|
-
if (typeof stderrFd === "number") {
|
|
7070
|
-
try {
|
|
7071
|
-
closeSync(stderrFd);
|
|
7072
|
-
} catch {
|
|
7073
|
-
}
|
|
7074
|
-
}
|
|
7075
|
-
}
|
|
7076
|
-
function acquireSpawnLock2() {
|
|
7077
|
-
try {
|
|
7078
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
7079
|
-
closeSync(fd);
|
|
7080
|
-
return true;
|
|
7081
|
-
} catch {
|
|
7082
|
-
try {
|
|
7083
|
-
const stat = statSync(SPAWN_LOCK_PATH);
|
|
7084
|
-
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
7085
|
-
try {
|
|
7086
|
-
unlinkSync6(SPAWN_LOCK_PATH);
|
|
7087
|
-
} catch {
|
|
7088
|
-
}
|
|
7089
|
-
try {
|
|
7090
|
-
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
7091
|
-
closeSync(fd);
|
|
7092
|
-
return true;
|
|
7093
|
-
} catch {
|
|
7094
|
-
}
|
|
7095
|
-
}
|
|
7096
|
-
} catch {
|
|
7097
|
-
}
|
|
7098
|
-
return false;
|
|
7099
|
-
}
|
|
7100
|
-
}
|
|
7101
|
-
function releaseSpawnLock2() {
|
|
7102
|
-
try {
|
|
7103
|
-
unlinkSync6(SPAWN_LOCK_PATH);
|
|
7104
|
-
} catch {
|
|
7105
|
-
}
|
|
7106
|
-
}
|
|
7107
|
-
function connectToSocket() {
|
|
7108
|
-
return new Promise((resolve) => {
|
|
7109
|
-
if (_socket && _connected) {
|
|
7110
|
-
resolve(true);
|
|
7111
|
-
return;
|
|
7112
|
-
}
|
|
7113
|
-
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
7114
|
-
const connectTimeout = setTimeout(() => {
|
|
7115
|
-
socket.destroy();
|
|
7116
|
-
resolve(false);
|
|
7117
|
-
}, 2e3);
|
|
7118
|
-
socket.on("connect", () => {
|
|
7119
|
-
clearTimeout(connectTimeout);
|
|
7120
|
-
_socket = socket;
|
|
7121
|
-
_connected = true;
|
|
7122
|
-
_buffer = "";
|
|
7123
|
-
socket.on("data", handleData);
|
|
7124
|
-
socket.on("close", () => {
|
|
7125
|
-
_connected = false;
|
|
7126
|
-
_socket = null;
|
|
7127
|
-
for (const [id, entry] of _pending) {
|
|
7128
|
-
clearTimeout(entry.timer);
|
|
7129
|
-
_pending.delete(id);
|
|
7130
|
-
entry.resolve({ error: "Connection closed" });
|
|
7131
|
-
}
|
|
7132
|
-
});
|
|
7133
|
-
socket.on("error", () => {
|
|
7134
|
-
_connected = false;
|
|
7135
|
-
_socket = null;
|
|
7136
|
-
});
|
|
7137
|
-
resolve(true);
|
|
7138
|
-
});
|
|
7139
|
-
socket.on("error", () => {
|
|
7140
|
-
clearTimeout(connectTimeout);
|
|
7141
|
-
resolve(false);
|
|
7142
|
-
});
|
|
7143
|
-
});
|
|
7144
|
-
}
|
|
7145
|
-
async function connectEmbedDaemon() {
|
|
7146
|
-
if (_socket && _connected) return true;
|
|
7147
|
-
if (await connectToSocket()) return true;
|
|
7148
|
-
if (acquireSpawnLock2()) {
|
|
7149
|
-
try {
|
|
7150
|
-
cleanupStaleFiles();
|
|
7151
|
-
spawnDaemon();
|
|
7152
|
-
} finally {
|
|
7153
|
-
releaseSpawnLock2();
|
|
7154
|
-
}
|
|
7155
|
-
}
|
|
7156
|
-
const start = Date.now();
|
|
7157
|
-
let delay2 = 100;
|
|
7158
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
7159
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
7160
|
-
if (await connectToSocket()) return true;
|
|
7161
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
7162
|
-
}
|
|
7163
|
-
return false;
|
|
7164
|
-
}
|
|
7165
|
-
function sendRequest(texts, priority) {
|
|
7166
|
-
return new Promise((resolve) => {
|
|
7167
|
-
if (!_socket || !_connected) {
|
|
7168
|
-
resolve({ error: "Not connected" });
|
|
7169
|
-
return;
|
|
7170
|
-
}
|
|
7171
|
-
const id = randomUUID4();
|
|
7172
|
-
const timer = setTimeout(() => {
|
|
7173
|
-
_pending.delete(id);
|
|
7174
|
-
resolve({ error: "Request timeout" });
|
|
7175
|
-
}, REQUEST_TIMEOUT_MS);
|
|
7176
|
-
_pending.set(id, { resolve, timer });
|
|
7177
|
-
try {
|
|
7178
|
-
_socket.write(JSON.stringify({ id, texts, priority }) + "\n");
|
|
7179
|
-
} catch {
|
|
7180
|
-
clearTimeout(timer);
|
|
7181
|
-
_pending.delete(id);
|
|
7182
|
-
resolve({ error: "Write failed" });
|
|
7183
|
-
}
|
|
7184
|
-
});
|
|
7185
|
-
}
|
|
7186
|
-
async function pingDaemon() {
|
|
7187
|
-
if (!_socket || !_connected) return null;
|
|
7188
|
-
return new Promise((resolve) => {
|
|
7189
|
-
const id = randomUUID4();
|
|
7190
|
-
const timer = setTimeout(() => {
|
|
7191
|
-
_pending.delete(id);
|
|
7192
|
-
resolve(null);
|
|
7193
|
-
}, 5e3);
|
|
7194
|
-
_pending.set(id, {
|
|
7195
|
-
resolve: (data) => {
|
|
7196
|
-
if (data.health) {
|
|
7197
|
-
resolve(data.health);
|
|
7198
|
-
} else {
|
|
7199
|
-
resolve(null);
|
|
7200
|
-
}
|
|
7201
|
-
},
|
|
7202
|
-
timer
|
|
7203
|
-
});
|
|
7204
|
-
try {
|
|
7205
|
-
_socket.write(JSON.stringify({ id, type: "health" }) + "\n");
|
|
7206
|
-
} catch {
|
|
7207
|
-
clearTimeout(timer);
|
|
7208
|
-
_pending.delete(id);
|
|
7209
|
-
resolve(null);
|
|
7960
|
+
sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
|
|
7961
|
+
args: sourceIds
|
|
7962
|
+
});
|
|
7963
|
+
}
|
|
7964
|
+
} else {
|
|
7965
|
+
const dedupCount = await dedupCluster(client, cluster, options.embedFn);
|
|
7966
|
+
memoriesConsolidated += dedupCount;
|
|
7967
|
+
if (dedupCount === 0) continue;
|
|
7968
|
+
}
|
|
7969
|
+
clustersProcessed++;
|
|
7970
|
+
memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
|
|
7971
|
+
} catch (err) {
|
|
7972
|
+
process.stderr.write(
|
|
7973
|
+
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
7974
|
+
`
|
|
7975
|
+
);
|
|
7210
7976
|
}
|
|
7211
|
-
}
|
|
7977
|
+
}
|
|
7978
|
+
return { clustersProcessed, memoriesConsolidated };
|
|
7212
7979
|
}
|
|
7213
|
-
function
|
|
7214
|
-
|
|
7215
|
-
|
|
7980
|
+
async function dedupCluster(client, cluster, embedFn) {
|
|
7981
|
+
if (!embedFn || cluster.memories.length < 2) return 0;
|
|
7982
|
+
const vectors = [];
|
|
7983
|
+
for (const mem of cluster.memories) {
|
|
7216
7984
|
try {
|
|
7217
|
-
const
|
|
7218
|
-
|
|
7219
|
-
try {
|
|
7220
|
-
process.kill(pid, "SIGKILL");
|
|
7221
|
-
} catch {
|
|
7222
|
-
}
|
|
7223
|
-
}
|
|
7985
|
+
const v = await embedFn(mem.raw_text.slice(0, 500));
|
|
7986
|
+
vectors.push({ id: mem.id, vector: v });
|
|
7224
7987
|
} catch {
|
|
7225
7988
|
}
|
|
7226
7989
|
}
|
|
7227
|
-
if (
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
}
|
|
7237
|
-
try {
|
|
7238
|
-
unlinkSync6(SOCKET_PATH);
|
|
7239
|
-
} catch {
|
|
7240
|
-
}
|
|
7241
|
-
spawnDaemon();
|
|
7242
|
-
}
|
|
7243
|
-
async function embedViaClient(text, priority = "high") {
|
|
7244
|
-
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
7245
|
-
_requestCount++;
|
|
7246
|
-
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
7247
|
-
const health = await pingDaemon();
|
|
7248
|
-
if (!health) {
|
|
7249
|
-
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
7250
|
-
`);
|
|
7251
|
-
killAndRespawnDaemon();
|
|
7252
|
-
const start = Date.now();
|
|
7253
|
-
let delay2 = 200;
|
|
7254
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
7255
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
7256
|
-
if (await connectToSocket()) break;
|
|
7257
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
7990
|
+
if (vectors.length < 2) return 0;
|
|
7991
|
+
const toArchive = /* @__PURE__ */ new Set();
|
|
7992
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
7993
|
+
if (toArchive.has(vectors[i].id)) continue;
|
|
7994
|
+
for (let j = i + 1; j < vectors.length; j++) {
|
|
7995
|
+
if (toArchive.has(vectors[j].id)) continue;
|
|
7996
|
+
const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
|
|
7997
|
+
if (sim > 0.95) {
|
|
7998
|
+
toArchive.add(vectors[j].id);
|
|
7258
7999
|
}
|
|
7259
|
-
if (!_connected) return null;
|
|
7260
8000
|
}
|
|
7261
8001
|
}
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
`)
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
const retry = await sendRequest([text], priority);
|
|
7277
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
7278
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
7279
|
-
`);
|
|
8002
|
+
if (toArchive.size === 0) return 0;
|
|
8003
|
+
const ids = [...toArchive];
|
|
8004
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
8005
|
+
await client.execute({
|
|
8006
|
+
sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
|
|
8007
|
+
args: ids
|
|
8008
|
+
});
|
|
8009
|
+
const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
|
|
8010
|
+
if (survivors.length > 0) {
|
|
8011
|
+
const survivorPlaceholders = survivors.map(() => "?").join(",");
|
|
8012
|
+
await client.execute({
|
|
8013
|
+
sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
|
|
8014
|
+
args: survivors
|
|
8015
|
+
});
|
|
7280
8016
|
}
|
|
7281
|
-
return
|
|
8017
|
+
return ids.length;
|
|
7282
8018
|
}
|
|
7283
|
-
function
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
_buffer = "";
|
|
7290
|
-
for (const [id, entry] of _pending) {
|
|
7291
|
-
clearTimeout(entry.timer);
|
|
7292
|
-
_pending.delete(id);
|
|
7293
|
-
entry.resolve({ error: "Client disconnected" });
|
|
8019
|
+
function cosineSimilarity(a, b) {
|
|
8020
|
+
let dot = 0, normA = 0, normB = 0;
|
|
8021
|
+
for (let i = 0; i < a.length; i++) {
|
|
8022
|
+
dot += a[i] * b[i];
|
|
8023
|
+
normA += a[i] * a[i];
|
|
8024
|
+
normB += b[i] * b[i];
|
|
7294
8025
|
}
|
|
8026
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
8027
|
+
return denom === 0 ? 0 : dot / denom;
|
|
7295
8028
|
}
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
8029
|
+
async function isUserIdle(client, idleMinutes = 30) {
|
|
8030
|
+
const result = await client.execute({
|
|
8031
|
+
sql: `SELECT MAX(timestamp) as last_activity
|
|
8032
|
+
FROM memories
|
|
8033
|
+
WHERE tool_name != 'consolidation'
|
|
8034
|
+
AND timestamp >= datetime('now', '-1 day')`,
|
|
8035
|
+
args: []
|
|
8036
|
+
});
|
|
8037
|
+
const lastActivity = result.rows[0]?.last_activity;
|
|
8038
|
+
if (!lastActivity) return true;
|
|
8039
|
+
const lastMs = new Date(lastActivity).getTime();
|
|
8040
|
+
const now = Date.now();
|
|
8041
|
+
return now - lastMs >= idleMinutes * 60 * 1e3;
|
|
8042
|
+
}
|
|
8043
|
+
async function countUnconsolidated(client) {
|
|
8044
|
+
const result = await client.execute({
|
|
8045
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
8046
|
+
WHERE consolidated = 0
|
|
8047
|
+
AND timestamp >= datetime('now', '-7 days')`,
|
|
8048
|
+
args: []
|
|
8049
|
+
});
|
|
8050
|
+
return Number(result.rows[0]?.cnt ?? 0);
|
|
8051
|
+
}
|
|
8052
|
+
var WIKI_FETCH_TIMEOUT_MS;
|
|
8053
|
+
var init_consolidation = __esm({
|
|
8054
|
+
"src/lib/consolidation.ts"() {
|
|
7299
8055
|
"use strict";
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
SPAWN_LOCK_PATH = path17.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
7304
|
-
SPAWN_LOCK_STALE_MS = 3e4;
|
|
7305
|
-
CONNECT_TIMEOUT_MS = 15e3;
|
|
7306
|
-
REQUEST_TIMEOUT_MS = 3e4;
|
|
7307
|
-
_socket = null;
|
|
7308
|
-
_connected = false;
|
|
7309
|
-
_buffer = "";
|
|
7310
|
-
_requestCount = 0;
|
|
7311
|
-
HEALTH_CHECK_INTERVAL = 100;
|
|
7312
|
-
_pending = /* @__PURE__ */ new Map();
|
|
7313
|
-
MAX_BUFFER = 1e7;
|
|
8056
|
+
init_store();
|
|
8057
|
+
init_employees();
|
|
8058
|
+
WIKI_FETCH_TIMEOUT_MS = 1e4;
|
|
7314
8059
|
}
|
|
7315
8060
|
});
|
|
7316
8061
|
|
|
@@ -7352,8 +8097,8 @@ async function embedDirect(text) {
|
|
|
7352
8097
|
const llamaCpp = await import("node-llama-cpp");
|
|
7353
8098
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
7354
8099
|
const { existsSync: existsSync18 } = await import("fs");
|
|
7355
|
-
const
|
|
7356
|
-
const modelPath =
|
|
8100
|
+
const path22 = await import("path");
|
|
8101
|
+
const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
7357
8102
|
if (!existsSync18(modelPath)) {
|
|
7358
8103
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
7359
8104
|
}
|
|
@@ -8076,8 +8821,8 @@ __export(wiki_sync_exports, {
|
|
|
8076
8821
|
listWorkspaces: () => listWorkspaces,
|
|
8077
8822
|
syncMemories: () => syncMemories
|
|
8078
8823
|
});
|
|
8079
|
-
async function wikiRequest(config,
|
|
8080
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
8824
|
+
async function wikiRequest(config, path22, method = "GET", body) {
|
|
8825
|
+
const url = `${config.wikiUrl}/api/v1${path22}`;
|
|
8081
8826
|
const headers = {
|
|
8082
8827
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
8083
8828
|
"Content-Type": "application/json"
|
|
@@ -8089,7 +8834,7 @@ async function wikiRequest(config, path21, method = "GET", body) {
|
|
|
8089
8834
|
signal: AbortSignal.timeout(3e4)
|
|
8090
8835
|
});
|
|
8091
8836
|
if (!response.ok) {
|
|
8092
|
-
throw new Error(`Wiki API ${method} ${
|
|
8837
|
+
throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
|
|
8093
8838
|
}
|
|
8094
8839
|
return response.json();
|
|
8095
8840
|
}
|
|
@@ -8193,6 +8938,162 @@ var init_wiki_sync = __esm({
|
|
|
8193
8938
|
}
|
|
8194
8939
|
});
|
|
8195
8940
|
|
|
8941
|
+
// src/lib/token-spend.ts
|
|
8942
|
+
var token_spend_exports = {};
|
|
8943
|
+
__export(token_spend_exports, {
|
|
8944
|
+
getAgentSpend: () => getAgentSpend
|
|
8945
|
+
});
|
|
8946
|
+
import { readdir } from "fs/promises";
|
|
8947
|
+
import { createReadStream } from "fs";
|
|
8948
|
+
import { createInterface } from "readline";
|
|
8949
|
+
import path18 from "path";
|
|
8950
|
+
import os10 from "os";
|
|
8951
|
+
function getPricing(model) {
|
|
8952
|
+
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
8953
|
+
const stripped = model.replace(/-\d{8}$/, "");
|
|
8954
|
+
if (MODEL_PRICING[stripped]) return MODEL_PRICING[stripped];
|
|
8955
|
+
const sortedKeys = Object.keys(MODEL_PRICING).sort((a, b) => b.length - a.length);
|
|
8956
|
+
for (const key of sortedKeys) {
|
|
8957
|
+
if (model.includes(key)) return MODEL_PRICING[key];
|
|
8958
|
+
}
|
|
8959
|
+
return DEFAULT_PRICING;
|
|
8960
|
+
}
|
|
8961
|
+
async function getAgentSpend(period = "7d") {
|
|
8962
|
+
const cutoff = periodToCutoff(period);
|
|
8963
|
+
const client = getClient();
|
|
8964
|
+
const result = await client.execute({
|
|
8965
|
+
sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
|
|
8966
|
+
args: [cutoff]
|
|
8967
|
+
});
|
|
8968
|
+
if (result.rows.length === 0) return [];
|
|
8969
|
+
const sessionAgent = /* @__PURE__ */ new Map();
|
|
8970
|
+
for (const row of result.rows) {
|
|
8971
|
+
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
8972
|
+
}
|
|
8973
|
+
const claudeDir = path18.join(os10.homedir(), ".claude", "projects");
|
|
8974
|
+
let projectDirs = [];
|
|
8975
|
+
try {
|
|
8976
|
+
const entries = await readdir(claudeDir);
|
|
8977
|
+
projectDirs = entries.map((e) => path18.join(claudeDir, e));
|
|
8978
|
+
} catch {
|
|
8979
|
+
return [];
|
|
8980
|
+
}
|
|
8981
|
+
const agentTotals = /* @__PURE__ */ new Map();
|
|
8982
|
+
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
8983
|
+
for (const dir of projectDirs) {
|
|
8984
|
+
const jsonlPath = path18.join(dir, `${sessionUuid}.jsonl`);
|
|
8985
|
+
try {
|
|
8986
|
+
const usage = await extractSessionUsage(jsonlPath);
|
|
8987
|
+
if (usage.input === 0 && usage.output === 0) continue;
|
|
8988
|
+
const totals = agentTotals.get(agentId) ?? {
|
|
8989
|
+
input: 0,
|
|
8990
|
+
output: 0,
|
|
8991
|
+
cacheRead: 0,
|
|
8992
|
+
cacheCreate: 0,
|
|
8993
|
+
costUSD: 0,
|
|
8994
|
+
sessions: /* @__PURE__ */ new Set()
|
|
8995
|
+
};
|
|
8996
|
+
totals.input += usage.input;
|
|
8997
|
+
totals.output += usage.output;
|
|
8998
|
+
totals.cacheRead += usage.cacheRead;
|
|
8999
|
+
totals.cacheCreate += usage.cacheCreate;
|
|
9000
|
+
totals.costUSD += usage.costUSD;
|
|
9001
|
+
totals.sessions.add(sessionUuid);
|
|
9002
|
+
agentTotals.set(agentId, totals);
|
|
9003
|
+
break;
|
|
9004
|
+
} catch {
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
9008
|
+
return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
|
|
9009
|
+
agentId,
|
|
9010
|
+
inputTokens: t.input,
|
|
9011
|
+
outputTokens: t.output,
|
|
9012
|
+
cacheReadTokens: t.cacheRead,
|
|
9013
|
+
cacheCreationTokens: t.cacheCreate,
|
|
9014
|
+
costUSD: t.costUSD,
|
|
9015
|
+
sessions: t.sessions.size,
|
|
9016
|
+
period
|
|
9017
|
+
})).sort((a, b) => b.costUSD - a.costUSD);
|
|
9018
|
+
}
|
|
9019
|
+
async function extractSessionUsage(jsonlPath) {
|
|
9020
|
+
let input = 0;
|
|
9021
|
+
let output = 0;
|
|
9022
|
+
let cacheRead = 0;
|
|
9023
|
+
let cacheCreate = 0;
|
|
9024
|
+
let costUSD = 0;
|
|
9025
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
9026
|
+
const rl = createInterface({
|
|
9027
|
+
input: createReadStream(jsonlPath, { encoding: "utf8" }),
|
|
9028
|
+
crlfDelay: Infinity
|
|
9029
|
+
});
|
|
9030
|
+
for await (const line of rl) {
|
|
9031
|
+
if (!line.includes('"type":"assistant"')) continue;
|
|
9032
|
+
try {
|
|
9033
|
+
const record = JSON.parse(line);
|
|
9034
|
+
if (record.type !== "assistant") continue;
|
|
9035
|
+
const messageId = record.message?.id;
|
|
9036
|
+
if (messageId) {
|
|
9037
|
+
if (seenMessageIds.has(messageId)) continue;
|
|
9038
|
+
seenMessageIds.add(messageId);
|
|
9039
|
+
}
|
|
9040
|
+
const usage = record.message?.usage;
|
|
9041
|
+
if (!usage) continue;
|
|
9042
|
+
const model = record.message?.model ?? "";
|
|
9043
|
+
const pricing = getPricing(model);
|
|
9044
|
+
const inp = usage.input_tokens ?? 0;
|
|
9045
|
+
const out = usage.output_tokens ?? 0;
|
|
9046
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
9047
|
+
const cc = usage.cache_creation_input_tokens ?? 0;
|
|
9048
|
+
input += inp;
|
|
9049
|
+
output += out;
|
|
9050
|
+
cacheRead += cr;
|
|
9051
|
+
cacheCreate += cc;
|
|
9052
|
+
if (pricing) {
|
|
9053
|
+
costUSD += inp * pricing.input + out * pricing.output + cr * pricing.cacheRead + cc * pricing.cacheWrite;
|
|
9054
|
+
}
|
|
9055
|
+
} catch {
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
return { input, output, cacheRead, cacheCreate, costUSD };
|
|
9059
|
+
}
|
|
9060
|
+
function periodToCutoff(period) {
|
|
9061
|
+
const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
|
|
9062
|
+
return new Date(Date.now() - ms).toISOString();
|
|
9063
|
+
}
|
|
9064
|
+
var MODEL_PRICING, DEFAULT_PRICING;
|
|
9065
|
+
var init_token_spend = __esm({
|
|
9066
|
+
"src/lib/token-spend.ts"() {
|
|
9067
|
+
"use strict";
|
|
9068
|
+
init_database();
|
|
9069
|
+
MODEL_PRICING = {
|
|
9070
|
+
// Opus 4.5+ ($5/$25 — Anthropic price drop from original Opus 4)
|
|
9071
|
+
"claude-opus-4-7": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
9072
|
+
"claude-opus-4-6": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
9073
|
+
"claude-opus-4-5": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
9074
|
+
// Opus 4.0/4.1 (legacy $15/$75)
|
|
9075
|
+
"claude-opus-4-1": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
9076
|
+
"claude-opus-4": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
9077
|
+
// Sonnet 4.x
|
|
9078
|
+
"claude-sonnet-4": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
9079
|
+
// Sonnet 3.7/3.5
|
|
9080
|
+
"claude-3-7-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
9081
|
+
"claude-3-5-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
9082
|
+
// Haiku 4.5
|
|
9083
|
+
"claude-haiku-4-5": { input: 1 / 1e6, output: 5 / 1e6, cacheRead: 0.1 / 1e6, cacheWrite: 1.25 / 1e6 },
|
|
9084
|
+
// Haiku 3.5
|
|
9085
|
+
"claude-3-5-haiku": { input: 0.8 / 1e6, output: 4 / 1e6, cacheRead: 0.08 / 1e6, cacheWrite: 1 / 1e6 },
|
|
9086
|
+
// Opus 3
|
|
9087
|
+
"claude-3-opus": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
9088
|
+
// Sonnet 3
|
|
9089
|
+
"claude-3-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
9090
|
+
// Haiku 3
|
|
9091
|
+
"claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
|
|
9092
|
+
};
|
|
9093
|
+
DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
|
|
9094
|
+
}
|
|
9095
|
+
});
|
|
9096
|
+
|
|
8196
9097
|
// src/lib/update-check.ts
|
|
8197
9098
|
var update_check_exports = {};
|
|
8198
9099
|
__export(update_check_exports, {
|
|
@@ -8202,9 +9103,9 @@ __export(update_check_exports, {
|
|
|
8202
9103
|
});
|
|
8203
9104
|
import { execSync as execSync11 } from "child_process";
|
|
8204
9105
|
import { readFileSync as readFileSync13 } from "fs";
|
|
8205
|
-
import
|
|
9106
|
+
import path19 from "path";
|
|
8206
9107
|
function getLocalVersion(packageRoot) {
|
|
8207
|
-
const pkgPath =
|
|
9108
|
+
const pkgPath = path19.join(packageRoot, "package.json");
|
|
8208
9109
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
8209
9110
|
return pkg.version;
|
|
8210
9111
|
}
|
|
@@ -8276,9 +9177,9 @@ __export(device_registry_exports, {
|
|
|
8276
9177
|
setFriendlyName: () => setFriendlyName
|
|
8277
9178
|
});
|
|
8278
9179
|
import crypto8 from "crypto";
|
|
8279
|
-
import
|
|
9180
|
+
import os11 from "os";
|
|
8280
9181
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
|
|
8281
|
-
import
|
|
9182
|
+
import path20 from "path";
|
|
8282
9183
|
function getDeviceInfo() {
|
|
8283
9184
|
if (existsSync16(DEVICE_JSON_PATH)) {
|
|
8284
9185
|
try {
|
|
@@ -8290,13 +9191,13 @@ function getDeviceInfo() {
|
|
|
8290
9191
|
} catch {
|
|
8291
9192
|
}
|
|
8292
9193
|
}
|
|
8293
|
-
const hostname =
|
|
9194
|
+
const hostname = os11.hostname();
|
|
8294
9195
|
const info = {
|
|
8295
9196
|
deviceId: crypto8.randomUUID(),
|
|
8296
9197
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
8297
9198
|
hostname
|
|
8298
9199
|
};
|
|
8299
|
-
mkdirSync7(
|
|
9200
|
+
mkdirSync7(path20.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
8300
9201
|
writeFileSync8(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
8301
9202
|
return info;
|
|
8302
9203
|
}
|
|
@@ -8337,7 +9238,7 @@ var init_device_registry = __esm({
|
|
|
8337
9238
|
"src/lib/device-registry.ts"() {
|
|
8338
9239
|
"use strict";
|
|
8339
9240
|
init_config();
|
|
8340
|
-
DEVICE_JSON_PATH =
|
|
9241
|
+
DEVICE_JSON_PATH = path20.join(EXE_AI_DIR, "device.json");
|
|
8341
9242
|
}
|
|
8342
9243
|
});
|
|
8343
9244
|
|
|
@@ -8388,6 +9289,7 @@ function createWsClient(config, handlers) {
|
|
|
8388
9289
|
let connected = false;
|
|
8389
9290
|
let closed = false;
|
|
8390
9291
|
let reconnectDelay = MIN_RECONNECT_MS;
|
|
9292
|
+
let reconnectAttempts = 0;
|
|
8391
9293
|
let reconnectTimer = null;
|
|
8392
9294
|
let heartbeatTimer = null;
|
|
8393
9295
|
let currentAgents = [];
|
|
@@ -8404,6 +9306,7 @@ function createWsClient(config, handlers) {
|
|
|
8404
9306
|
ws.onopen = () => {
|
|
8405
9307
|
connected = true;
|
|
8406
9308
|
reconnectDelay = MIN_RECONNECT_MS;
|
|
9309
|
+
reconnectAttempts = 0;
|
|
8407
9310
|
handlers.onConnect();
|
|
8408
9311
|
sendRaw({
|
|
8409
9312
|
type: "register",
|
|
@@ -8470,6 +9373,14 @@ function createWsClient(config, handlers) {
|
|
|
8470
9373
|
}
|
|
8471
9374
|
function scheduleReconnect() {
|
|
8472
9375
|
if (closed || reconnectTimer) return;
|
|
9376
|
+
reconnectAttempts++;
|
|
9377
|
+
if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
|
|
9378
|
+
process.stderr.write(
|
|
9379
|
+
`[ws-client] Cloud sync not available \u2014 gave up after ${MAX_RECONNECT_ATTEMPTS} attempts. Check your network or endpoint config.
|
|
9380
|
+
`
|
|
9381
|
+
);
|
|
9382
|
+
return;
|
|
9383
|
+
}
|
|
8473
9384
|
const jitter = reconnectDelay * (0.75 + Math.random() * 0.5);
|
|
8474
9385
|
reconnectTimer = setTimeout(() => {
|
|
8475
9386
|
reconnectTimer = null;
|
|
@@ -8522,7 +9433,7 @@ function createWsClient(config, handlers) {
|
|
|
8522
9433
|
}
|
|
8523
9434
|
};
|
|
8524
9435
|
}
|
|
8525
|
-
var MIN_RECONNECT_MS, MAX_RECONNECT_MS, HEARTBEAT_INTERVAL_MS;
|
|
9436
|
+
var MIN_RECONNECT_MS, MAX_RECONNECT_MS, HEARTBEAT_INTERVAL_MS, MAX_RECONNECT_ATTEMPTS;
|
|
8526
9437
|
var init_ws_client = __esm({
|
|
8527
9438
|
"src/lib/ws-client.ts"() {
|
|
8528
9439
|
"use strict";
|
|
@@ -8530,6 +9441,7 @@ var init_ws_client = __esm({
|
|
|
8530
9441
|
MIN_RECONNECT_MS = 1e3;
|
|
8531
9442
|
MAX_RECONNECT_MS = 3e4;
|
|
8532
9443
|
HEARTBEAT_INTERVAL_MS = 3e4;
|
|
9444
|
+
MAX_RECONNECT_ATTEMPTS = 10;
|
|
8533
9445
|
}
|
|
8534
9446
|
});
|
|
8535
9447
|
|
|
@@ -8787,13 +9699,14 @@ var init_messaging = __esm({
|
|
|
8787
9699
|
// src/lib/exe-daemon.ts
|
|
8788
9700
|
init_config();
|
|
8789
9701
|
init_memory();
|
|
9702
|
+
init_daemon_protocol();
|
|
8790
9703
|
init_daemon_orchestration();
|
|
8791
9704
|
import net2 from "net";
|
|
8792
9705
|
import { writeFileSync as writeFileSync9, unlinkSync as unlinkSync7, mkdirSync as mkdirSync8, existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
8793
|
-
import
|
|
9706
|
+
import path21 from "path";
|
|
8794
9707
|
import { getLlama } from "node-llama-cpp";
|
|
8795
|
-
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
8796
|
-
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
9708
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path21.join(EXE_AI_DIR, "exed.sock");
|
|
9709
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path21.join(EXE_AI_DIR, "exed.pid");
|
|
8797
9710
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
8798
9711
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
8799
9712
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
@@ -8819,7 +9732,7 @@ function enqueue(queue, entry) {
|
|
|
8819
9732
|
queue.push(entry);
|
|
8820
9733
|
}
|
|
8821
9734
|
async function loadModel() {
|
|
8822
|
-
const modelPath =
|
|
9735
|
+
const modelPath = path21.join(MODELS_DIR, MODEL_FILE);
|
|
8823
9736
|
if (!existsSync17(modelPath)) {
|
|
8824
9737
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
8825
9738
|
`);
|
|
@@ -8926,13 +9839,15 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
8926
9839
|
testOk = false;
|
|
8927
9840
|
}
|
|
8928
9841
|
}
|
|
9842
|
+
const dbConnected = _storeInitialized;
|
|
8929
9843
|
sendResponse(socket, {
|
|
8930
9844
|
id: requestId,
|
|
8931
9845
|
...healthy && testOk ? {
|
|
8932
9846
|
health: {
|
|
8933
9847
|
status: "ok",
|
|
8934
9848
|
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
8935
|
-
requests_served: _requestsServed
|
|
9849
|
+
requests_served: _requestsServed,
|
|
9850
|
+
db: { connected: dbConnected, totalDbRequests: _dbRequestsServed }
|
|
8936
9851
|
}
|
|
8937
9852
|
} : { error: "unhealthy: model not loaded or test embed failed" }
|
|
8938
9853
|
});
|
|
@@ -8941,10 +9856,55 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
8941
9856
|
void shutdown();
|
|
8942
9857
|
}
|
|
8943
9858
|
}
|
|
9859
|
+
var _dbRequestsServed = 0;
|
|
9860
|
+
async function handleDbExecute(socket, requestId, sql, args) {
|
|
9861
|
+
try {
|
|
9862
|
+
if (!await ensureStoreForPolling()) {
|
|
9863
|
+
sendResponse(socket, { id: requestId, error: "DB not initialized" });
|
|
9864
|
+
return;
|
|
9865
|
+
}
|
|
9866
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
9867
|
+
const client = getClient2();
|
|
9868
|
+
const deserializedArgs = deserializeArgs(args);
|
|
9869
|
+
const result = await client.execute({ sql, args: deserializedArgs });
|
|
9870
|
+
_dbRequestsServed++;
|
|
9871
|
+
sendResponse(socket, { id: requestId, db: serializeResultSet(result) });
|
|
9872
|
+
} catch (err) {
|
|
9873
|
+
sendResponse(socket, {
|
|
9874
|
+
id: requestId,
|
|
9875
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9876
|
+
});
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9879
|
+
async function handleDbBatch(socket, requestId, statements, mode) {
|
|
9880
|
+
try {
|
|
9881
|
+
if (!await ensureStoreForPolling()) {
|
|
9882
|
+
sendResponse(socket, { id: requestId, error: "DB not initialized" });
|
|
9883
|
+
return;
|
|
9884
|
+
}
|
|
9885
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
9886
|
+
const client = getClient2();
|
|
9887
|
+
const stmts = statements.map((s) => ({
|
|
9888
|
+
sql: s.sql,
|
|
9889
|
+
args: deserializeArgs(s.args)
|
|
9890
|
+
}));
|
|
9891
|
+
const results = await client.batch(stmts, mode);
|
|
9892
|
+
_dbRequestsServed++;
|
|
9893
|
+
sendResponse(socket, {
|
|
9894
|
+
id: requestId,
|
|
9895
|
+
"db-batch": results.map(serializeResultSet)
|
|
9896
|
+
});
|
|
9897
|
+
} catch (err) {
|
|
9898
|
+
sendResponse(socket, {
|
|
9899
|
+
id: requestId,
|
|
9900
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9901
|
+
});
|
|
9902
|
+
}
|
|
9903
|
+
}
|
|
8944
9904
|
function startServer() {
|
|
8945
|
-
mkdirSync8(
|
|
9905
|
+
mkdirSync8(path21.dirname(SOCKET_PATH2), { recursive: true });
|
|
8946
9906
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
8947
|
-
const oldPath =
|
|
9907
|
+
const oldPath = path21.join(path21.dirname(SOCKET_PATH2), oldFile);
|
|
8948
9908
|
try {
|
|
8949
9909
|
if (oldFile.endsWith(".pid")) {
|
|
8950
9910
|
const pid = parseInt(readFileSync15(oldPath, "utf8").trim(), 10);
|
|
@@ -8983,6 +9943,22 @@ function startServer() {
|
|
|
8983
9943
|
void handleHealthCheck(socket, request.id ?? "health");
|
|
8984
9944
|
continue;
|
|
8985
9945
|
}
|
|
9946
|
+
if (request.type === "db-execute") {
|
|
9947
|
+
if (!request.id || typeof request.sql !== "string") {
|
|
9948
|
+
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid db-execute: missing id or sql" });
|
|
9949
|
+
continue;
|
|
9950
|
+
}
|
|
9951
|
+
void handleDbExecute(socket, request.id, request.sql, request.args ?? []);
|
|
9952
|
+
continue;
|
|
9953
|
+
}
|
|
9954
|
+
if (request.type === "db-batch") {
|
|
9955
|
+
if (!request.id || !Array.isArray(request.statements)) {
|
|
9956
|
+
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid db-batch: missing id or statements" });
|
|
9957
|
+
continue;
|
|
9958
|
+
}
|
|
9959
|
+
void handleDbBatch(socket, request.id, request.statements, request.mode);
|
|
9960
|
+
continue;
|
|
9961
|
+
}
|
|
8986
9962
|
if (!request.id || !Array.isArray(request.texts)) {
|
|
8987
9963
|
sendResponse(socket, { id: request.id ?? "unknown", error: "Invalid request: missing id or texts" });
|
|
8988
9964
|
continue;
|
|
@@ -9313,7 +10289,7 @@ function startWikiSync() {
|
|
|
9313
10289
|
});
|
|
9314
10290
|
}
|
|
9315
10291
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
9316
|
-
var AGENT_STATS_PATH =
|
|
10292
|
+
var AGENT_STATS_PATH = path21.join(EXE_AI_DIR, "agent-stats.json");
|
|
9317
10293
|
async function writeAgentStats() {
|
|
9318
10294
|
if (!await ensureStoreForPolling()) return;
|
|
9319
10295
|
try {
|
|
@@ -9322,21 +10298,50 @@ async function writeAgentStats() {
|
|
|
9322
10298
|
const result = await client.execute({
|
|
9323
10299
|
sql: `SELECT agent_id,
|
|
9324
10300
|
COUNT(*) as total,
|
|
9325
|
-
SUM(CASE WHEN timestamp > datetime('now', '-
|
|
10301
|
+
SUM(CASE WHEN timestamp > datetime('now', '-1 day') THEN 1 ELSE 0 END) as growth_24h,
|
|
10302
|
+
SUM(CASE WHEN timestamp > datetime('now', '-7 days') THEN 1 ELSE 0 END) as growth_7d,
|
|
10303
|
+
SUM(CASE WHEN timestamp > datetime('now', '-30 days') THEN 1 ELSE 0 END) as growth_30d
|
|
9326
10304
|
FROM memories
|
|
9327
10305
|
WHERE agent_id != 'default'
|
|
9328
10306
|
GROUP BY agent_id
|
|
9329
10307
|
ORDER BY total DESC`,
|
|
9330
10308
|
args: []
|
|
9331
10309
|
});
|
|
9332
|
-
const
|
|
10310
|
+
const agentsBase = result.rows.map((row) => ({
|
|
9333
10311
|
id: row.agent_id,
|
|
9334
10312
|
total: Number(row.total),
|
|
9335
|
-
|
|
10313
|
+
growth24h: Number(row.growth_24h),
|
|
10314
|
+
growth7d: Number(row.growth_7d),
|
|
10315
|
+
growth30d: Number(row.growth_30d),
|
|
10316
|
+
spend24h: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 },
|
|
10317
|
+
spend7d: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 },
|
|
10318
|
+
spend30d: { inputTokens: 0, outputTokens: 0, costUSD: 0, sessions: 0 }
|
|
9336
10319
|
}));
|
|
10320
|
+
try {
|
|
10321
|
+
const { getAgentSpend: getAgentSpend2 } = await Promise.resolve().then(() => (init_token_spend(), token_spend_exports));
|
|
10322
|
+
const [spend24h, spend7d, spend30d] = await Promise.all([
|
|
10323
|
+
getAgentSpend2("24h"),
|
|
10324
|
+
getAgentSpend2("7d"),
|
|
10325
|
+
getAgentSpend2("30d")
|
|
10326
|
+
]);
|
|
10327
|
+
const map24h = new Map(spend24h.map((s) => [s.agentId, s]));
|
|
10328
|
+
const map7d = new Map(spend7d.map((s) => [s.agentId, s]));
|
|
10329
|
+
const map30d = new Map(spend30d.map((s) => [s.agentId, s]));
|
|
10330
|
+
for (const agent of agentsBase) {
|
|
10331
|
+
const s24 = map24h.get(agent.id);
|
|
10332
|
+
const s7 = map7d.get(agent.id);
|
|
10333
|
+
const s30 = map30d.get(agent.id);
|
|
10334
|
+
if (s24) agent.spend24h = { inputTokens: s24.inputTokens, outputTokens: s24.outputTokens, costUSD: s24.costUSD, sessions: s24.sessions };
|
|
10335
|
+
if (s7) agent.spend7d = { inputTokens: s7.inputTokens, outputTokens: s7.outputTokens, costUSD: s7.costUSD, sessions: s7.sessions };
|
|
10336
|
+
if (s30) agent.spend30d = { inputTokens: s30.inputTokens, outputTokens: s30.outputTokens, costUSD: s30.costUSD, sessions: s30.sessions };
|
|
10337
|
+
}
|
|
10338
|
+
} catch (err) {
|
|
10339
|
+
process.stderr.write(`[exed] Agent spend merge: ${err instanceof Error ? err.message : String(err)}
|
|
10340
|
+
`);
|
|
10341
|
+
}
|
|
9337
10342
|
const stats = {
|
|
9338
10343
|
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9339
|
-
agents,
|
|
10344
|
+
agents: agentsBase,
|
|
9340
10345
|
daemon: {
|
|
9341
10346
|
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
9342
10347
|
pid: process.pid
|
|
@@ -9459,6 +10464,32 @@ function startOrphanReaper() {
|
|
|
9459
10464
|
process.stderr.write(`[exed] Orphan reaper started (every ${ORPHAN_REAP_INTERVAL_MS / 6e4}m)
|
|
9460
10465
|
`);
|
|
9461
10466
|
}
|
|
10467
|
+
var AUTO_WAKE_INTERVAL_MS = 60 * 1e3;
|
|
10468
|
+
function startAutoWake() {
|
|
10469
|
+
const tick = async () => {
|
|
10470
|
+
if (process.env.EXE_AUTO_WAKE === "0") return;
|
|
10471
|
+
if (!await ensureStoreForPolling()) return;
|
|
10472
|
+
try {
|
|
10473
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
10474
|
+
const { pollOrphanedTasks: pollOrphanedTasks2, createAutoWakeRealDeps: createAutoWakeRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
|
|
10475
|
+
const { getCurrentSessionScope: getCurrentSessionScope2 } = await Promise.resolve().then(() => (init_task_scope(), task_scope_exports));
|
|
10476
|
+
const sessionScope = getCurrentSessionScope2() ?? "";
|
|
10477
|
+
const deps = createAutoWakeRealDeps2(getClient2, sessionScope, process.cwd());
|
|
10478
|
+
const woken = await pollOrphanedTasks2(deps);
|
|
10479
|
+
for (const agent of woken) {
|
|
10480
|
+
process.stderr.write(`[exed] Auto-wake: spawned ${agent}
|
|
10481
|
+
`);
|
|
10482
|
+
}
|
|
10483
|
+
} catch (err) {
|
|
10484
|
+
process.stderr.write(`[exed] Auto-wake error: ${err instanceof Error ? err.message : String(err)}
|
|
10485
|
+
`);
|
|
10486
|
+
}
|
|
10487
|
+
};
|
|
10488
|
+
const timer = setInterval(() => void tick(), AUTO_WAKE_INTERVAL_MS);
|
|
10489
|
+
timer.unref();
|
|
10490
|
+
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
10491
|
+
`);
|
|
10492
|
+
}
|
|
9462
10493
|
process.on("SIGINT", () => void shutdown());
|
|
9463
10494
|
process.on("SIGTERM", () => void shutdown());
|
|
9464
10495
|
function checkExistingDaemon() {
|
|
@@ -9532,6 +10563,7 @@ function startAutoUpdateCheck() {
|
|
|
9532
10563
|
if (checkExistingDaemon()) {
|
|
9533
10564
|
process.exit(0);
|
|
9534
10565
|
}
|
|
10566
|
+
process.env.EXE_IS_DAEMON = "1";
|
|
9535
10567
|
try {
|
|
9536
10568
|
await loadModel();
|
|
9537
10569
|
startServer();
|
|
@@ -9545,6 +10577,7 @@ try {
|
|
|
9545
10577
|
startOrphanReaper();
|
|
9546
10578
|
startAgentStats();
|
|
9547
10579
|
startCapacityMonitoring();
|
|
10580
|
+
startAutoWake();
|
|
9548
10581
|
startGraphExtraction();
|
|
9549
10582
|
startWikiSync();
|
|
9550
10583
|
startIntercomQueueDrain();
|