@askexenow/exe-os 0.8.80 → 0.8.82
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 +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
package/dist/mcp/server.js
CHANGED
|
@@ -39,7 +39,6 @@ var config_exports = {};
|
|
|
39
39
|
__export(config_exports, {
|
|
40
40
|
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
41
41
|
CONFIG_PATH: () => CONFIG_PATH,
|
|
42
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
43
42
|
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
44
43
|
DB_PATH: () => DB_PATH,
|
|
45
44
|
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
@@ -195,7 +194,7 @@ async function loadConfigFrom(configPath) {
|
|
|
195
194
|
return { ...DEFAULT_CONFIG };
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH,
|
|
197
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
199
198
|
var init_config = __esm({
|
|
200
199
|
"src/lib/config.ts"() {
|
|
201
200
|
"use strict";
|
|
@@ -203,7 +202,6 @@ var init_config = __esm({
|
|
|
203
202
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
204
203
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
205
204
|
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
206
|
-
COO_AGENT_NAME = "exe";
|
|
207
205
|
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
208
206
|
CURRENT_CONFIG_VERSION = 1;
|
|
209
207
|
DEFAULT_CONFIG = {
|
|
@@ -239,13 +237,7 @@ var init_config = __esm({
|
|
|
239
237
|
wikiUrl: "",
|
|
240
238
|
wikiApiKey: "",
|
|
241
239
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
242
|
-
wikiWorkspaceMapping: {
|
|
243
|
-
exe: "Executive",
|
|
244
|
-
yoshi: "Engineering",
|
|
245
|
-
mari: "Marketing",
|
|
246
|
-
tom: "Engineering",
|
|
247
|
-
sasha: "Production"
|
|
248
|
-
},
|
|
240
|
+
wikiWorkspaceMapping: {},
|
|
249
241
|
wikiAutoUpdate: true,
|
|
250
242
|
wikiAutoUpdateThreshold: 0.5,
|
|
251
243
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -370,6 +362,10 @@ function spawnDaemon() {
|
|
|
370
362
|
stdio: ["ignore", "ignore", stderrFd],
|
|
371
363
|
env: {
|
|
372
364
|
...process.env,
|
|
365
|
+
TMUX: void 0,
|
|
366
|
+
// Daemon is global — must not inherit session scope
|
|
367
|
+
TMUX_PANE: void 0,
|
|
368
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
373
369
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
374
370
|
EXE_DAEMON_PID: PID_PATH
|
|
375
371
|
}
|
|
@@ -730,7 +726,7 @@ function wrapWithRetry(client) {
|
|
|
730
726
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
731
727
|
}
|
|
732
728
|
if (prop === "batch") {
|
|
733
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
729
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
734
730
|
}
|
|
735
731
|
return Reflect.get(target, prop, receiver);
|
|
736
732
|
}
|
|
@@ -746,6 +742,204 @@ var init_db_retry = __esm({
|
|
|
746
742
|
}
|
|
747
743
|
});
|
|
748
744
|
|
|
745
|
+
// src/lib/employees.ts
|
|
746
|
+
var employees_exports = {};
|
|
747
|
+
__export(employees_exports, {
|
|
748
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
749
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
750
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
751
|
+
addEmployee: () => addEmployee,
|
|
752
|
+
canCoordinate: () => canCoordinate,
|
|
753
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
754
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
755
|
+
getEmployee: () => getEmployee,
|
|
756
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
757
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
758
|
+
hasRole: () => hasRole,
|
|
759
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
760
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
761
|
+
isMultiInstance: () => isMultiInstance,
|
|
762
|
+
loadEmployees: () => loadEmployees,
|
|
763
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
764
|
+
normalizeRole: () => normalizeRole,
|
|
765
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
766
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
767
|
+
saveEmployees: () => saveEmployees,
|
|
768
|
+
validateEmployeeName: () => validateEmployeeName
|
|
769
|
+
});
|
|
770
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
771
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
772
|
+
import { execSync } from "child_process";
|
|
773
|
+
import path3 from "path";
|
|
774
|
+
import os2 from "os";
|
|
775
|
+
function normalizeRole(role) {
|
|
776
|
+
return (role ?? "").trim().toLowerCase();
|
|
777
|
+
}
|
|
778
|
+
function isCoordinatorRole(role) {
|
|
779
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
780
|
+
}
|
|
781
|
+
function getCoordinatorEmployee(employees) {
|
|
782
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
783
|
+
}
|
|
784
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
785
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
786
|
+
}
|
|
787
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
788
|
+
if (!agentName) return false;
|
|
789
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
790
|
+
}
|
|
791
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
792
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
793
|
+
}
|
|
794
|
+
function validateEmployeeName(name) {
|
|
795
|
+
if (!name) {
|
|
796
|
+
return { valid: false, error: "Name is required" };
|
|
797
|
+
}
|
|
798
|
+
if (name.length > 32) {
|
|
799
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
800
|
+
}
|
|
801
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
802
|
+
return {
|
|
803
|
+
valid: false,
|
|
804
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return { valid: true };
|
|
808
|
+
}
|
|
809
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
810
|
+
if (!existsSync3(employeesPath)) {
|
|
811
|
+
return [];
|
|
812
|
+
}
|
|
813
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
814
|
+
try {
|
|
815
|
+
return JSON.parse(raw);
|
|
816
|
+
} catch {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
821
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
822
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
823
|
+
}
|
|
824
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
825
|
+
if (!existsSync3(employeesPath)) return [];
|
|
826
|
+
try {
|
|
827
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
828
|
+
} catch {
|
|
829
|
+
return [];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function getEmployee(employees, name) {
|
|
833
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
834
|
+
}
|
|
835
|
+
function getEmployeeByRole(employees, role) {
|
|
836
|
+
const lower = role.toLowerCase();
|
|
837
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
838
|
+
}
|
|
839
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
840
|
+
const lower = role.toLowerCase();
|
|
841
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
842
|
+
}
|
|
843
|
+
function hasRole(agentName, role) {
|
|
844
|
+
const employees = loadEmployeesSync();
|
|
845
|
+
const emp = getEmployee(employees, agentName);
|
|
846
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
847
|
+
}
|
|
848
|
+
function isMultiInstance(agentName, employees) {
|
|
849
|
+
const roster = employees ?? loadEmployeesSync();
|
|
850
|
+
const emp = getEmployee(roster, agentName);
|
|
851
|
+
if (!emp) return false;
|
|
852
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
853
|
+
}
|
|
854
|
+
function addEmployee(employees, employee) {
|
|
855
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
856
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
857
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
858
|
+
}
|
|
859
|
+
return [...employees, normalized];
|
|
860
|
+
}
|
|
861
|
+
async function normalizeRosterCase(rosterPath) {
|
|
862
|
+
const employees = await loadEmployees(rosterPath);
|
|
863
|
+
let changed = false;
|
|
864
|
+
for (const emp of employees) {
|
|
865
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
866
|
+
const oldName = emp.name;
|
|
867
|
+
emp.name = emp.name.toLowerCase();
|
|
868
|
+
changed = true;
|
|
869
|
+
try {
|
|
870
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
871
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
872
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
873
|
+
if (existsSync3(oldPath) && !existsSync3(newPath)) {
|
|
874
|
+
renameSync2(oldPath, newPath);
|
|
875
|
+
} else if (existsSync3(oldPath) && oldPath !== newPath) {
|
|
876
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
877
|
+
writeFileSync(newPath, content, "utf-8");
|
|
878
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
879
|
+
unlinkSync2(oldPath);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (changed) {
|
|
887
|
+
await saveEmployees(employees, rosterPath);
|
|
888
|
+
}
|
|
889
|
+
return changed;
|
|
890
|
+
}
|
|
891
|
+
function findExeBin() {
|
|
892
|
+
try {
|
|
893
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
894
|
+
} catch {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function registerBinSymlinks(name) {
|
|
899
|
+
const created = [];
|
|
900
|
+
const skipped = [];
|
|
901
|
+
const errors = [];
|
|
902
|
+
const exeBinPath = findExeBin();
|
|
903
|
+
if (!exeBinPath) {
|
|
904
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
905
|
+
return { created, skipped, errors };
|
|
906
|
+
}
|
|
907
|
+
const binDir = path3.dirname(exeBinPath);
|
|
908
|
+
let target;
|
|
909
|
+
try {
|
|
910
|
+
target = readlinkSync(exeBinPath);
|
|
911
|
+
} catch {
|
|
912
|
+
errors.push("Could not read 'exe' symlink");
|
|
913
|
+
return { created, skipped, errors };
|
|
914
|
+
}
|
|
915
|
+
for (const suffix of ["", "-opencode"]) {
|
|
916
|
+
const linkName = `${name}${suffix}`;
|
|
917
|
+
const linkPath = path3.join(binDir, linkName);
|
|
918
|
+
if (existsSync3(linkPath)) {
|
|
919
|
+
skipped.push(linkName);
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
symlinkSync(target, linkPath);
|
|
924
|
+
created.push(linkName);
|
|
925
|
+
} catch (err) {
|
|
926
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return { created, skipped, errors };
|
|
930
|
+
}
|
|
931
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
932
|
+
var init_employees = __esm({
|
|
933
|
+
"src/lib/employees.ts"() {
|
|
934
|
+
"use strict";
|
|
935
|
+
init_config();
|
|
936
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
937
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
938
|
+
COORDINATOR_ROLE = "COO";
|
|
939
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
|
|
749
943
|
// src/lib/database.ts
|
|
750
944
|
var database_exports = {};
|
|
751
945
|
__export(database_exports, {
|
|
@@ -893,22 +1087,24 @@ async function ensureSchema() {
|
|
|
893
1087
|
ON behaviors(agent_id, active);
|
|
894
1088
|
`);
|
|
895
1089
|
try {
|
|
1090
|
+
const coordinatorName = getCoordinatorName();
|
|
896
1091
|
const existing = await client.execute({
|
|
897
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
898
|
-
args: []
|
|
1092
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1093
|
+
args: [coordinatorName]
|
|
899
1094
|
});
|
|
900
1095
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1096
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1097
|
+
for (const [domain, content] of [
|
|
1098
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1099
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1100
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1101
|
+
]) {
|
|
1102
|
+
await client.execute({
|
|
1103
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1104
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1105
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
912
1108
|
}
|
|
913
1109
|
} catch {
|
|
914
1110
|
}
|
|
@@ -1600,6 +1796,39 @@ async function ensureSchema() {
|
|
|
1600
1796
|
} catch {
|
|
1601
1797
|
}
|
|
1602
1798
|
}
|
|
1799
|
+
try {
|
|
1800
|
+
await client.execute({
|
|
1801
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1802
|
+
args: []
|
|
1803
|
+
});
|
|
1804
|
+
} catch {
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
await client.execute(
|
|
1808
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1809
|
+
);
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
await client.execute({
|
|
1814
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1815
|
+
args: []
|
|
1816
|
+
});
|
|
1817
|
+
} catch {
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
await client.execute(
|
|
1821
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1822
|
+
);
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
await client.execute({
|
|
1827
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1828
|
+
args: []
|
|
1829
|
+
});
|
|
1830
|
+
} catch {
|
|
1831
|
+
}
|
|
1603
1832
|
}
|
|
1604
1833
|
async function disposeDatabase() {
|
|
1605
1834
|
if (_client) {
|
|
@@ -1613,6 +1842,7 @@ var init_database = __esm({
|
|
|
1613
1842
|
"src/lib/database.ts"() {
|
|
1614
1843
|
"use strict";
|
|
1615
1844
|
init_db_retry();
|
|
1845
|
+
init_employees();
|
|
1616
1846
|
_client = null;
|
|
1617
1847
|
_resilientClient = null;
|
|
1618
1848
|
initTurso = initDatabase;
|
|
@@ -1621,15 +1851,15 @@ var init_database = __esm({
|
|
|
1621
1851
|
});
|
|
1622
1852
|
|
|
1623
1853
|
// src/lib/keychain.ts
|
|
1624
|
-
import { readFile as
|
|
1625
|
-
import { existsSync as
|
|
1626
|
-
import
|
|
1627
|
-
import
|
|
1854
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1855
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1856
|
+
import path4 from "path";
|
|
1857
|
+
import os3 from "os";
|
|
1628
1858
|
function getKeyDir() {
|
|
1629
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
1859
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os3.homedir(), ".exe-os");
|
|
1630
1860
|
}
|
|
1631
1861
|
function getKeyPath() {
|
|
1632
|
-
return
|
|
1862
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1633
1863
|
}
|
|
1634
1864
|
async function tryKeytar() {
|
|
1635
1865
|
try {
|
|
@@ -1650,11 +1880,11 @@ async function getMasterKey() {
|
|
|
1650
1880
|
}
|
|
1651
1881
|
}
|
|
1652
1882
|
const keyPath = getKeyPath();
|
|
1653
|
-
if (!
|
|
1883
|
+
if (!existsSync4(keyPath)) {
|
|
1654
1884
|
return null;
|
|
1655
1885
|
}
|
|
1656
1886
|
try {
|
|
1657
|
-
const content = await
|
|
1887
|
+
const content = await readFile3(keyPath, "utf-8");
|
|
1658
1888
|
return Buffer.from(content.trim(), "base64");
|
|
1659
1889
|
} catch {
|
|
1660
1890
|
return null;
|
|
@@ -1737,12 +1967,12 @@ __export(shard_manager_exports, {
|
|
|
1737
1967
|
listShards: () => listShards,
|
|
1738
1968
|
shardExists: () => shardExists
|
|
1739
1969
|
});
|
|
1740
|
-
import
|
|
1741
|
-
import { existsSync as
|
|
1970
|
+
import path5 from "path";
|
|
1971
|
+
import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
|
|
1742
1972
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1743
1973
|
function initShardManager(encryptionKey) {
|
|
1744
1974
|
_encryptionKey = encryptionKey;
|
|
1745
|
-
if (!
|
|
1975
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1746
1976
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1747
1977
|
}
|
|
1748
1978
|
_shardingEnabled = true;
|
|
@@ -1763,7 +1993,7 @@ function getShardClient(projectName) {
|
|
|
1763
1993
|
}
|
|
1764
1994
|
const cached = _shards.get(safeName);
|
|
1765
1995
|
if (cached) return cached;
|
|
1766
|
-
const dbPath =
|
|
1996
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1767
1997
|
const client = createClient2({
|
|
1768
1998
|
url: `file:${dbPath}`,
|
|
1769
1999
|
encryptionKey: _encryptionKey
|
|
@@ -1773,10 +2003,10 @@ function getShardClient(projectName) {
|
|
|
1773
2003
|
}
|
|
1774
2004
|
function shardExists(projectName) {
|
|
1775
2005
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1776
|
-
return
|
|
2006
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1777
2007
|
}
|
|
1778
2008
|
function listShards() {
|
|
1779
|
-
if (!
|
|
2009
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1780
2010
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1781
2011
|
}
|
|
1782
2012
|
async function ensureShardSchema(client) {
|
|
@@ -1846,7 +2076,11 @@ async function ensureShardSchema(client) {
|
|
|
1846
2076
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1847
2077
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1848
2078
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1849
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
2079
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
2080
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
2081
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
2082
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
2083
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
1850
2084
|
]) {
|
|
1851
2085
|
try {
|
|
1852
2086
|
await client.execute(col);
|
|
@@ -1958,7 +2192,7 @@ var init_shard_manager = __esm({
|
|
|
1958
2192
|
"src/lib/shard-manager.ts"() {
|
|
1959
2193
|
"use strict";
|
|
1960
2194
|
init_config();
|
|
1961
|
-
SHARDS_DIR =
|
|
2195
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
1962
2196
|
_shards = /* @__PURE__ */ new Map();
|
|
1963
2197
|
_encryptionKey = null;
|
|
1964
2198
|
_shardingEnabled = false;
|
|
@@ -1976,26 +2210,26 @@ var init_platform_procedures = __esm({
|
|
|
1976
2210
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1977
2211
|
domain: "architecture",
|
|
1978
2212
|
priority: "p0",
|
|
1979
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
2213
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
1980
2214
|
},
|
|
1981
2215
|
{
|
|
1982
2216
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1983
2217
|
domain: "architecture",
|
|
1984
2218
|
priority: "p0",
|
|
1985
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
2219
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
|
|
1986
2220
|
},
|
|
1987
2221
|
{
|
|
1988
|
-
title: "Sessions explained \u2014
|
|
2222
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
1989
2223
|
domain: "architecture",
|
|
1990
2224
|
priority: "p0",
|
|
1991
|
-
content: "Each
|
|
2225
|
+
content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
1992
2226
|
},
|
|
1993
2227
|
// --- Hierarchy and dispatch ---
|
|
1994
2228
|
{
|
|
1995
2229
|
title: "Chain of command \u2014 who talks to whom",
|
|
1996
2230
|
domain: "workflow",
|
|
1997
2231
|
priority: "p0",
|
|
1998
|
-
content: "Founder
|
|
2232
|
+
content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
1999
2233
|
},
|
|
2000
2234
|
{
|
|
2001
2235
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -2005,30 +2239,30 @@ var init_platform_procedures = __esm({
|
|
|
2005
2239
|
},
|
|
2006
2240
|
// --- Session isolation ---
|
|
2007
2241
|
{
|
|
2008
|
-
title: "Session scoping \u2014 stay in your
|
|
2242
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
2009
2243
|
domain: "security",
|
|
2010
2244
|
priority: "p0",
|
|
2011
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
2245
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
|
|
2012
2246
|
},
|
|
2013
2247
|
{
|
|
2014
2248
|
title: "Session isolation \u2014 never touch another session's work",
|
|
2015
2249
|
domain: "workflow",
|
|
2016
2250
|
priority: "p0",
|
|
2017
|
-
content:
|
|
2251
|
+
content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
|
|
2018
2252
|
},
|
|
2019
2253
|
// --- Engineering: session scoping in code ---
|
|
2020
2254
|
{
|
|
2021
2255
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
2022
2256
|
domain: "architecture",
|
|
2023
2257
|
priority: "p0",
|
|
2024
|
-
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current
|
|
2258
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
|
|
2025
2259
|
},
|
|
2026
2260
|
// --- Hard constraints ---
|
|
2027
2261
|
{
|
|
2028
2262
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
2029
2263
|
domain: "security",
|
|
2030
2264
|
priority: "p0",
|
|
2031
|
-
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014
|
|
2265
|
+
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
2032
2266
|
},
|
|
2033
2267
|
// --- Operations ---
|
|
2034
2268
|
{
|
|
@@ -2268,7 +2502,10 @@ async function writeMemory(record) {
|
|
|
2268
2502
|
source_path: record.source_path ?? null,
|
|
2269
2503
|
source_type: record.source_type ?? null,
|
|
2270
2504
|
tier: record.tier ?? classifyTier(record),
|
|
2271
|
-
supersedes_id: record.supersedes_id ?? null
|
|
2505
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
2506
|
+
draft: record.draft ? 1 : 0,
|
|
2507
|
+
memory_type: record.memory_type ?? "raw",
|
|
2508
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
2272
2509
|
};
|
|
2273
2510
|
_pendingRecords.push(dbRow);
|
|
2274
2511
|
orgBus.emit({
|
|
@@ -2323,6 +2560,9 @@ async function flushBatch() {
|
|
|
2323
2560
|
const sourceType = row.source_type ?? null;
|
|
2324
2561
|
const tier = row.tier ?? 3;
|
|
2325
2562
|
const supersedesId = row.supersedes_id ?? null;
|
|
2563
|
+
const draft = row.draft ? 1 : 0;
|
|
2564
|
+
const memoryType = row.memory_type ?? "raw";
|
|
2565
|
+
const trajectory = row.trajectory ?? null;
|
|
2326
2566
|
return {
|
|
2327
2567
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
2328
2568
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -2330,15 +2570,15 @@ async function flushBatch() {
|
|
|
2330
2570
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2331
2571
|
confidence, last_accessed,
|
|
2332
2572
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2333
|
-
source_path, source_type, tier, supersedes_id)
|
|
2334
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
2573
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
2574
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
2335
2575
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2336
2576
|
tool_name, project_name,
|
|
2337
2577
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2338
2578
|
confidence, last_accessed,
|
|
2339
2579
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2340
|
-
source_path, source_type, tier, supersedes_id)
|
|
2341
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2580
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
2581
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2342
2582
|
args: hasVector ? [
|
|
2343
2583
|
row.id,
|
|
2344
2584
|
row.agent_id,
|
|
@@ -2364,7 +2604,10 @@ async function flushBatch() {
|
|
|
2364
2604
|
sourcePath,
|
|
2365
2605
|
sourceType,
|
|
2366
2606
|
tier,
|
|
2367
|
-
supersedesId
|
|
2607
|
+
supersedesId,
|
|
2608
|
+
draft,
|
|
2609
|
+
memoryType,
|
|
2610
|
+
trajectory
|
|
2368
2611
|
] : [
|
|
2369
2612
|
row.id,
|
|
2370
2613
|
row.agent_id,
|
|
@@ -2389,7 +2632,10 @@ async function flushBatch() {
|
|
|
2389
2632
|
sourcePath,
|
|
2390
2633
|
sourceType,
|
|
2391
2634
|
tier,
|
|
2392
|
-
supersedesId
|
|
2635
|
+
supersedesId,
|
|
2636
|
+
draft,
|
|
2637
|
+
memoryType,
|
|
2638
|
+
trajectory
|
|
2393
2639
|
]
|
|
2394
2640
|
};
|
|
2395
2641
|
};
|
|
@@ -2458,6 +2704,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2458
2704
|
const limit = options?.limit ?? 10;
|
|
2459
2705
|
const statusFilter = options?.includeArchived ? "" : `
|
|
2460
2706
|
AND COALESCE(status, 'active') = 'active'`;
|
|
2707
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
2708
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
2461
2709
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
2462
2710
|
tool_name, project_name,
|
|
2463
2711
|
has_error, raw_text, vector, importance, status,
|
|
@@ -2467,7 +2715,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2467
2715
|
source_path, source_type
|
|
2468
2716
|
FROM memories
|
|
2469
2717
|
WHERE agent_id = ?
|
|
2470
|
-
AND vector IS NOT NULL${statusFilter}
|
|
2718
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
2471
2719
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
2472
2720
|
const args = [agentId];
|
|
2473
2721
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -2489,6 +2737,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2489
2737
|
sql += ` AND timestamp >= ?`;
|
|
2490
2738
|
args.push(options.since);
|
|
2491
2739
|
}
|
|
2740
|
+
if (options?.memoryType) {
|
|
2741
|
+
sql += ` AND memory_type = ?`;
|
|
2742
|
+
args.push(options.memoryType);
|
|
2743
|
+
}
|
|
2492
2744
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
2493
2745
|
args.push(vectorToBlob(queryVector));
|
|
2494
2746
|
sql += ` LIMIT ?`;
|
|
@@ -2714,7 +2966,7 @@ var init_self_query_router = __esm({
|
|
|
2714
2966
|
},
|
|
2715
2967
|
is_broad_query: {
|
|
2716
2968
|
type: "boolean",
|
|
2717
|
-
description: "True if the query is exploratory/broad (e.g., 'what has
|
|
2969
|
+
description: "True if the query is exploratory/broad (e.g., 'what has the CTO been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
|
|
2718
2970
|
}
|
|
2719
2971
|
},
|
|
2720
2972
|
required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
|
|
@@ -2729,34 +2981,34 @@ __export(project_name_exports, {
|
|
|
2729
2981
|
_resetCache: () => _resetCache,
|
|
2730
2982
|
getProjectName: () => getProjectName
|
|
2731
2983
|
});
|
|
2732
|
-
import { execSync } from "child_process";
|
|
2733
|
-
import
|
|
2984
|
+
import { execSync as execSync2 } from "child_process";
|
|
2985
|
+
import path6 from "path";
|
|
2734
2986
|
function getProjectName(cwd) {
|
|
2735
2987
|
const dir = cwd ?? process.cwd();
|
|
2736
2988
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
2737
2989
|
try {
|
|
2738
2990
|
let repoRoot;
|
|
2739
2991
|
try {
|
|
2740
|
-
const gitCommonDir =
|
|
2992
|
+
const gitCommonDir = execSync2("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2741
2993
|
cwd: dir,
|
|
2742
2994
|
encoding: "utf8",
|
|
2743
2995
|
timeout: 2e3,
|
|
2744
2996
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2745
2997
|
}).trim();
|
|
2746
|
-
repoRoot =
|
|
2998
|
+
repoRoot = path6.dirname(gitCommonDir);
|
|
2747
2999
|
} catch {
|
|
2748
|
-
repoRoot =
|
|
3000
|
+
repoRoot = execSync2("git rev-parse --show-toplevel", {
|
|
2749
3001
|
cwd: dir,
|
|
2750
3002
|
encoding: "utf8",
|
|
2751
3003
|
timeout: 2e3,
|
|
2752
3004
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2753
3005
|
}).trim();
|
|
2754
3006
|
}
|
|
2755
|
-
_cached =
|
|
3007
|
+
_cached = path6.basename(repoRoot);
|
|
2756
3008
|
_cachedCwd = dir;
|
|
2757
3009
|
return _cached;
|
|
2758
3010
|
} catch {
|
|
2759
|
-
_cached =
|
|
3011
|
+
_cached = path6.basename(dir);
|
|
2760
3012
|
_cachedCwd = dir;
|
|
2761
3013
|
return _cached;
|
|
2762
3014
|
}
|
|
@@ -2779,14 +3031,14 @@ var file_grep_exports = {};
|
|
|
2779
3031
|
__export(file_grep_exports, {
|
|
2780
3032
|
grepProjectFiles: () => grepProjectFiles
|
|
2781
3033
|
});
|
|
2782
|
-
import { execSync as
|
|
2783
|
-
import { readFileSync as
|
|
2784
|
-
import
|
|
3034
|
+
import { execSync as execSync3 } from "child_process";
|
|
3035
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
|
|
3036
|
+
import path7 from "path";
|
|
2785
3037
|
import crypto from "crypto";
|
|
2786
3038
|
function hasRipgrep() {
|
|
2787
3039
|
if (_hasRg === null) {
|
|
2788
3040
|
try {
|
|
2789
|
-
|
|
3041
|
+
execSync3("rg --version", { stdio: "ignore", timeout: 2e3 });
|
|
2790
3042
|
_hasRg = true;
|
|
2791
3043
|
} catch {
|
|
2792
3044
|
_hasRg = false;
|
|
@@ -2821,7 +3073,7 @@ async function grepProjectFiles(query, projectRoot, options) {
|
|
|
2821
3073
|
session_id: "file-grep",
|
|
2822
3074
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2823
3075
|
tool_name: "file_grep",
|
|
2824
|
-
project_name:
|
|
3076
|
+
project_name: path7.basename(projectRoot),
|
|
2825
3077
|
has_error: false,
|
|
2826
3078
|
raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
|
|
2827
3079
|
vector: null,
|
|
@@ -2833,7 +3085,7 @@ function getChunkContext(filePath, lineNumber) {
|
|
|
2833
3085
|
try {
|
|
2834
3086
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
2835
3087
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
2836
|
-
const source =
|
|
3088
|
+
const source = readFileSync4(filePath, "utf8");
|
|
2837
3089
|
const lines = source.split("\n");
|
|
2838
3090
|
for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
|
|
2839
3091
|
const line = lines[i];
|
|
@@ -2852,7 +3104,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
|
|
|
2852
3104
|
const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
|
|
2853
3105
|
const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
|
|
2854
3106
|
const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
|
|
2855
|
-
const output =
|
|
3107
|
+
const output = execSync3(cmd, {
|
|
2856
3108
|
cwd: projectRoot,
|
|
2857
3109
|
encoding: "utf8",
|
|
2858
3110
|
timeout: 3e3,
|
|
@@ -2867,12 +3119,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
|
|
|
2867
3119
|
const matchCount = parseInt(line.slice(colonIdx + 1));
|
|
2868
3120
|
if (isNaN(matchCount) || matchCount === 0) continue;
|
|
2869
3121
|
try {
|
|
2870
|
-
const firstMatch =
|
|
3122
|
+
const firstMatch = execSync3(
|
|
2871
3123
|
`rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
|
|
2872
3124
|
{ cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
|
|
2873
3125
|
).trim();
|
|
2874
3126
|
const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
|
|
2875
|
-
const totalLines =
|
|
3127
|
+
const totalLines = execSync3(`wc -l < '${filePath}'`, {
|
|
2876
3128
|
cwd: projectRoot,
|
|
2877
3129
|
encoding: "utf8",
|
|
2878
3130
|
timeout: 1e3
|
|
@@ -2895,11 +3147,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
|
|
|
2895
3147
|
const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
|
|
2896
3148
|
const hits = [];
|
|
2897
3149
|
for (const filePath of files.slice(0, MAX_FILES)) {
|
|
2898
|
-
const absPath =
|
|
3150
|
+
const absPath = path7.join(projectRoot, filePath);
|
|
2899
3151
|
try {
|
|
2900
3152
|
const stat = statSync2(absPath);
|
|
2901
3153
|
if (stat.size > MAX_FILE_SIZE) continue;
|
|
2902
|
-
const content =
|
|
3154
|
+
const content = readFileSync4(absPath, "utf8");
|
|
2903
3155
|
const lines = content.split("\n");
|
|
2904
3156
|
const matches = content.match(regex);
|
|
2905
3157
|
if (!matches || matches.length === 0) continue;
|
|
@@ -2922,15 +3174,15 @@ function collectFiles(root, patterns) {
|
|
|
2922
3174
|
const files = [];
|
|
2923
3175
|
function walk(dir, relative) {
|
|
2924
3176
|
if (files.length >= MAX_FILES) return;
|
|
2925
|
-
const basename =
|
|
3177
|
+
const basename = path7.basename(dir);
|
|
2926
3178
|
if (EXCLUDE_DIRS.includes(basename)) return;
|
|
2927
3179
|
try {
|
|
2928
3180
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2929
3181
|
for (const entry of entries) {
|
|
2930
3182
|
if (files.length >= MAX_FILES) return;
|
|
2931
|
-
const rel =
|
|
3183
|
+
const rel = path7.join(relative, entry.name);
|
|
2932
3184
|
if (entry.isDirectory()) {
|
|
2933
|
-
walk(
|
|
3185
|
+
walk(path7.join(dir, entry.name), rel);
|
|
2934
3186
|
} else if (entry.isFile()) {
|
|
2935
3187
|
for (const pat of patterns) {
|
|
2936
3188
|
if (matchGlob(rel, pat)) {
|
|
@@ -2962,7 +3214,7 @@ function matchGlob(filePath, pattern) {
|
|
|
2962
3214
|
if (slashIdx !== -1) {
|
|
2963
3215
|
const dir = pattern.slice(0, slashIdx);
|
|
2964
3216
|
const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
|
|
2965
|
-
const fileDir =
|
|
3217
|
+
const fileDir = path7.dirname(filePath);
|
|
2966
3218
|
return fileDir === dir && filePath.endsWith(ext2);
|
|
2967
3219
|
}
|
|
2968
3220
|
const ext = pattern.replace("*", "");
|
|
@@ -2970,9 +3222,9 @@ function matchGlob(filePath, pattern) {
|
|
|
2970
3222
|
}
|
|
2971
3223
|
function buildSnippet(hit, projectRoot) {
|
|
2972
3224
|
try {
|
|
2973
|
-
const absPath =
|
|
2974
|
-
if (!
|
|
2975
|
-
const lines =
|
|
3225
|
+
const absPath = path7.join(projectRoot, hit.filePath);
|
|
3226
|
+
if (!existsSync6(absPath)) return hit.matchLine;
|
|
3227
|
+
const lines = readFileSync4(absPath, "utf8").split("\n");
|
|
2976
3228
|
const start = Math.max(0, hit.lineNumber - 3);
|
|
2977
3229
|
const end = Math.min(lines.length, hit.lineNumber + 2);
|
|
2978
3230
|
return lines.slice(start, end).join("\n").slice(0, 500);
|
|
@@ -3006,8 +3258,8 @@ __export(reranker_exports, {
|
|
|
3006
3258
|
rerank: () => rerank,
|
|
3007
3259
|
rerankWithScores: () => rerankWithScores
|
|
3008
3260
|
});
|
|
3009
|
-
import
|
|
3010
|
-
import { existsSync as
|
|
3261
|
+
import path8 from "path";
|
|
3262
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3011
3263
|
function resetIdleTimer() {
|
|
3012
3264
|
if (_idleTimer) clearTimeout(_idleTimer);
|
|
3013
3265
|
_idleTimer = setTimeout(() => {
|
|
@@ -3018,18 +3270,18 @@ function resetIdleTimer() {
|
|
|
3018
3270
|
}
|
|
3019
3271
|
}
|
|
3020
3272
|
function isRerankerAvailable() {
|
|
3021
|
-
return
|
|
3273
|
+
return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
|
|
3022
3274
|
}
|
|
3023
3275
|
function getRerankerModelPath() {
|
|
3024
|
-
return
|
|
3276
|
+
return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3025
3277
|
}
|
|
3026
3278
|
async function ensureLoaded() {
|
|
3027
3279
|
if (_rerankerContext) {
|
|
3028
3280
|
resetIdleTimer();
|
|
3029
3281
|
return;
|
|
3030
3282
|
}
|
|
3031
|
-
const modelPath =
|
|
3032
|
-
if (!
|
|
3283
|
+
const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
|
|
3284
|
+
if (!existsSync7(modelPath)) {
|
|
3033
3285
|
throw new Error(
|
|
3034
3286
|
`Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
|
|
3035
3287
|
);
|
|
@@ -3105,541 +3357,881 @@ var init_reranker = __esm({
|
|
|
3105
3357
|
}
|
|
3106
3358
|
});
|
|
3107
3359
|
|
|
3108
|
-
// src/lib/
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3360
|
+
// src/lib/hybrid-search.ts
|
|
3361
|
+
var hybrid_search_exports = {};
|
|
3362
|
+
__export(hybrid_search_exports, {
|
|
3363
|
+
estimateCardinality: () => estimateCardinality,
|
|
3364
|
+
hybridSearch: () => hybridSearch,
|
|
3365
|
+
lightweightSearch: () => lightweightSearch,
|
|
3366
|
+
rrfMerge: () => rrfMerge,
|
|
3367
|
+
rrfMergeMulti: () => rrfMergeMulti
|
|
3368
|
+
});
|
|
3369
|
+
async function hybridSearch(queryText, agentId, options) {
|
|
3370
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3371
|
+
const config2 = await loadConfig2();
|
|
3372
|
+
if (config2.searchMode === "fts") {
|
|
3373
|
+
return lightweightSearch(queryText, agentId, options);
|
|
3374
|
+
}
|
|
3375
|
+
const limit = options?.limit ?? 10;
|
|
3376
|
+
const trajectoryResults = await trajectoryBypass(queryText, agentId, options, limit);
|
|
3377
|
+
if (trajectoryResults !== null) return trajectoryResults;
|
|
3378
|
+
let effectiveQuery = queryText;
|
|
3379
|
+
let effectiveOptions = { ...options };
|
|
3380
|
+
let _isBroadQuery = false;
|
|
3381
|
+
if (config2.selfQueryRouter && process.env.ANTHROPIC_API_KEY) {
|
|
3114
3382
|
try {
|
|
3115
|
-
const
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
if (
|
|
3123
|
-
|
|
3124
|
-
return _cached2;
|
|
3383
|
+
const { routeQuery: routeQuery2 } = await Promise.resolve().then(() => (init_self_query_router(), self_query_router_exports));
|
|
3384
|
+
const routed = await routeQuery2(queryText, config2.selfQueryModel);
|
|
3385
|
+
effectiveQuery = routed.semanticQuery;
|
|
3386
|
+
_isBroadQuery = routed.isBroadQuery;
|
|
3387
|
+
if (routed.projectFilter && !effectiveOptions.projectName) {
|
|
3388
|
+
effectiveOptions.projectName = routed.projectFilter;
|
|
3389
|
+
}
|
|
3390
|
+
if (routed.timeFilter && !effectiveOptions.since) {
|
|
3391
|
+
effectiveOptions.since = routed.timeFilter;
|
|
3125
3392
|
}
|
|
3126
|
-
pid = parseInt(ppid, 10);
|
|
3127
|
-
if (pid <= 1) break;
|
|
3128
3393
|
} catch {
|
|
3129
|
-
break;
|
|
3130
3394
|
}
|
|
3131
3395
|
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
|
-
// src/adapters/claude/session-key.ts
|
|
3144
|
-
var init_session_key2 = __esm({
|
|
3145
|
-
"src/adapters/claude/session-key.ts"() {
|
|
3146
|
-
"use strict";
|
|
3147
|
-
init_session_key();
|
|
3148
|
-
}
|
|
3149
|
-
});
|
|
3150
|
-
|
|
3151
|
-
// src/adapters/claude/active-agent.ts
|
|
3152
|
-
var active_agent_exports = {};
|
|
3153
|
-
__export(active_agent_exports, {
|
|
3154
|
-
cleanupSessionMarkers: () => cleanupSessionMarkers,
|
|
3155
|
-
clearActiveAgent: () => clearActiveAgent,
|
|
3156
|
-
getActiveAgent: () => getActiveAgent,
|
|
3157
|
-
getAllActiveAgents: () => getAllActiveAgents,
|
|
3158
|
-
writeActiveAgent: () => writeActiveAgent
|
|
3159
|
-
});
|
|
3160
|
-
import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync3 } from "fs";
|
|
3161
|
-
import { execSync as execSync4 } from "child_process";
|
|
3162
|
-
import path8 from "path";
|
|
3163
|
-
function getMarkerPath() {
|
|
3164
|
-
return path8.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
3165
|
-
}
|
|
3166
|
-
function writeActiveAgent(agentId, agentRole) {
|
|
3167
|
-
try {
|
|
3168
|
-
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
3169
|
-
writeFileSync(
|
|
3170
|
-
getMarkerPath(),
|
|
3171
|
-
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
3396
|
+
const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
3397
|
+
const cardinality = await getMemoryCardinality2(agentId);
|
|
3398
|
+
const { rerankerAutoTrigger } = config2.scalingRoadmap ?? {};
|
|
3399
|
+
const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
|
|
3400
|
+
const useNarrowPath = cardinality < 1e4;
|
|
3401
|
+
const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
|
|
3402
|
+
const effectiveIsBroad = useBroadPath && !useNarrowPath;
|
|
3403
|
+
if (effectiveIsBroad !== _isBroadQuery) {
|
|
3404
|
+
process.stderr.write(
|
|
3405
|
+
`[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
|
|
3406
|
+
`
|
|
3172
3407
|
);
|
|
3173
|
-
} catch {
|
|
3174
3408
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3409
|
+
const broadFetchTopK = config2.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
|
|
3410
|
+
const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
|
|
3411
|
+
const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
|
|
3412
|
+
let queryVector = null;
|
|
3177
3413
|
try {
|
|
3178
|
-
|
|
3414
|
+
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
3415
|
+
queryVector = await embed2(effectiveQuery);
|
|
3179
3416
|
} catch {
|
|
3417
|
+
process.stderr.write("[hybrid-search] Embed daemon unavailable \u2014 FTS-only mode\n");
|
|
3180
3418
|
}
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
unlinkSync2(markerPath);
|
|
3193
|
-
} catch {
|
|
3194
|
-
}
|
|
3195
|
-
} else {
|
|
3196
|
-
return {
|
|
3197
|
-
agentId: data.agentId,
|
|
3198
|
-
agentRole: data.agentRole || "employee"
|
|
3199
|
-
};
|
|
3200
|
-
}
|
|
3201
|
-
} else {
|
|
3202
|
-
return {
|
|
3203
|
-
agentId: data.agentId,
|
|
3204
|
-
agentRole: data.agentRole || "employee"
|
|
3205
|
-
};
|
|
3419
|
+
let grepPromise = Promise.resolve([]);
|
|
3420
|
+
if (config2.fileGrepEnabled !== false) {
|
|
3421
|
+
try {
|
|
3422
|
+
const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
3423
|
+
const projectRoot = process.cwd();
|
|
3424
|
+
const projectName = getProjectName2(projectRoot);
|
|
3425
|
+
if (projectName && projectName !== "tmp") {
|
|
3426
|
+
const { grepProjectFiles: grepProjectFiles2 } = await Promise.resolve().then(() => (init_file_grep(), file_grep_exports));
|
|
3427
|
+
grepPromise = grepProjectFiles2(effectiveQuery, projectRoot, {
|
|
3428
|
+
maxResults: 10
|
|
3429
|
+
}).catch(() => []);
|
|
3206
3430
|
}
|
|
3431
|
+
} catch {
|
|
3207
3432
|
}
|
|
3208
|
-
} catch {
|
|
3209
3433
|
}
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
return { agentId: "exe", agentRole: "COO" };
|
|
3221
|
-
}
|
|
3222
|
-
} catch {
|
|
3434
|
+
const [ftsResults, vectorResults, grepResults] = await Promise.all([
|
|
3435
|
+
lightweightSearch(effectiveQuery, agentId, fetchOptions),
|
|
3436
|
+
queryVector ? searchMemories(queryVector, agentId, fetchOptions) : Promise.resolve([]),
|
|
3437
|
+
grepPromise
|
|
3438
|
+
]);
|
|
3439
|
+
const lists = [];
|
|
3440
|
+
const weights = [];
|
|
3441
|
+
if (ftsResults.length > 0) {
|
|
3442
|
+
lists.push(ftsResults);
|
|
3443
|
+
weights.push(1);
|
|
3223
3444
|
}
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3445
|
+
if (vectorResults.length > 0) {
|
|
3446
|
+
lists.push(vectorResults);
|
|
3447
|
+
weights.push(1);
|
|
3448
|
+
}
|
|
3449
|
+
if (grepResults.length > 0) {
|
|
3450
|
+
lists.push(grepResults);
|
|
3451
|
+
weights.push(0.5);
|
|
3452
|
+
}
|
|
3453
|
+
if (lists.length === 0) return [];
|
|
3454
|
+
if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
|
|
3455
|
+
const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
|
|
3456
|
+
const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
|
|
3457
|
+
const auto = config2.scalingRoadmap?.rerankerAutoTrigger ?? {
|
|
3458
|
+
enabled: config2.rerankerEnabled ?? true,
|
|
3459
|
+
broadQueryMinCardinality: 5e4,
|
|
3460
|
+
fetchTopK: 150,
|
|
3461
|
+
returnTopK: 5
|
|
3227
3462
|
};
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
const sessions = [];
|
|
3233
|
-
for (const file of files) {
|
|
3234
|
-
if (!file.startsWith("active-agent-") || !file.endsWith(".json")) continue;
|
|
3235
|
-
const key = file.slice("active-agent-".length, -".json".length);
|
|
3236
|
-
if (key === "undefined") continue;
|
|
3463
|
+
let rerankedAndBlended = null;
|
|
3464
|
+
if (effectiveIsBroad && auto.enabled) {
|
|
3465
|
+
const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
|
|
3466
|
+
if (cardinality2 > auto.broadQueryMinCardinality) {
|
|
3237
3467
|
try {
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
}
|
|
3248
|
-
continue;
|
|
3468
|
+
const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
|
|
3469
|
+
if (isRerankerAvailable2()) {
|
|
3470
|
+
const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
|
|
3471
|
+
if (reranked.length > 0) {
|
|
3472
|
+
rerankedAndBlended = rrfMergeMulti(
|
|
3473
|
+
[reranked],
|
|
3474
|
+
auto.returnTopK,
|
|
3475
|
+
RRF_K
|
|
3476
|
+
);
|
|
3249
3477
|
}
|
|
3250
3478
|
}
|
|
3251
|
-
sessions.push({
|
|
3252
|
-
agentId: data.agentId,
|
|
3253
|
-
agentRole: data.agentRole || "employee",
|
|
3254
|
-
startedAt: data.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3255
|
-
sessionKey: key
|
|
3256
|
-
});
|
|
3257
3479
|
} catch {
|
|
3258
3480
|
}
|
|
3259
3481
|
}
|
|
3260
|
-
return sessions;
|
|
3261
|
-
} catch {
|
|
3262
|
-
return [];
|
|
3263
3482
|
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3483
|
+
const finalResults = (rerankedAndBlended ?? merged).slice(
|
|
3484
|
+
0,
|
|
3485
|
+
rerankedAndBlended ? auto.returnTopK : limit
|
|
3486
|
+
);
|
|
3487
|
+
if (options?.includeSource && finalResults.length > 0) {
|
|
3488
|
+
await attachDocumentMetadata(finalResults);
|
|
3270
3489
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3490
|
+
if (finalResults.length > 0) {
|
|
3491
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3492
|
+
const ids = finalResults.map((r) => r.id);
|
|
3493
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
3494
|
+
try {
|
|
3495
|
+
const client = getClient();
|
|
3496
|
+
void client.execute({
|
|
3497
|
+
sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
|
|
3498
|
+
args: [now, ...ids]
|
|
3499
|
+
}).catch(() => {
|
|
3500
|
+
});
|
|
3501
|
+
} catch {
|
|
3502
|
+
}
|
|
3274
3503
|
}
|
|
3504
|
+
return finalResults;
|
|
3275
3505
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
// src/lib/employees.ts
|
|
3288
|
-
var employees_exports = {};
|
|
3289
|
-
__export(employees_exports, {
|
|
3290
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
3291
|
-
addEmployee: () => addEmployee,
|
|
3292
|
-
getEmployee: () => getEmployee,
|
|
3293
|
-
getEmployeeByRole: () => getEmployeeByRole,
|
|
3294
|
-
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
3295
|
-
hasRole: () => hasRole,
|
|
3296
|
-
isMultiInstance: () => isMultiInstance,
|
|
3297
|
-
loadEmployees: () => loadEmployees,
|
|
3298
|
-
loadEmployeesSync: () => loadEmployeesSync,
|
|
3299
|
-
normalizeRosterCase: () => normalizeRosterCase,
|
|
3300
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
3301
|
-
saveEmployees: () => saveEmployees,
|
|
3302
|
-
validateEmployeeName: () => validateEmployeeName
|
|
3303
|
-
});
|
|
3304
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
3305
|
-
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3306
|
-
import { execSync as execSync5 } from "child_process";
|
|
3307
|
-
import path9 from "path";
|
|
3308
|
-
import os3 from "os";
|
|
3309
|
-
function validateEmployeeName(name) {
|
|
3310
|
-
if (!name) {
|
|
3311
|
-
return { valid: false, error: "Name is required" };
|
|
3506
|
+
async function estimateCardinality(agentId, options) {
|
|
3507
|
+
const client = getClient();
|
|
3508
|
+
let sql = `SELECT COUNT(*) as cnt FROM memories
|
|
3509
|
+
WHERE agent_id = ?
|
|
3510
|
+
AND COALESCE(status, 'active') = 'active'
|
|
3511
|
+
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
3512
|
+
const args = [agentId];
|
|
3513
|
+
if (options?.projectName) {
|
|
3514
|
+
sql += ` AND project_name = ?`;
|
|
3515
|
+
args.push(options.projectName);
|
|
3312
3516
|
}
|
|
3313
|
-
if (
|
|
3314
|
-
|
|
3517
|
+
if (options?.toolName) {
|
|
3518
|
+
sql += ` AND tool_name = ?`;
|
|
3519
|
+
args.push(options.toolName);
|
|
3315
3520
|
}
|
|
3316
|
-
if (
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
3320
|
-
};
|
|
3521
|
+
if (options?.hasError !== void 0) {
|
|
3522
|
+
sql += ` AND has_error = ?`;
|
|
3523
|
+
args.push(options.hasError ? 1 : 0);
|
|
3321
3524
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
if (!existsSync7(employeesPath)) {
|
|
3326
|
-
return [];
|
|
3525
|
+
if (options?.since) {
|
|
3526
|
+
sql += ` AND timestamp >= ?`;
|
|
3527
|
+
args.push(options.since);
|
|
3327
3528
|
}
|
|
3328
|
-
const raw = await readFile3(employeesPath, "utf-8");
|
|
3329
3529
|
try {
|
|
3330
|
-
|
|
3530
|
+
const result = await client.execute({ sql, args });
|
|
3531
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
3331
3532
|
} catch {
|
|
3332
|
-
return
|
|
3533
|
+
return 0;
|
|
3333
3534
|
}
|
|
3334
3535
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
3536
|
+
function rrfMerge(listA, listB, limit, k = RRF_K) {
|
|
3537
|
+
return rrfMergeMulti([listA, listB], limit, k);
|
|
3338
3538
|
}
|
|
3339
|
-
function
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
3343
|
-
} catch {
|
|
3344
|
-
return [];
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
function getEmployee(employees, name) {
|
|
3348
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
3349
|
-
}
|
|
3350
|
-
function getEmployeeByRole(employees, role) {
|
|
3351
|
-
const lower = role.toLowerCase();
|
|
3352
|
-
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
3353
|
-
}
|
|
3354
|
-
function getEmployeeNamesByRole(employees, role) {
|
|
3355
|
-
const lower = role.toLowerCase();
|
|
3356
|
-
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
3539
|
+
function recencyScore(timestamp) {
|
|
3540
|
+
const daysSince = (Date.now() - new Date(timestamp).getTime()) / (1e3 * 60 * 60 * 24);
|
|
3541
|
+
return 1 / (1 + daysSince * 0.01);
|
|
3357
3542
|
}
|
|
3358
|
-
function
|
|
3359
|
-
|
|
3360
|
-
const emp = getEmployee(employees, agentName);
|
|
3361
|
-
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
3543
|
+
function normalizedImportance(importance) {
|
|
3544
|
+
return ((importance ?? 5) - 1) / 9;
|
|
3362
3545
|
}
|
|
3363
|
-
function
|
|
3364
|
-
const
|
|
3365
|
-
const
|
|
3366
|
-
|
|
3367
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
3546
|
+
function frecencyBoost(lastAccessed, timestamp) {
|
|
3547
|
+
const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
|
|
3548
|
+
const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
|
|
3549
|
+
return Math.exp(-0.01 * hoursSince);
|
|
3368
3550
|
}
|
|
3369
|
-
function
|
|
3370
|
-
const
|
|
3371
|
-
|
|
3372
|
-
|
|
3551
|
+
function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
3552
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3553
|
+
for (let listIdx = 0; listIdx < lists.length; listIdx++) {
|
|
3554
|
+
const list = lists[listIdx];
|
|
3555
|
+
const weight = weights?.[listIdx] ?? 1;
|
|
3556
|
+
for (let i = 0; i < list.length; i++) {
|
|
3557
|
+
const rec = list[i];
|
|
3558
|
+
const entry = scores.get(rec.id) ?? { rrfScore: 0, record: rec };
|
|
3559
|
+
entry.rrfScore += weight * (1 / (k + i + 1));
|
|
3560
|
+
scores.set(rec.id, entry);
|
|
3561
|
+
}
|
|
3373
3562
|
}
|
|
3374
|
-
|
|
3563
|
+
const entries = Array.from(scores.values()).map((e) => {
|
|
3564
|
+
const recency = recencyScore(e.record.timestamp);
|
|
3565
|
+
const importance = normalizedImportance(e.record.importance);
|
|
3566
|
+
const confidence = e.record.confidence ?? 0.7;
|
|
3567
|
+
const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
|
|
3568
|
+
const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
|
|
3569
|
+
const finalScore = baseScore * (1 + 0.3 * frecency);
|
|
3570
|
+
return { score: finalScore, record: e.record };
|
|
3571
|
+
});
|
|
3572
|
+
return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
|
|
3375
3573
|
}
|
|
3376
|
-
async function
|
|
3377
|
-
const
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
} catch {
|
|
3574
|
+
async function lightweightSearch(queryText, agentId, options) {
|
|
3575
|
+
const client = getClient();
|
|
3576
|
+
const limit = options?.limit ?? 5;
|
|
3577
|
+
const terms = queryText.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_]/g, "")).filter((t) => t.length >= 3);
|
|
3578
|
+
if (terms.length === 0) {
|
|
3579
|
+
return recentRecords(agentId, options, limit);
|
|
3580
|
+
}
|
|
3581
|
+
const prefixTerms = terms.map((t) => `${t}*`);
|
|
3582
|
+
const useAnd = terms.length >= 3;
|
|
3583
|
+
const matchExpr = useAnd ? prefixTerms.join(" AND ") : prefixTerms.join(" OR ");
|
|
3584
|
+
const results = await ftsQuery(client, matchExpr, agentId, options, limit);
|
|
3585
|
+
if (useAnd && results.length < limit) {
|
|
3586
|
+
const orExpr = prefixTerms.join(" OR ");
|
|
3587
|
+
const orResults = await ftsQuery(client, orExpr, agentId, options, limit);
|
|
3588
|
+
const seen = new Set(results.map((r) => r.id));
|
|
3589
|
+
for (const r of orResults) {
|
|
3590
|
+
if (!seen.has(r.id) && results.length < limit) {
|
|
3591
|
+
results.push(r);
|
|
3592
|
+
seen.add(r.id);
|
|
3398
3593
|
}
|
|
3399
3594
|
}
|
|
3400
3595
|
}
|
|
3401
|
-
if (
|
|
3402
|
-
await
|
|
3596
|
+
if (options?.includeSource && results.length > 0) {
|
|
3597
|
+
await attachDocumentMetadata(results);
|
|
3403
3598
|
}
|
|
3404
|
-
return
|
|
3599
|
+
return results;
|
|
3405
3600
|
}
|
|
3406
|
-
function
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3601
|
+
async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
3602
|
+
const statusFilter = options?.includeArchived ? "" : `
|
|
3603
|
+
AND COALESCE(m.status, 'active') = 'active'`;
|
|
3604
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
3605
|
+
AND (m.draft = 0 OR m.draft IS NULL)`;
|
|
3606
|
+
let sql = `SELECT m.id, m.agent_id, m.agent_role, m.session_id, m.timestamp,
|
|
3607
|
+
m.tool_name, m.project_name,
|
|
3608
|
+
m.has_error, m.raw_text, m.vector, m.task_id,
|
|
3609
|
+
m.importance, m.status, m.confidence, m.last_accessed,
|
|
3610
|
+
m.workspace_id, m.document_id, m.user_id,
|
|
3611
|
+
m.char_offset, m.page_number,
|
|
3612
|
+
m.source_path, m.source_type
|
|
3613
|
+
FROM memories m
|
|
3614
|
+
JOIN memories_fts fts ON m.rowid = fts.rowid
|
|
3615
|
+
WHERE memories_fts MATCH ?
|
|
3616
|
+
AND m.agent_id = ?${statusFilter}${draftFilter}
|
|
3617
|
+
AND COALESCE(m.confidence, 0.7) >= 0.3`;
|
|
3618
|
+
const args = [matchExpr, agentId];
|
|
3619
|
+
const scope = buildWikiScopeFilter(options, "m.");
|
|
3620
|
+
sql += scope.clause;
|
|
3621
|
+
args.push(...scope.args);
|
|
3622
|
+
if (options?.projectName) {
|
|
3623
|
+
sql += ` AND m.project_name = ?`;
|
|
3624
|
+
args.push(options.projectName);
|
|
3411
3625
|
}
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
const skipped = [];
|
|
3416
|
-
const errors = [];
|
|
3417
|
-
const exeBinPath = findExeBin();
|
|
3418
|
-
if (!exeBinPath) {
|
|
3419
|
-
errors.push("Could not find 'exe-os' in PATH");
|
|
3420
|
-
return { created, skipped, errors };
|
|
3626
|
+
if (options?.toolName) {
|
|
3627
|
+
sql += ` AND m.tool_name = ?`;
|
|
3628
|
+
args.push(options.toolName);
|
|
3421
3629
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
target = readlinkSync(exeBinPath);
|
|
3426
|
-
} catch {
|
|
3427
|
-
errors.push("Could not read 'exe' symlink");
|
|
3428
|
-
return { created, skipped, errors };
|
|
3630
|
+
if (options?.hasError !== void 0) {
|
|
3631
|
+
sql += ` AND m.has_error = ?`;
|
|
3632
|
+
args.push(options.hasError ? 1 : 0);
|
|
3429
3633
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
if (existsSync7(linkPath)) {
|
|
3434
|
-
skipped.push(linkName);
|
|
3435
|
-
continue;
|
|
3436
|
-
}
|
|
3437
|
-
try {
|
|
3438
|
-
symlinkSync(target, linkPath);
|
|
3439
|
-
created.push(linkName);
|
|
3440
|
-
} catch (err) {
|
|
3441
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3442
|
-
}
|
|
3634
|
+
if (options?.since) {
|
|
3635
|
+
sql += ` AND m.timestamp >= ?`;
|
|
3636
|
+
args.push(options.since);
|
|
3443
3637
|
}
|
|
3444
|
-
|
|
3638
|
+
if (options?.memoryType) {
|
|
3639
|
+
sql += ` AND m.memory_type = ?`;
|
|
3640
|
+
args.push(options.memoryType);
|
|
3641
|
+
}
|
|
3642
|
+
sql += ` ORDER BY rank LIMIT ?`;
|
|
3643
|
+
args.push(limit);
|
|
3644
|
+
const result = await client.execute({ sql, args });
|
|
3645
|
+
return result.rows.map((row) => ({
|
|
3646
|
+
id: row.id,
|
|
3647
|
+
agent_id: row.agent_id,
|
|
3648
|
+
agent_role: row.agent_role,
|
|
3649
|
+
session_id: row.session_id,
|
|
3650
|
+
timestamp: row.timestamp,
|
|
3651
|
+
tool_name: row.tool_name,
|
|
3652
|
+
project_name: row.project_name,
|
|
3653
|
+
has_error: row.has_error === 1,
|
|
3654
|
+
raw_text: row.raw_text,
|
|
3655
|
+
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
3656
|
+
task_id: row.task_id ?? null,
|
|
3657
|
+
importance: row.importance ?? 5,
|
|
3658
|
+
status: row.status ?? "active",
|
|
3659
|
+
confidence: row.confidence ?? 0.7,
|
|
3660
|
+
last_accessed: row.last_accessed ?? row.timestamp,
|
|
3661
|
+
workspace_id: row.workspace_id ?? null,
|
|
3662
|
+
document_id: row.document_id ?? null,
|
|
3663
|
+
user_id: row.user_id ?? null,
|
|
3664
|
+
char_offset: row.char_offset ?? null,
|
|
3665
|
+
page_number: row.page_number ?? null,
|
|
3666
|
+
source_path: row.source_path ?? null,
|
|
3667
|
+
source_type: row.source_type ?? null
|
|
3668
|
+
}));
|
|
3445
3669
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
"
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3670
|
+
async function recentRecords(agentId, options, limit) {
|
|
3671
|
+
const client = getClient();
|
|
3672
|
+
const statusFilter = options?.includeArchived ? "" : `
|
|
3673
|
+
AND COALESCE(status, 'active') = 'active'`;
|
|
3674
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
3675
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
3676
|
+
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
3677
|
+
tool_name, project_name,
|
|
3678
|
+
has_error, raw_text, vector, task_id,
|
|
3679
|
+
importance, status, confidence, last_accessed,
|
|
3680
|
+
workspace_id, document_id, user_id,
|
|
3681
|
+
char_offset, page_number,
|
|
3682
|
+
source_path, source_type
|
|
3683
|
+
FROM memories
|
|
3684
|
+
WHERE agent_id = ?${statusFilter}${draftFilter}
|
|
3685
|
+
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
3686
|
+
const args = [agentId];
|
|
3687
|
+
const scope = buildWikiScopeFilter(options, "");
|
|
3688
|
+
sql += scope.clause;
|
|
3689
|
+
args.push(...scope.args);
|
|
3690
|
+
if (options?.projectName) {
|
|
3691
|
+
sql += ` AND project_name = ?`;
|
|
3692
|
+
args.push(options.projectName);
|
|
3453
3693
|
}
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
var license_exports = {};
|
|
3458
|
-
__export(license_exports, {
|
|
3459
|
-
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
3460
|
-
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
3461
|
-
assertVpsLicense: () => assertVpsLicense,
|
|
3462
|
-
checkLicense: () => checkLicense,
|
|
3463
|
-
getCachedLicense: () => getCachedLicense,
|
|
3464
|
-
isFeatureAllowed: () => isFeatureAllowed,
|
|
3465
|
-
loadDeviceId: () => loadDeviceId,
|
|
3466
|
-
loadLicense: () => loadLicense,
|
|
3467
|
-
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
3468
|
-
saveLicense: () => saveLicense,
|
|
3469
|
-
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
3470
|
-
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
3471
|
-
validateLicense: () => validateLicense
|
|
3472
|
-
});
|
|
3473
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
3474
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
3475
|
-
import path10 from "path";
|
|
3476
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
3477
|
-
async function fetchRetry(url, init) {
|
|
3478
|
-
try {
|
|
3479
|
-
return await fetch(url, init);
|
|
3480
|
-
} catch {
|
|
3481
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
3482
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
3694
|
+
if (options?.toolName) {
|
|
3695
|
+
sql += ` AND tool_name = ?`;
|
|
3696
|
+
args.push(options.toolName);
|
|
3483
3697
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
try {
|
|
3488
|
-
if (existsSync8(deviceJsonPath)) {
|
|
3489
|
-
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
3490
|
-
if (data.deviceId) return data.deviceId;
|
|
3491
|
-
}
|
|
3492
|
-
} catch {
|
|
3698
|
+
if (options?.hasError !== void 0) {
|
|
3699
|
+
sql += ` AND has_error = ?`;
|
|
3700
|
+
args.push(options.hasError ? 1 : 0);
|
|
3493
3701
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
if (id2) return id2;
|
|
3498
|
-
}
|
|
3499
|
-
} catch {
|
|
3702
|
+
if (options?.since) {
|
|
3703
|
+
sql += ` AND timestamp >= ?`;
|
|
3704
|
+
args.push(options.since);
|
|
3500
3705
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3706
|
+
if (options?.memoryType) {
|
|
3707
|
+
sql += ` AND memory_type = ?`;
|
|
3708
|
+
args.push(options.memoryType);
|
|
3709
|
+
}
|
|
3710
|
+
sql += ` ORDER BY timestamp DESC LIMIT ?`;
|
|
3711
|
+
args.push(limit);
|
|
3712
|
+
const result = await client.execute({ sql, args });
|
|
3713
|
+
return result.rows.map((row) => ({
|
|
3714
|
+
id: row.id,
|
|
3715
|
+
agent_id: row.agent_id,
|
|
3716
|
+
agent_role: row.agent_role,
|
|
3717
|
+
session_id: row.session_id,
|
|
3718
|
+
timestamp: row.timestamp,
|
|
3719
|
+
tool_name: row.tool_name,
|
|
3720
|
+
project_name: row.project_name,
|
|
3721
|
+
has_error: row.has_error === 1,
|
|
3722
|
+
raw_text: row.raw_text,
|
|
3723
|
+
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
3724
|
+
task_id: row.task_id ?? null,
|
|
3725
|
+
importance: row.importance ?? 5,
|
|
3726
|
+
status: row.status ?? "active",
|
|
3727
|
+
confidence: row.confidence ?? 0.7,
|
|
3728
|
+
last_accessed: row.last_accessed ?? row.timestamp,
|
|
3729
|
+
workspace_id: row.workspace_id ?? null,
|
|
3730
|
+
document_id: row.document_id ?? null,
|
|
3731
|
+
user_id: row.user_id ?? null,
|
|
3732
|
+
char_offset: row.char_offset ?? null,
|
|
3733
|
+
page_number: row.page_number ?? null,
|
|
3734
|
+
source_path: row.source_path ?? null,
|
|
3735
|
+
source_type: row.source_type ?? null
|
|
3736
|
+
}));
|
|
3505
3737
|
}
|
|
3506
|
-
function
|
|
3738
|
+
function detectToolQuery(query) {
|
|
3739
|
+
const lower = query.toLowerCase().trim();
|
|
3740
|
+
for (const tool of KNOWN_TOOLS) {
|
|
3741
|
+
if (lower === tool.toLowerCase()) return tool;
|
|
3742
|
+
if (lower.startsWith(tool.toLowerCase() + " ")) return tool;
|
|
3743
|
+
if (lower.includes(`tool:${tool.toLowerCase()}`)) return tool;
|
|
3744
|
+
}
|
|
3745
|
+
return null;
|
|
3746
|
+
}
|
|
3747
|
+
async function trajectoryBypass(queryText, agentId, options, limit) {
|
|
3748
|
+
const toolName = detectToolQuery(queryText);
|
|
3749
|
+
if (!toolName) return null;
|
|
3507
3750
|
try {
|
|
3508
|
-
|
|
3509
|
-
|
|
3751
|
+
const client = getClient();
|
|
3752
|
+
const statusFilter = options?.includeArchived ? "" : `
|
|
3753
|
+
AND COALESCE(status, 'active') = 'active'`;
|
|
3754
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
3755
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
3756
|
+
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
3757
|
+
tool_name, project_name,
|
|
3758
|
+
has_error, raw_text, vector, task_id,
|
|
3759
|
+
importance, status, confidence, last_accessed,
|
|
3760
|
+
workspace_id, document_id, user_id,
|
|
3761
|
+
char_offset, page_number,
|
|
3762
|
+
source_path, source_type
|
|
3763
|
+
FROM memories
|
|
3764
|
+
WHERE trajectory IS NOT NULL
|
|
3765
|
+
AND json_extract(trajectory, '$.tool') = ?
|
|
3766
|
+
AND agent_id = ?${statusFilter}${draftFilter}`;
|
|
3767
|
+
const args = [toolName, agentId];
|
|
3768
|
+
if (options?.projectName) {
|
|
3769
|
+
sql += ` AND project_name = ?`;
|
|
3770
|
+
args.push(options.projectName);
|
|
3771
|
+
}
|
|
3772
|
+
if (options?.since) {
|
|
3773
|
+
sql += ` AND timestamp >= ?`;
|
|
3774
|
+
args.push(options.since);
|
|
3775
|
+
}
|
|
3776
|
+
sql += ` ORDER BY timestamp DESC LIMIT ?`;
|
|
3777
|
+
args.push(limit);
|
|
3778
|
+
const result = await client.execute({ sql, args });
|
|
3779
|
+
if (result.rows.length < 3) return null;
|
|
3780
|
+
return result.rows.map((row) => ({
|
|
3781
|
+
id: row.id,
|
|
3782
|
+
agent_id: row.agent_id,
|
|
3783
|
+
agent_role: row.agent_role,
|
|
3784
|
+
session_id: row.session_id,
|
|
3785
|
+
timestamp: row.timestamp,
|
|
3786
|
+
tool_name: row.tool_name,
|
|
3787
|
+
project_name: row.project_name,
|
|
3788
|
+
has_error: row.has_error === 1,
|
|
3789
|
+
raw_text: row.raw_text,
|
|
3790
|
+
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
3791
|
+
task_id: row.task_id ?? null,
|
|
3792
|
+
importance: row.importance ?? 5,
|
|
3793
|
+
status: row.status ?? "active",
|
|
3794
|
+
confidence: row.confidence ?? 0.7,
|
|
3795
|
+
last_accessed: row.last_accessed ?? row.timestamp,
|
|
3796
|
+
workspace_id: row.workspace_id ?? null,
|
|
3797
|
+
document_id: row.document_id ?? null,
|
|
3798
|
+
user_id: row.user_id ?? null,
|
|
3799
|
+
char_offset: row.char_offset ?? null,
|
|
3800
|
+
page_number: row.page_number ?? null,
|
|
3801
|
+
source_path: row.source_path ?? null,
|
|
3802
|
+
source_type: row.source_type ?? null
|
|
3803
|
+
}));
|
|
3510
3804
|
} catch {
|
|
3511
3805
|
return null;
|
|
3512
3806
|
}
|
|
3513
3807
|
}
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3808
|
+
var RRF_K, KNOWN_TOOLS;
|
|
3809
|
+
var init_hybrid_search = __esm({
|
|
3810
|
+
"src/lib/hybrid-search.ts"() {
|
|
3811
|
+
"use strict";
|
|
3812
|
+
init_store();
|
|
3813
|
+
init_database();
|
|
3814
|
+
RRF_K = 60;
|
|
3815
|
+
KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
3816
|
+
"Bash",
|
|
3817
|
+
"Read",
|
|
3818
|
+
"Write",
|
|
3819
|
+
"Edit",
|
|
3820
|
+
"Grep",
|
|
3821
|
+
"Glob",
|
|
3822
|
+
"Agent",
|
|
3823
|
+
"Skill",
|
|
3824
|
+
"WebSearch",
|
|
3825
|
+
"WebFetch"
|
|
3826
|
+
]);
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
|
|
3830
|
+
// src/lib/session-key.ts
|
|
3831
|
+
import { execSync as execSync4 } from "child_process";
|
|
3832
|
+
function getSessionKey() {
|
|
3833
|
+
if (_cached2) return _cached2;
|
|
3834
|
+
let pid = process.ppid;
|
|
3835
|
+
for (let i = 0; i < 10; i++) {
|
|
3836
|
+
try {
|
|
3837
|
+
const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
|
|
3838
|
+
encoding: "utf8",
|
|
3839
|
+
timeout: 2e3
|
|
3840
|
+
}).trim();
|
|
3841
|
+
const match = info.match(/^\s*(\d+)\s+(.+)$/);
|
|
3842
|
+
if (!match) break;
|
|
3843
|
+
const [, ppid, cmd] = match;
|
|
3844
|
+
if (cmd === "claude" || cmd.endsWith("/claude")) {
|
|
3845
|
+
_cached2 = String(pid);
|
|
3846
|
+
return _cached2;
|
|
3847
|
+
}
|
|
3848
|
+
pid = parseInt(ppid, 10);
|
|
3849
|
+
if (pid <= 1) break;
|
|
3850
|
+
} catch {
|
|
3851
|
+
break;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
_cached2 = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
|
|
3855
|
+
return _cached2;
|
|
3517
3856
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3532
|
-
deviceLimit: limits.devices,
|
|
3533
|
-
employeeLimit: limits.employees,
|
|
3534
|
-
memoryLimit: limits.memories
|
|
3535
|
-
};
|
|
3536
|
-
} catch {
|
|
3537
|
-
return null;
|
|
3857
|
+
var _cached2;
|
|
3858
|
+
var init_session_key = __esm({
|
|
3859
|
+
"src/lib/session-key.ts"() {
|
|
3860
|
+
"use strict";
|
|
3861
|
+
_cached2 = null;
|
|
3862
|
+
}
|
|
3863
|
+
});
|
|
3864
|
+
|
|
3865
|
+
// src/adapters/claude/session-key.ts
|
|
3866
|
+
var init_session_key2 = __esm({
|
|
3867
|
+
"src/adapters/claude/session-key.ts"() {
|
|
3868
|
+
"use strict";
|
|
3869
|
+
init_session_key();
|
|
3538
3870
|
}
|
|
3871
|
+
});
|
|
3872
|
+
|
|
3873
|
+
// src/adapters/claude/active-agent.ts
|
|
3874
|
+
var active_agent_exports = {};
|
|
3875
|
+
__export(active_agent_exports, {
|
|
3876
|
+
cleanupSessionMarkers: () => cleanupSessionMarkers,
|
|
3877
|
+
clearActiveAgent: () => clearActiveAgent,
|
|
3878
|
+
getActiveAgent: () => getActiveAgent,
|
|
3879
|
+
getAllActiveAgents: () => getAllActiveAgents,
|
|
3880
|
+
resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
|
|
3881
|
+
writeActiveAgent: () => writeActiveAgent
|
|
3882
|
+
});
|
|
3883
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
|
|
3884
|
+
import { execSync as execSync5 } from "child_process";
|
|
3885
|
+
import path9 from "path";
|
|
3886
|
+
function isNameWithOptionalInstance(candidate, baseName) {
|
|
3887
|
+
if (candidate === baseName) return true;
|
|
3888
|
+
if (!candidate.startsWith(baseName)) return false;
|
|
3889
|
+
return /^\d+$/.test(candidate.slice(baseName.length));
|
|
3539
3890
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
} catch {
|
|
3547
|
-
return null;
|
|
3891
|
+
function resolveEmployeeFromSessionPrefix(prefix, employees) {
|
|
3892
|
+
const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
|
|
3893
|
+
for (const employee of sorted) {
|
|
3894
|
+
if (isNameWithOptionalInstance(prefix, employee.name)) {
|
|
3895
|
+
return { agentId: employee.name, agentRole: employee.role };
|
|
3896
|
+
}
|
|
3548
3897
|
}
|
|
3898
|
+
return null;
|
|
3549
3899
|
}
|
|
3550
|
-
function
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3900
|
+
function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
3901
|
+
const employees = loadEmployeesSync();
|
|
3902
|
+
const coordinator = getCoordinatorEmployee(employees);
|
|
3903
|
+
const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
3904
|
+
if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
|
|
3905
|
+
return {
|
|
3906
|
+
agentId: coordinatorName,
|
|
3907
|
+
agentRole: coordinator?.role ?? "COO"
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
|
|
3911
|
+
return {
|
|
3912
|
+
agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
3913
|
+
agentRole: coordinator?.role ?? "COO"
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
if (sessionName.includes("-")) {
|
|
3917
|
+
const prefix = sessionName.split("-")[0] ?? "";
|
|
3918
|
+
const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
|
|
3919
|
+
if (employee) return employee;
|
|
3920
|
+
const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
|
|
3921
|
+
if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
|
|
3922
|
+
const emp = getEmployee(employees, legacy[1]);
|
|
3923
|
+
return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
|
|
3924
|
+
}
|
|
3557
3925
|
}
|
|
3926
|
+
return null;
|
|
3558
3927
|
}
|
|
3559
|
-
function
|
|
3928
|
+
function getMarkerPath() {
|
|
3929
|
+
return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
3930
|
+
}
|
|
3931
|
+
function writeActiveAgent(agentId, agentRole) {
|
|
3560
3932
|
try {
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3566
|
-
const plan = payload.plan ?? "free";
|
|
3567
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3568
|
-
process.stderr.write(
|
|
3569
|
-
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
3570
|
-
`
|
|
3933
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
3934
|
+
writeFileSync2(
|
|
3935
|
+
getMarkerPath(),
|
|
3936
|
+
JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
3571
3937
|
);
|
|
3572
|
-
return {
|
|
3573
|
-
valid: true,
|
|
3574
|
-
plan,
|
|
3575
|
-
email: payload.sub ?? "",
|
|
3576
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3577
|
-
deviceLimit: limits.devices,
|
|
3578
|
-
employeeLimit: limits.employees,
|
|
3579
|
-
memoryLimit: limits.memories
|
|
3580
|
-
};
|
|
3581
3938
|
} catch {
|
|
3582
|
-
return null;
|
|
3583
3939
|
}
|
|
3584
3940
|
}
|
|
3585
|
-
function
|
|
3941
|
+
function clearActiveAgent() {
|
|
3586
3942
|
try {
|
|
3587
|
-
|
|
3943
|
+
unlinkSync3(getMarkerPath());
|
|
3588
3944
|
} catch {
|
|
3589
3945
|
}
|
|
3590
3946
|
}
|
|
3591
|
-
|
|
3592
|
-
const did = deviceId ?? loadDeviceId();
|
|
3947
|
+
function getActiveAgent() {
|
|
3593
3948
|
try {
|
|
3594
|
-
const
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3949
|
+
const markerPath = getMarkerPath();
|
|
3950
|
+
const raw = readFileSync5(markerPath, "utf8");
|
|
3951
|
+
const data = JSON.parse(raw);
|
|
3952
|
+
if (data.agentId) {
|
|
3953
|
+
if (data.startedAt) {
|
|
3954
|
+
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
3955
|
+
if (age > STALE_MS) {
|
|
3956
|
+
try {
|
|
3957
|
+
unlinkSync3(markerPath);
|
|
3958
|
+
} catch {
|
|
3959
|
+
}
|
|
3960
|
+
} else {
|
|
3961
|
+
return {
|
|
3962
|
+
agentId: data.agentId,
|
|
3963
|
+
agentRole: data.agentRole || "employee"
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
} else {
|
|
3967
|
+
return {
|
|
3968
|
+
agentId: data.agentId,
|
|
3969
|
+
agentRole: data.agentRole || "employee"
|
|
3970
|
+
};
|
|
3613
3971
|
}
|
|
3614
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
3615
|
-
return {
|
|
3616
|
-
valid: data.valid,
|
|
3617
|
-
plan: data.plan,
|
|
3618
|
-
email: data.email,
|
|
3619
|
-
expiresAt: data.expiresAt,
|
|
3620
|
-
deviceLimit: limits.devices,
|
|
3621
|
-
employeeLimit: limits.employees,
|
|
3622
|
-
memoryLimit: limits.memories
|
|
3623
|
-
};
|
|
3624
3972
|
}
|
|
3625
|
-
const cached = await getCachedLicense();
|
|
3626
|
-
if (cached) return cached;
|
|
3627
|
-
const raw = getRawCachedPlan();
|
|
3628
|
-
if (raw) return raw;
|
|
3629
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
3630
3973
|
} catch {
|
|
3631
|
-
const cached = await getCachedLicense();
|
|
3632
|
-
if (cached) return cached;
|
|
3633
|
-
const rawFallback = getRawCachedPlan();
|
|
3634
|
-
if (rawFallback) return rawFallback;
|
|
3635
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
3636
3974
|
}
|
|
3637
|
-
}
|
|
3638
|
-
function getCacheAgeMs() {
|
|
3639
3975
|
try {
|
|
3640
|
-
const
|
|
3641
|
-
|
|
3642
|
-
|
|
3976
|
+
const sessionName = execSync5(
|
|
3977
|
+
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
3978
|
+
{ encoding: "utf8", timeout: 2e3 }
|
|
3979
|
+
).trim();
|
|
3980
|
+
const resolved = resolveActiveAgentFromTmuxSession(sessionName);
|
|
3981
|
+
if (resolved) return resolved;
|
|
3982
|
+
} catch {
|
|
3983
|
+
}
|
|
3984
|
+
return {
|
|
3985
|
+
agentId: process.env.AGENT_ID || "default",
|
|
3986
|
+
agentRole: process.env.AGENT_ROLE || "employee"
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
function getAllActiveAgents() {
|
|
3990
|
+
try {
|
|
3991
|
+
const files = readdirSync3(CACHE_DIR);
|
|
3992
|
+
const sessions = [];
|
|
3993
|
+
for (const file of files) {
|
|
3994
|
+
if (!file.startsWith("active-agent-") || !file.endsWith(".json")) continue;
|
|
3995
|
+
const key = file.slice("active-agent-".length, -".json".length);
|
|
3996
|
+
if (key === "undefined") continue;
|
|
3997
|
+
try {
|
|
3998
|
+
const raw = readFileSync5(path9.join(CACHE_DIR, file), "utf8");
|
|
3999
|
+
const data = JSON.parse(raw);
|
|
4000
|
+
if (!data.agentId) continue;
|
|
4001
|
+
if (data.startedAt) {
|
|
4002
|
+
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
4003
|
+
if (age > STALE_MS) {
|
|
4004
|
+
try {
|
|
4005
|
+
unlinkSync3(path9.join(CACHE_DIR, file));
|
|
4006
|
+
} catch {
|
|
4007
|
+
}
|
|
4008
|
+
continue;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
sessions.push({
|
|
4012
|
+
agentId: data.agentId,
|
|
4013
|
+
agentRole: data.agentRole || "employee",
|
|
4014
|
+
startedAt: data.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
4015
|
+
sessionKey: key
|
|
4016
|
+
});
|
|
4017
|
+
} catch {
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return sessions;
|
|
4021
|
+
} catch {
|
|
4022
|
+
return [];
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
function cleanupSessionMarkers() {
|
|
4026
|
+
const key = getSessionKey();
|
|
4027
|
+
try {
|
|
4028
|
+
unlinkSync3(path9.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
4029
|
+
} catch {
|
|
4030
|
+
}
|
|
4031
|
+
try {
|
|
4032
|
+
unlinkSync3(path9.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
4033
|
+
} catch {
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
var CACHE_DIR, STALE_MS;
|
|
4037
|
+
var init_active_agent = __esm({
|
|
4038
|
+
"src/adapters/claude/active-agent.ts"() {
|
|
4039
|
+
"use strict";
|
|
4040
|
+
init_config();
|
|
4041
|
+
init_session_key2();
|
|
4042
|
+
init_employees();
|
|
4043
|
+
CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
|
|
4044
|
+
STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4045
|
+
}
|
|
4046
|
+
});
|
|
4047
|
+
|
|
4048
|
+
// src/lib/license.ts
|
|
4049
|
+
var license_exports = {};
|
|
4050
|
+
__export(license_exports, {
|
|
4051
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4052
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4053
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4054
|
+
checkLicense: () => checkLicense,
|
|
4055
|
+
getCachedLicense: () => getCachedLicense,
|
|
4056
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4057
|
+
loadDeviceId: () => loadDeviceId,
|
|
4058
|
+
loadLicense: () => loadLicense,
|
|
4059
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4060
|
+
saveLicense: () => saveLicense,
|
|
4061
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
4062
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4063
|
+
validateLicense: () => validateLicense
|
|
4064
|
+
});
|
|
4065
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
4066
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4067
|
+
import path10 from "path";
|
|
4068
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
4069
|
+
async function fetchRetry(url, init) {
|
|
4070
|
+
try {
|
|
4071
|
+
return await fetch(url, init);
|
|
4072
|
+
} catch {
|
|
4073
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
4074
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
function loadDeviceId() {
|
|
4078
|
+
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
4079
|
+
try {
|
|
4080
|
+
if (existsSync8(deviceJsonPath)) {
|
|
4081
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
4082
|
+
if (data.deviceId) return data.deviceId;
|
|
4083
|
+
}
|
|
4084
|
+
} catch {
|
|
4085
|
+
}
|
|
4086
|
+
try {
|
|
4087
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
4088
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
4089
|
+
if (id2) return id2;
|
|
4090
|
+
}
|
|
4091
|
+
} catch {
|
|
4092
|
+
}
|
|
4093
|
+
const id = randomUUID3();
|
|
4094
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
4095
|
+
writeFileSync3(DEVICE_ID_PATH, id, "utf8");
|
|
4096
|
+
return id;
|
|
4097
|
+
}
|
|
4098
|
+
function loadLicense() {
|
|
4099
|
+
try {
|
|
4100
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
4101
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
4102
|
+
} catch {
|
|
4103
|
+
return null;
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
function saveLicense(apiKey) {
|
|
4107
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
4108
|
+
writeFileSync3(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4109
|
+
}
|
|
4110
|
+
async function verifyLicenseJwt(token) {
|
|
4111
|
+
try {
|
|
4112
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4113
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4114
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4115
|
+
});
|
|
4116
|
+
const plan = payload.plan ?? "free";
|
|
4117
|
+
const email = payload.sub ?? "";
|
|
4118
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4119
|
+
return {
|
|
4120
|
+
valid: true,
|
|
4121
|
+
plan,
|
|
4122
|
+
email,
|
|
4123
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4124
|
+
deviceLimit: limits.devices,
|
|
4125
|
+
employeeLimit: limits.employees,
|
|
4126
|
+
memoryLimit: limits.memories
|
|
4127
|
+
};
|
|
4128
|
+
} catch {
|
|
4129
|
+
return null;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
async function getCachedLicense() {
|
|
4133
|
+
try {
|
|
4134
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
4135
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4136
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4137
|
+
return await verifyLicenseJwt(raw.token);
|
|
4138
|
+
} catch {
|
|
4139
|
+
return null;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
function readCachedToken() {
|
|
4143
|
+
try {
|
|
4144
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
4145
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
4146
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4147
|
+
} catch {
|
|
4148
|
+
return null;
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
function getRawCachedPlan() {
|
|
4152
|
+
try {
|
|
4153
|
+
const token = readCachedToken();
|
|
4154
|
+
if (!token) return null;
|
|
4155
|
+
const parts = token.split(".");
|
|
4156
|
+
if (parts.length !== 3) return null;
|
|
4157
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4158
|
+
const plan = payload.plan ?? "free";
|
|
4159
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4160
|
+
process.stderr.write(
|
|
4161
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4162
|
+
`
|
|
4163
|
+
);
|
|
4164
|
+
return {
|
|
4165
|
+
valid: true,
|
|
4166
|
+
plan,
|
|
4167
|
+
email: payload.sub ?? "",
|
|
4168
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4169
|
+
deviceLimit: limits.devices,
|
|
4170
|
+
employeeLimit: limits.employees,
|
|
4171
|
+
memoryLimit: limits.memories
|
|
4172
|
+
};
|
|
4173
|
+
} catch {
|
|
4174
|
+
return null;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
function cacheResponse(token) {
|
|
4178
|
+
try {
|
|
4179
|
+
writeFileSync3(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4180
|
+
} catch {
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4184
|
+
const did = deviceId ?? loadDeviceId();
|
|
4185
|
+
try {
|
|
4186
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4187
|
+
method: "POST",
|
|
4188
|
+
headers: { "Content-Type": "application/json" },
|
|
4189
|
+
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
4190
|
+
signal: AbortSignal.timeout(1e4)
|
|
4191
|
+
});
|
|
4192
|
+
if (res.ok) {
|
|
4193
|
+
const data = await res.json();
|
|
4194
|
+
if (data.error === "device_limit_exceeded") {
|
|
4195
|
+
const cached2 = await getCachedLicense();
|
|
4196
|
+
if (cached2) return cached2;
|
|
4197
|
+
const raw2 = getRawCachedPlan();
|
|
4198
|
+
if (raw2) return { ...raw2, valid: false };
|
|
4199
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4200
|
+
}
|
|
4201
|
+
if (data.token) {
|
|
4202
|
+
cacheResponse(data.token);
|
|
4203
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4204
|
+
if (verified) return verified;
|
|
4205
|
+
}
|
|
4206
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4207
|
+
return {
|
|
4208
|
+
valid: data.valid,
|
|
4209
|
+
plan: data.plan,
|
|
4210
|
+
email: data.email,
|
|
4211
|
+
expiresAt: data.expiresAt,
|
|
4212
|
+
deviceLimit: limits.devices,
|
|
4213
|
+
employeeLimit: limits.employees,
|
|
4214
|
+
memoryLimit: limits.memories
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
const cached = await getCachedLicense();
|
|
4218
|
+
if (cached) return cached;
|
|
4219
|
+
const raw = getRawCachedPlan();
|
|
4220
|
+
if (raw) return raw;
|
|
4221
|
+
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
4222
|
+
} catch {
|
|
4223
|
+
const cached = await getCachedLicense();
|
|
4224
|
+
if (cached) return cached;
|
|
4225
|
+
const rawFallback = getRawCachedPlan();
|
|
4226
|
+
if (rawFallback) return rawFallback;
|
|
4227
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
function getCacheAgeMs() {
|
|
4231
|
+
try {
|
|
4232
|
+
const { statSync: statSync4 } = __require("fs");
|
|
4233
|
+
const s = statSync4(CACHE_PATH);
|
|
4234
|
+
return Date.now() - s.mtimeMs;
|
|
3643
4235
|
} catch {
|
|
3644
4236
|
return Infinity;
|
|
3645
4237
|
}
|
|
@@ -4390,7 +4982,7 @@ function _resetLastRelaunchCache() {
|
|
|
4390
4982
|
}
|
|
4391
4983
|
async function lastResumeCreatedAtMs(agentId) {
|
|
4392
4984
|
const client = getClient();
|
|
4393
|
-
const cmScope = sessionScopeFilter();
|
|
4985
|
+
const cmScope = sessionScopeFilter(null);
|
|
4394
4986
|
const result = await client.execute({
|
|
4395
4987
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
4396
4988
|
FROM tasks
|
|
@@ -4415,7 +5007,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
4415
5007
|
const client = getClient();
|
|
4416
5008
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4417
5009
|
const context = buildResumeContext(agentId, openTasks);
|
|
4418
|
-
const rdScope = sessionScopeFilter();
|
|
5010
|
+
const rdScope = sessionScopeFilter(null);
|
|
4419
5011
|
const existing = await client.execute({
|
|
4420
5012
|
sql: `SELECT id FROM tasks
|
|
4421
5013
|
WHERE assigned_to = ?
|
|
@@ -4449,7 +5041,7 @@ async function pollCapacityDead() {
|
|
|
4449
5041
|
const transport = getTransport();
|
|
4450
5042
|
const relaunched = [];
|
|
4451
5043
|
const registered = listSessions().filter(
|
|
4452
|
-
(s) => s.agentId !== "exe"
|
|
5044
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
4453
5045
|
);
|
|
4454
5046
|
if (registered.length === 0) return [];
|
|
4455
5047
|
let liveSessions;
|
|
@@ -4509,7 +5101,7 @@ async function pollCapacityDead() {
|
|
|
4509
5101
|
reason: "capacity"
|
|
4510
5102
|
});
|
|
4511
5103
|
const client = getClient();
|
|
4512
|
-
const rlScope = sessionScopeFilter();
|
|
5104
|
+
const rlScope = sessionScopeFilter(null);
|
|
4513
5105
|
const openTasks = await client.execute({
|
|
4514
5106
|
sql: `SELECT id, title, priority, task_file, status
|
|
4515
5107
|
FROM tasks
|
|
@@ -4563,6 +5155,7 @@ var init_capacity_monitor = __esm({
|
|
|
4563
5155
|
init_session_kill_telemetry();
|
|
4564
5156
|
init_tmux_routing();
|
|
4565
5157
|
init_task_scope();
|
|
5158
|
+
init_employees();
|
|
4566
5159
|
CAPACITY_PATTERNS = [
|
|
4567
5160
|
/conversation is too long/i,
|
|
4568
5161
|
/maximum context length/i,
|
|
@@ -4712,7 +5305,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
4712
5305
|
exeSession = root;
|
|
4713
5306
|
} else {
|
|
4714
5307
|
throw new Error(
|
|
4715
|
-
`Invalid
|
|
5308
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4716
5309
|
);
|
|
4717
5310
|
}
|
|
4718
5311
|
}
|
|
@@ -4732,8 +5325,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
4732
5325
|
return match?.[1] ?? null;
|
|
4733
5326
|
}
|
|
4734
5327
|
function extractRootExe(name) {
|
|
4735
|
-
|
|
4736
|
-
|
|
5328
|
+
if (!name) return null;
|
|
5329
|
+
if (!name.includes("-")) return name;
|
|
5330
|
+
const parts = name.split("-").filter(Boolean);
|
|
5331
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4737
5332
|
}
|
|
4738
5333
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4739
5334
|
if (!existsSync13(SESSION_CACHE)) {
|
|
@@ -4878,12 +5473,14 @@ function isSessionBusy(sessionName) {
|
|
|
4878
5473
|
return state === "thinking" || state === "tool";
|
|
4879
5474
|
}
|
|
4880
5475
|
function isExeSession(sessionName) {
|
|
4881
|
-
|
|
5476
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
5477
|
+
const coordinatorName = getCoordinatorName();
|
|
5478
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
4882
5479
|
}
|
|
4883
5480
|
function sendIntercom(targetSession) {
|
|
4884
5481
|
const transport = getTransport();
|
|
4885
5482
|
if (isExeSession(targetSession)) {
|
|
4886
|
-
logIntercom(`
|
|
5483
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
4887
5484
|
return "skipped_exe";
|
|
4888
5485
|
}
|
|
4889
5486
|
if (isDebounced(targetSession)) {
|
|
@@ -4935,7 +5532,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4935
5532
|
if (result === "failed") {
|
|
4936
5533
|
const rootExe = resolveExeSession();
|
|
4937
5534
|
if (rootExe && rootExe !== target) {
|
|
4938
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
5535
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
4939
5536
|
`);
|
|
4940
5537
|
const fallback = sendIntercom(rootExe);
|
|
4941
5538
|
return fallback !== "failed";
|
|
@@ -4945,8 +5542,8 @@ function notifyParentExe(sessionKey) {
|
|
|
4945
5542
|
return true;
|
|
4946
5543
|
}
|
|
4947
5544
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4948
|
-
if (employeeName === "exe") {
|
|
4949
|
-
return { status: "failed", sessionName: "", error: "
|
|
5545
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
5546
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4950
5547
|
}
|
|
4951
5548
|
try {
|
|
4952
5549
|
assertEmployeeLimitSync();
|
|
@@ -4955,8 +5552,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4955
5552
|
return { status: "failed", sessionName: "", error: err.message };
|
|
4956
5553
|
}
|
|
4957
5554
|
}
|
|
4958
|
-
if (
|
|
4959
|
-
const bare = employeeName.
|
|
5555
|
+
if (employeeName.includes("-")) {
|
|
5556
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
4960
5557
|
return {
|
|
4961
5558
|
status: "failed",
|
|
4962
5559
|
sessionName: "",
|
|
@@ -4975,7 +5572,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4975
5572
|
return {
|
|
4976
5573
|
status: "failed",
|
|
4977
5574
|
sessionName: "",
|
|
4978
|
-
error: `Invalid
|
|
5575
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4979
5576
|
};
|
|
4980
5577
|
}
|
|
4981
5578
|
}
|
|
@@ -5132,8 +5729,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5132
5729
|
const ctxContent = [
|
|
5133
5730
|
`## Session Context`,
|
|
5134
5731
|
`You are running in tmux session: ${sessionName}.`,
|
|
5135
|
-
`Your parent
|
|
5136
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
5732
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
5733
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5137
5734
|
].join("\n");
|
|
5138
5735
|
writeFileSync8(ctxFile, ctxContent);
|
|
5139
5736
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -5237,6 +5834,7 @@ var init_tmux_routing = __esm({
|
|
|
5237
5834
|
init_provider_table();
|
|
5238
5835
|
init_intercom_queue();
|
|
5239
5836
|
init_plan_limits();
|
|
5837
|
+
init_employees();
|
|
5240
5838
|
SPAWN_LOCK_DIR = path17.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
5241
5839
|
SESSION_CACHE = path17.join(os7.homedir(), ".exe-os", "session-cache");
|
|
5242
5840
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -5275,11 +5873,27 @@ var init_task_scope = __esm({
|
|
|
5275
5873
|
});
|
|
5276
5874
|
|
|
5277
5875
|
// src/lib/tasks-crud.ts
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5876
|
+
var tasks_crud_exports = {};
|
|
5877
|
+
__export(tasks_crud_exports, {
|
|
5878
|
+
TASK_ALREADY_CLAIMED_PREFIX: () => TASK_ALREADY_CLAIMED_PREFIX,
|
|
5879
|
+
checkStaleCompletion: () => checkStaleCompletion,
|
|
5880
|
+
createTaskCore: () => createTaskCore,
|
|
5881
|
+
deleteTaskCore: () => deleteTaskCore,
|
|
5882
|
+
ensureArchitectureDoc: () => ensureArchitectureDoc,
|
|
5883
|
+
ensureGitignoreExe: () => ensureGitignoreExe,
|
|
5884
|
+
extractParentFromContext: () => extractParentFromContext,
|
|
5885
|
+
isTmuxSessionAlive: () => isTmuxSessionAlive,
|
|
5886
|
+
listTasks: () => listTasks,
|
|
5887
|
+
resolveTask: () => resolveTask,
|
|
5888
|
+
slugify: () => slugify,
|
|
5889
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
5890
|
+
writeCheckpoint: () => writeCheckpoint
|
|
5891
|
+
});
|
|
5892
|
+
import crypto6 from "crypto";
|
|
5893
|
+
import path18 from "path";
|
|
5894
|
+
import { execSync as execSync8 } from "child_process";
|
|
5895
|
+
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
5896
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
5283
5897
|
async function writeCheckpoint(input) {
|
|
5284
5898
|
const client = getClient();
|
|
5285
5899
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -5519,6 +6133,36 @@ async function listTasks(input) {
|
|
|
5519
6133
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
5520
6134
|
}));
|
|
5521
6135
|
}
|
|
6136
|
+
function isTmuxSessionAlive(identifier) {
|
|
6137
|
+
if (!identifier || identifier === "unknown") return true;
|
|
6138
|
+
try {
|
|
6139
|
+
if (identifier.startsWith("%")) {
|
|
6140
|
+
const output = execSync8("tmux list-panes -a -F '#{pane_id}'", {
|
|
6141
|
+
timeout: 2e3,
|
|
6142
|
+
encoding: "utf8",
|
|
6143
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6144
|
+
});
|
|
6145
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
6146
|
+
} else {
|
|
6147
|
+
execSync8(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
6148
|
+
timeout: 2e3,
|
|
6149
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6150
|
+
});
|
|
6151
|
+
return true;
|
|
6152
|
+
}
|
|
6153
|
+
} catch {
|
|
6154
|
+
if (identifier.startsWith("%")) return true;
|
|
6155
|
+
try {
|
|
6156
|
+
execSync8("tmux list-sessions", {
|
|
6157
|
+
timeout: 2e3,
|
|
6158
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6159
|
+
});
|
|
6160
|
+
return false;
|
|
6161
|
+
} catch {
|
|
6162
|
+
return true;
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
5522
6166
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
5523
6167
|
if (!taskContext) return null;
|
|
5524
6168
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -5581,13 +6225,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
5581
6225
|
});
|
|
5582
6226
|
if (claim.rowsAffected === 0) {
|
|
5583
6227
|
const current = await client.execute({
|
|
5584
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
6228
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
5585
6229
|
args: [taskId]
|
|
5586
6230
|
});
|
|
5587
6231
|
const cur = current.rows[0];
|
|
5588
|
-
const
|
|
5589
|
-
const
|
|
5590
|
-
|
|
6232
|
+
const curStatus = cur?.status ?? "unknown";
|
|
6233
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
6234
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
6235
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
6236
|
+
process.stderr.write(
|
|
6237
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
6238
|
+
`
|
|
6239
|
+
);
|
|
6240
|
+
await client.execute({
|
|
6241
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
6242
|
+
args: [now, taskId]
|
|
6243
|
+
});
|
|
6244
|
+
const retried = await client.execute({
|
|
6245
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
6246
|
+
args: [tmuxSession, now, taskId]
|
|
6247
|
+
});
|
|
6248
|
+
if (retried.rowsAffected > 0) {
|
|
6249
|
+
try {
|
|
6250
|
+
await writeCheckpoint({
|
|
6251
|
+
taskId,
|
|
6252
|
+
step: "reclaimed_dead_session",
|
|
6253
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
6254
|
+
});
|
|
6255
|
+
} catch {
|
|
6256
|
+
}
|
|
6257
|
+
return { row, taskFile, now, taskId };
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
6261
|
+
process.stderr.write(
|
|
6262
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
6263
|
+
`
|
|
6264
|
+
);
|
|
6265
|
+
await client.execute({
|
|
6266
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
6267
|
+
args: [tmuxSession, now, taskId]
|
|
6268
|
+
});
|
|
6269
|
+
try {
|
|
6270
|
+
await writeCheckpoint({
|
|
6271
|
+
taskId,
|
|
6272
|
+
step: "assigner_override",
|
|
6273
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
6274
|
+
});
|
|
6275
|
+
} catch {
|
|
6276
|
+
}
|
|
6277
|
+
return { row, taskFile, now, taskId };
|
|
6278
|
+
}
|
|
6279
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
6280
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
5591
6281
|
}
|
|
5592
6282
|
try {
|
|
5593
6283
|
await writeCheckpoint({
|
|
@@ -5685,7 +6375,7 @@ var init_tasks_crud = __esm({
|
|
|
5685
6375
|
"use strict";
|
|
5686
6376
|
init_database();
|
|
5687
6377
|
init_task_scope();
|
|
5688
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
6378
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
5689
6379
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
5690
6380
|
}
|
|
5691
6381
|
});
|
|
@@ -6000,7 +6690,7 @@ function findSessionForProject(projectName) {
|
|
|
6000
6690
|
const sessions = listSessions();
|
|
6001
6691
|
for (const s of sessions) {
|
|
6002
6692
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
6003
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
6693
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
6004
6694
|
}
|
|
6005
6695
|
return null;
|
|
6006
6696
|
}
|
|
@@ -6040,12 +6730,13 @@ var init_session_scope = __esm({
|
|
|
6040
6730
|
init_session_registry();
|
|
6041
6731
|
init_project_name();
|
|
6042
6732
|
init_tmux_routing();
|
|
6733
|
+
init_employees();
|
|
6043
6734
|
}
|
|
6044
6735
|
});
|
|
6045
6736
|
|
|
6046
6737
|
// src/lib/tasks-notify.ts
|
|
6047
6738
|
async function dispatchTaskToEmployee(input) {
|
|
6048
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
6739
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
6049
6740
|
let crossProject = false;
|
|
6050
6741
|
if (input.projectName) {
|
|
6051
6742
|
try {
|
|
@@ -6516,6 +7207,24 @@ async function updateTask(input) {
|
|
|
6516
7207
|
});
|
|
6517
7208
|
} catch {
|
|
6518
7209
|
}
|
|
7210
|
+
const assignedAgent = String(row.assigned_to);
|
|
7211
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
7212
|
+
try {
|
|
7213
|
+
const draftClient = getClient();
|
|
7214
|
+
if (input.status === "done") {
|
|
7215
|
+
await draftClient.execute({
|
|
7216
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
7217
|
+
args: [assignedAgent]
|
|
7218
|
+
});
|
|
7219
|
+
} else if (input.status === "cancelled") {
|
|
7220
|
+
await draftClient.execute({
|
|
7221
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
7222
|
+
args: [assignedAgent]
|
|
7223
|
+
});
|
|
7224
|
+
}
|
|
7225
|
+
} catch {
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
6519
7228
|
try {
|
|
6520
7229
|
const client = getClient();
|
|
6521
7230
|
const cascaded = await client.execute({
|
|
@@ -6534,8 +7243,8 @@ async function updateTask(input) {
|
|
|
6534
7243
|
}
|
|
6535
7244
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
6536
7245
|
if (isTerminal) {
|
|
6537
|
-
const
|
|
6538
|
-
if (!
|
|
7246
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
7247
|
+
if (!isCoordinator) {
|
|
6539
7248
|
notifyTaskDone();
|
|
6540
7249
|
}
|
|
6541
7250
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -6559,7 +7268,7 @@ async function updateTask(input) {
|
|
|
6559
7268
|
}
|
|
6560
7269
|
}
|
|
6561
7270
|
}
|
|
6562
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
7271
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6563
7272
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6564
7273
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
6565
7274
|
taskId,
|
|
@@ -6575,7 +7284,7 @@ async function updateTask(input) {
|
|
|
6575
7284
|
});
|
|
6576
7285
|
}
|
|
6577
7286
|
let nextTask;
|
|
6578
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
7287
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
6579
7288
|
try {
|
|
6580
7289
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
6581
7290
|
} catch {
|
|
@@ -6602,12 +7311,14 @@ async function updateTask(input) {
|
|
|
6602
7311
|
async function deleteTask(taskId, baseDir) {
|
|
6603
7312
|
const client = getClient();
|
|
6604
7313
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
6605
|
-
const
|
|
7314
|
+
const coordinatorName = getCoordinatorName();
|
|
7315
|
+
const reviewer = assignedBy || coordinatorName;
|
|
6606
7316
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
6607
7317
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
7318
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
6608
7319
|
await client.execute({
|
|
6609
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
6610
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
7320
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
7321
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
6611
7322
|
});
|
|
6612
7323
|
await markAsReadByTaskFile(taskFile);
|
|
6613
7324
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -6619,6 +7330,7 @@ var init_tasks = __esm({
|
|
|
6619
7330
|
init_config();
|
|
6620
7331
|
init_notifications();
|
|
6621
7332
|
init_state_bus();
|
|
7333
|
+
init_employees();
|
|
6622
7334
|
init_tasks_crud();
|
|
6623
7335
|
init_tasks_review();
|
|
6624
7336
|
init_tasks_crud();
|
|
@@ -6733,7 +7445,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
6733
7445
|
try {
|
|
6734
7446
|
const exeSession = resolveExeSession();
|
|
6735
7447
|
if (!exeSession) {
|
|
6736
|
-
throw new Error("No
|
|
7448
|
+
throw new Error("No coordinator session found");
|
|
6737
7449
|
}
|
|
6738
7450
|
const sessionName = employeeSessionName(targetAgent, exeSession);
|
|
6739
7451
|
if (!isEmployeeAlive(sessionName)) {
|
|
@@ -7065,8 +7777,8 @@ async function runConsolidation(client, options) {
|
|
|
7065
7777
|
if (clustersProcessed >= options.maxCalls) break;
|
|
7066
7778
|
if (cluster.memories.length < 3) continue;
|
|
7067
7779
|
try {
|
|
7068
|
-
const
|
|
7069
|
-
if (
|
|
7780
|
+
const isCoordinator = cluster.agentId === "exe" || isCoordinatorName(cluster.agentId);
|
|
7781
|
+
if (isCoordinator) {
|
|
7070
7782
|
const synthesis = await consolidateCluster(cluster, options.model);
|
|
7071
7783
|
if (!synthesis.trim()) continue;
|
|
7072
7784
|
const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
|
|
@@ -7095,7 +7807,7 @@ async function runConsolidation(client, options) {
|
|
|
7095
7807
|
if (dedupCount === 0) continue;
|
|
7096
7808
|
}
|
|
7097
7809
|
clustersProcessed++;
|
|
7098
|
-
memoriesConsolidated +=
|
|
7810
|
+
memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
|
|
7099
7811
|
} catch (err) {
|
|
7100
7812
|
process.stderr.write(
|
|
7101
7813
|
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
@@ -7182,10 +7894,153 @@ var init_consolidation = __esm({
|
|
|
7182
7894
|
"src/lib/consolidation.ts"() {
|
|
7183
7895
|
"use strict";
|
|
7184
7896
|
init_store();
|
|
7897
|
+
init_employees();
|
|
7185
7898
|
WIKI_FETCH_TIMEOUT_MS = 1e4;
|
|
7186
7899
|
}
|
|
7187
7900
|
});
|
|
7188
7901
|
|
|
7902
|
+
// src/lib/wiki-client.ts
|
|
7903
|
+
var wiki_client_exports = {};
|
|
7904
|
+
__export(wiki_client_exports, {
|
|
7905
|
+
chatInWorkspace: () => chatInWorkspace,
|
|
7906
|
+
createWikiClient: () => createWikiClient,
|
|
7907
|
+
getChatHistory: () => getChatHistory,
|
|
7908
|
+
listDocuments: () => listDocuments,
|
|
7909
|
+
listWorkspaces: () => listWorkspaces
|
|
7910
|
+
});
|
|
7911
|
+
async function wikiFetch(config2, path30, method = "GET", body) {
|
|
7912
|
+
const url = `${config2.baseUrl}/api/v1${path30}`;
|
|
7913
|
+
const headers = {
|
|
7914
|
+
Authorization: `Bearer ${config2.apiKey}`,
|
|
7915
|
+
"Content-Type": "application/json"
|
|
7916
|
+
};
|
|
7917
|
+
const controller = new AbortController();
|
|
7918
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
7919
|
+
try {
|
|
7920
|
+
let response;
|
|
7921
|
+
try {
|
|
7922
|
+
response = await fetch(url, {
|
|
7923
|
+
method,
|
|
7924
|
+
headers,
|
|
7925
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
7926
|
+
signal: controller.signal
|
|
7927
|
+
});
|
|
7928
|
+
} catch {
|
|
7929
|
+
clearTimeout(timeout);
|
|
7930
|
+
const retryController = new AbortController();
|
|
7931
|
+
const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
|
|
7932
|
+
try {
|
|
7933
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
7934
|
+
response = await fetch(url, {
|
|
7935
|
+
method,
|
|
7936
|
+
headers,
|
|
7937
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
7938
|
+
signal: retryController.signal
|
|
7939
|
+
});
|
|
7940
|
+
} finally {
|
|
7941
|
+
clearTimeout(retryTimeout);
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
if (!response.ok) {
|
|
7945
|
+
throw new Error(`Wiki API ${method} ${path30}: ${response.status} ${response.statusText}`);
|
|
7946
|
+
}
|
|
7947
|
+
return response.json();
|
|
7948
|
+
} finally {
|
|
7949
|
+
clearTimeout(timeout);
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
async function resolveWikiUrl(configUrl) {
|
|
7953
|
+
try {
|
|
7954
|
+
const controller = new AbortController();
|
|
7955
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
7956
|
+
const res = await fetch(`${LOCAL_WIKI_URL}/api/v1/auth`, { signal: controller.signal });
|
|
7957
|
+
clearTimeout(timeout);
|
|
7958
|
+
if (res.ok || res.status === 401 || res.status === 403) {
|
|
7959
|
+
return LOCAL_WIKI_URL;
|
|
7960
|
+
}
|
|
7961
|
+
} catch {
|
|
7962
|
+
}
|
|
7963
|
+
if (configUrl && configUrl !== LOCAL_WIKI_URL) {
|
|
7964
|
+
try {
|
|
7965
|
+
const controller = new AbortController();
|
|
7966
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
7967
|
+
const res = await fetch(`${configUrl}/api/v1/auth`, { signal: controller.signal });
|
|
7968
|
+
clearTimeout(timeout);
|
|
7969
|
+
if (res.ok || res.status === 401 || res.status === 403) {
|
|
7970
|
+
return configUrl;
|
|
7971
|
+
}
|
|
7972
|
+
} catch {
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
return null;
|
|
7976
|
+
}
|
|
7977
|
+
async function createWikiClient() {
|
|
7978
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
7979
|
+
const config2 = await loadConfig2();
|
|
7980
|
+
const baseUrl = await resolveWikiUrl(config2.wikiUrl);
|
|
7981
|
+
if (!baseUrl) return null;
|
|
7982
|
+
return {
|
|
7983
|
+
baseUrl,
|
|
7984
|
+
apiKey: config2.wikiApiKey
|
|
7985
|
+
};
|
|
7986
|
+
}
|
|
7987
|
+
async function listWorkspaces(client) {
|
|
7988
|
+
const data = await wikiFetch(client, "/workspaces");
|
|
7989
|
+
return (data.workspaces ?? []).map((w) => ({
|
|
7990
|
+
slug: w.slug,
|
|
7991
|
+
name: w.name,
|
|
7992
|
+
createdAt: w.createdAt ?? "",
|
|
7993
|
+
threads: w.threads ?? []
|
|
7994
|
+
}));
|
|
7995
|
+
}
|
|
7996
|
+
async function listDocuments(client, workspaceSlug) {
|
|
7997
|
+
const data = await wikiFetch(
|
|
7998
|
+
client,
|
|
7999
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}`
|
|
8000
|
+
);
|
|
8001
|
+
const docs = Array.isArray(data.workspace) ? data.workspace[0]?.documents ?? [] : [];
|
|
8002
|
+
return docs.map((d) => ({
|
|
8003
|
+
filename: d.filename ?? "untitled",
|
|
8004
|
+
docpath: d.docpath,
|
|
8005
|
+
workspace: workspaceSlug,
|
|
8006
|
+
pinned: d.pinned ?? false
|
|
8007
|
+
}));
|
|
8008
|
+
}
|
|
8009
|
+
async function chatInWorkspace(client, workspaceSlug, message, mode = "chat") {
|
|
8010
|
+
const data = await wikiFetch(
|
|
8011
|
+
client,
|
|
8012
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}/chat`,
|
|
8013
|
+
"POST",
|
|
8014
|
+
{ message, mode }
|
|
8015
|
+
);
|
|
8016
|
+
return {
|
|
8017
|
+
id: data.id ?? "",
|
|
8018
|
+
type: data.type ?? "textResponse",
|
|
8019
|
+
textResponse: data.textResponse ?? "",
|
|
8020
|
+
sources: data.sources ?? [],
|
|
8021
|
+
error: data.error ?? null
|
|
8022
|
+
};
|
|
8023
|
+
}
|
|
8024
|
+
async function getChatHistory(client, workspaceSlug, limit = 50) {
|
|
8025
|
+
const data = await wikiFetch(
|
|
8026
|
+
client,
|
|
8027
|
+
`/workspace/${encodeURIComponent(workspaceSlug)}/chats?limit=${limit}&orderBy=desc`
|
|
8028
|
+
);
|
|
8029
|
+
return (data.history ?? []).reverse().map((h) => ({
|
|
8030
|
+
role: h.role === "user" ? "user" : "assistant",
|
|
8031
|
+
content: h.content,
|
|
8032
|
+
sentAt: h.sentAt ?? 0
|
|
8033
|
+
}));
|
|
8034
|
+
}
|
|
8035
|
+
var LOCAL_WIKI_URL, REQUEST_TIMEOUT_MS2;
|
|
8036
|
+
var init_wiki_client = __esm({
|
|
8037
|
+
"src/lib/wiki-client.ts"() {
|
|
8038
|
+
"use strict";
|
|
8039
|
+
LOCAL_WIKI_URL = "http://localhost:3001";
|
|
8040
|
+
REQUEST_TIMEOUT_MS2 = 8e3;
|
|
8041
|
+
}
|
|
8042
|
+
});
|
|
8043
|
+
|
|
7189
8044
|
// src/lib/worker-gate.ts
|
|
7190
8045
|
var worker_gate_exports = {};
|
|
7191
8046
|
__export(worker_gate_exports, {
|
|
@@ -7271,404 +8126,46 @@ function tryAcquireBackfillLock() {
|
|
|
7271
8126
|
}
|
|
7272
8127
|
}
|
|
7273
8128
|
} catch {
|
|
7274
|
-
}
|
|
7275
|
-
}
|
|
7276
|
-
writeFileSync13(BACKFILL_LOCK, String(process.pid));
|
|
7277
|
-
return true;
|
|
7278
|
-
} catch {
|
|
7279
|
-
return true;
|
|
7280
|
-
}
|
|
7281
|
-
}
|
|
7282
|
-
function releaseBackfillLock() {
|
|
7283
|
-
try {
|
|
7284
|
-
unlinkSync8(BACKFILL_LOCK);
|
|
7285
|
-
} catch {
|
|
7286
|
-
}
|
|
7287
|
-
}
|
|
7288
|
-
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
|
|
7289
|
-
var init_worker_gate = __esm({
|
|
7290
|
-
"src/lib/worker-gate.ts"() {
|
|
7291
|
-
"use strict";
|
|
7292
|
-
init_config();
|
|
7293
|
-
WORKER_PID_DIR = path28.join(EXE_AI_DIR, "worker-pids");
|
|
7294
|
-
MAX_CONCURRENT_WORKERS = 3;
|
|
7295
|
-
BACKFILL_LOCK = path28.join(WORKER_PID_DIR, "backfill.lock");
|
|
7296
|
-
}
|
|
7297
|
-
});
|
|
7298
|
-
|
|
7299
|
-
// src/mcp/server.ts
|
|
7300
|
-
init_embedder();
|
|
7301
|
-
init_store();
|
|
7302
|
-
init_database();
|
|
7303
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7304
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7305
|
-
import { spawn as spawn2 } from "child_process";
|
|
7306
|
-
import { existsSync as existsSync22, openSync as openSync2, mkdirSync as mkdirSync12, closeSync as closeSync2 } from "fs";
|
|
7307
|
-
import path29 from "path";
|
|
7308
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7309
|
-
|
|
7310
|
-
// src/mcp/tools/recall-my-memory.ts
|
|
7311
|
-
import { z } from "zod";
|
|
7312
|
-
|
|
7313
|
-
// src/lib/hybrid-search.ts
|
|
7314
|
-
init_store();
|
|
7315
|
-
init_database();
|
|
7316
|
-
var RRF_K = 60;
|
|
7317
|
-
async function hybridSearch(queryText, agentId, options) {
|
|
7318
|
-
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
7319
|
-
const config2 = await loadConfig2();
|
|
7320
|
-
if (config2.searchMode === "fts") {
|
|
7321
|
-
return lightweightSearch(queryText, agentId, options);
|
|
7322
|
-
}
|
|
7323
|
-
const limit = options?.limit ?? 10;
|
|
7324
|
-
let effectiveQuery = queryText;
|
|
7325
|
-
let effectiveOptions = { ...options };
|
|
7326
|
-
let _isBroadQuery = false;
|
|
7327
|
-
if (config2.selfQueryRouter && process.env.ANTHROPIC_API_KEY) {
|
|
7328
|
-
try {
|
|
7329
|
-
const { routeQuery: routeQuery2 } = await Promise.resolve().then(() => (init_self_query_router(), self_query_router_exports));
|
|
7330
|
-
const routed = await routeQuery2(queryText, config2.selfQueryModel);
|
|
7331
|
-
effectiveQuery = routed.semanticQuery;
|
|
7332
|
-
_isBroadQuery = routed.isBroadQuery;
|
|
7333
|
-
if (routed.projectFilter && !effectiveOptions.projectName) {
|
|
7334
|
-
effectiveOptions.projectName = routed.projectFilter;
|
|
7335
|
-
}
|
|
7336
|
-
if (routed.timeFilter && !effectiveOptions.since) {
|
|
7337
|
-
effectiveOptions.since = routed.timeFilter;
|
|
7338
|
-
}
|
|
7339
|
-
} catch {
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
const { getMemoryCardinality: getMemoryCardinality2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7343
|
-
const cardinality = await getMemoryCardinality2(agentId);
|
|
7344
|
-
const { rerankerAutoTrigger } = config2.scalingRoadmap ?? {};
|
|
7345
|
-
const minCardForBroad = rerankerAutoTrigger?.broadQueryMinCardinality ?? 5e4;
|
|
7346
|
-
const useNarrowPath = cardinality < 1e4;
|
|
7347
|
-
const useBroadPath = cardinality > minCardForBroad || _isBroadQuery && cardinality >= 1e4;
|
|
7348
|
-
const effectiveIsBroad = useBroadPath && !useNarrowPath;
|
|
7349
|
-
if (effectiveIsBroad !== _isBroadQuery) {
|
|
7350
|
-
process.stderr.write(
|
|
7351
|
-
`[hybrid-search] Adaptive routing override: cardinality=${cardinality}, router=${_isBroadQuery ? "broad" : "narrow"} \u2192 ${effectiveIsBroad ? "broad" : "narrow"}
|
|
7352
|
-
`
|
|
7353
|
-
);
|
|
7354
|
-
}
|
|
7355
|
-
const broadFetchTopK = config2.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
|
|
7356
|
-
const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
|
|
7357
|
-
const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
|
|
7358
|
-
let queryVector = null;
|
|
7359
|
-
try {
|
|
7360
|
-
const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
|
|
7361
|
-
queryVector = await embed2(effectiveQuery);
|
|
7362
|
-
} catch {
|
|
7363
|
-
process.stderr.write("[hybrid-search] Embed daemon unavailable \u2014 FTS-only mode\n");
|
|
7364
|
-
}
|
|
7365
|
-
let grepPromise = Promise.resolve([]);
|
|
7366
|
-
if (config2.fileGrepEnabled !== false) {
|
|
7367
|
-
try {
|
|
7368
|
-
const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
7369
|
-
const projectRoot = process.cwd();
|
|
7370
|
-
const projectName = getProjectName2(projectRoot);
|
|
7371
|
-
if (projectName && projectName !== "tmp") {
|
|
7372
|
-
const { grepProjectFiles: grepProjectFiles2 } = await Promise.resolve().then(() => (init_file_grep(), file_grep_exports));
|
|
7373
|
-
grepPromise = grepProjectFiles2(effectiveQuery, projectRoot, {
|
|
7374
|
-
maxResults: 10
|
|
7375
|
-
}).catch(() => []);
|
|
7376
|
-
}
|
|
7377
|
-
} catch {
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
const [ftsResults, vectorResults, grepResults] = await Promise.all([
|
|
7381
|
-
lightweightSearch(effectiveQuery, agentId, fetchOptions),
|
|
7382
|
-
queryVector ? searchMemories(queryVector, agentId, fetchOptions) : Promise.resolve([]),
|
|
7383
|
-
grepPromise
|
|
7384
|
-
]);
|
|
7385
|
-
const lists = [];
|
|
7386
|
-
const weights = [];
|
|
7387
|
-
if (ftsResults.length > 0) {
|
|
7388
|
-
lists.push(ftsResults);
|
|
7389
|
-
weights.push(1);
|
|
7390
|
-
}
|
|
7391
|
-
if (vectorResults.length > 0) {
|
|
7392
|
-
lists.push(vectorResults);
|
|
7393
|
-
weights.push(1);
|
|
7394
|
-
}
|
|
7395
|
-
if (grepResults.length > 0) {
|
|
7396
|
-
lists.push(grepResults);
|
|
7397
|
-
weights.push(0.5);
|
|
7398
|
-
}
|
|
7399
|
-
if (lists.length === 0) return [];
|
|
7400
|
-
if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
|
|
7401
|
-
const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
|
|
7402
|
-
const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
|
|
7403
|
-
const auto = config2.scalingRoadmap?.rerankerAutoTrigger ?? {
|
|
7404
|
-
enabled: config2.rerankerEnabled ?? true,
|
|
7405
|
-
broadQueryMinCardinality: 5e4,
|
|
7406
|
-
fetchTopK: 150,
|
|
7407
|
-
returnTopK: 5
|
|
7408
|
-
};
|
|
7409
|
-
let rerankedAndBlended = null;
|
|
7410
|
-
if (effectiveIsBroad && auto.enabled) {
|
|
7411
|
-
const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
|
|
7412
|
-
if (cardinality2 > auto.broadQueryMinCardinality) {
|
|
7413
|
-
try {
|
|
7414
|
-
const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
|
|
7415
|
-
if (isRerankerAvailable2()) {
|
|
7416
|
-
const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
|
|
7417
|
-
if (reranked.length > 0) {
|
|
7418
|
-
rerankedAndBlended = rrfMergeMulti(
|
|
7419
|
-
[reranked],
|
|
7420
|
-
auto.returnTopK,
|
|
7421
|
-
RRF_K
|
|
7422
|
-
);
|
|
7423
|
-
}
|
|
7424
|
-
}
|
|
7425
|
-
} catch {
|
|
7426
|
-
}
|
|
7427
|
-
}
|
|
7428
|
-
}
|
|
7429
|
-
const finalResults = (rerankedAndBlended ?? merged).slice(
|
|
7430
|
-
0,
|
|
7431
|
-
rerankedAndBlended ? auto.returnTopK : limit
|
|
7432
|
-
);
|
|
7433
|
-
if (options?.includeSource && finalResults.length > 0) {
|
|
7434
|
-
await attachDocumentMetadata(finalResults);
|
|
7435
|
-
}
|
|
7436
|
-
if (finalResults.length > 0) {
|
|
7437
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7438
|
-
const ids = finalResults.map((r) => r.id);
|
|
7439
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
7440
|
-
try {
|
|
7441
|
-
const client = getClient();
|
|
7442
|
-
void client.execute({
|
|
7443
|
-
sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
|
|
7444
|
-
args: [now, ...ids]
|
|
7445
|
-
}).catch(() => {
|
|
7446
|
-
});
|
|
7447
|
-
} catch {
|
|
7448
|
-
}
|
|
7449
|
-
}
|
|
7450
|
-
return finalResults;
|
|
7451
|
-
}
|
|
7452
|
-
async function estimateCardinality(agentId, options) {
|
|
7453
|
-
const client = getClient();
|
|
7454
|
-
let sql = `SELECT COUNT(*) as cnt FROM memories
|
|
7455
|
-
WHERE agent_id = ?
|
|
7456
|
-
AND COALESCE(status, 'active') = 'active'
|
|
7457
|
-
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
7458
|
-
const args = [agentId];
|
|
7459
|
-
if (options?.projectName) {
|
|
7460
|
-
sql += ` AND project_name = ?`;
|
|
7461
|
-
args.push(options.projectName);
|
|
7462
|
-
}
|
|
7463
|
-
if (options?.toolName) {
|
|
7464
|
-
sql += ` AND tool_name = ?`;
|
|
7465
|
-
args.push(options.toolName);
|
|
7466
|
-
}
|
|
7467
|
-
if (options?.hasError !== void 0) {
|
|
7468
|
-
sql += ` AND has_error = ?`;
|
|
7469
|
-
args.push(options.hasError ? 1 : 0);
|
|
7470
|
-
}
|
|
7471
|
-
if (options?.since) {
|
|
7472
|
-
sql += ` AND timestamp >= ?`;
|
|
7473
|
-
args.push(options.since);
|
|
7474
|
-
}
|
|
7475
|
-
try {
|
|
7476
|
-
const result = await client.execute({ sql, args });
|
|
7477
|
-
return Number(result.rows[0]?.cnt) || 0;
|
|
7478
|
-
} catch {
|
|
7479
|
-
return 0;
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
|
-
function recencyScore(timestamp) {
|
|
7483
|
-
const daysSince = (Date.now() - new Date(timestamp).getTime()) / (1e3 * 60 * 60 * 24);
|
|
7484
|
-
return 1 / (1 + daysSince * 0.01);
|
|
7485
|
-
}
|
|
7486
|
-
function normalizedImportance(importance) {
|
|
7487
|
-
return ((importance ?? 5) - 1) / 9;
|
|
7488
|
-
}
|
|
7489
|
-
function frecencyBoost(lastAccessed, timestamp) {
|
|
7490
|
-
const accessTime = lastAccessed ? new Date(lastAccessed).getTime() : new Date(timestamp).getTime();
|
|
7491
|
-
const hoursSince = Math.max(0, (Date.now() - accessTime) / (1e3 * 60 * 60));
|
|
7492
|
-
return Math.exp(-0.01 * hoursSince);
|
|
7493
|
-
}
|
|
7494
|
-
function rrfMergeMulti(lists, limit, k = RRF_K, weights) {
|
|
7495
|
-
const scores = /* @__PURE__ */ new Map();
|
|
7496
|
-
for (let listIdx = 0; listIdx < lists.length; listIdx++) {
|
|
7497
|
-
const list = lists[listIdx];
|
|
7498
|
-
const weight = weights?.[listIdx] ?? 1;
|
|
7499
|
-
for (let i = 0; i < list.length; i++) {
|
|
7500
|
-
const rec = list[i];
|
|
7501
|
-
const entry = scores.get(rec.id) ?? { rrfScore: 0, record: rec };
|
|
7502
|
-
entry.rrfScore += weight * (1 / (k + i + 1));
|
|
7503
|
-
scores.set(rec.id, entry);
|
|
7504
|
-
}
|
|
7505
|
-
}
|
|
7506
|
-
const entries = Array.from(scores.values()).map((e) => {
|
|
7507
|
-
const recency = recencyScore(e.record.timestamp);
|
|
7508
|
-
const importance = normalizedImportance(e.record.importance);
|
|
7509
|
-
const confidence = e.record.confidence ?? 0.7;
|
|
7510
|
-
const frecency = frecencyBoost(e.record.last_accessed, e.record.timestamp);
|
|
7511
|
-
const baseScore = e.rrfScore * 0.35 + recency * 0.14 + importance * 0.21 + confidence * 0.3;
|
|
7512
|
-
const finalScore = baseScore * (1 + 0.3 * frecency);
|
|
7513
|
-
return { score: finalScore, record: e.record };
|
|
7514
|
-
});
|
|
7515
|
-
return entries.sort((a, b) => b.score - a.score).slice(0, limit).map((e) => e.record);
|
|
7516
|
-
}
|
|
7517
|
-
async function lightweightSearch(queryText, agentId, options) {
|
|
7518
|
-
const client = getClient();
|
|
7519
|
-
const limit = options?.limit ?? 5;
|
|
7520
|
-
const terms = queryText.toLowerCase().split(/\s+/).filter((t) => t.length >= 3).map((t) => t.replace(/[^a-z0-9_]/g, "")).filter((t) => t.length >= 3);
|
|
7521
|
-
if (terms.length === 0) {
|
|
7522
|
-
return recentRecords(agentId, options, limit);
|
|
7523
|
-
}
|
|
7524
|
-
const prefixTerms = terms.map((t) => `${t}*`);
|
|
7525
|
-
const useAnd = terms.length >= 3;
|
|
7526
|
-
const matchExpr = useAnd ? prefixTerms.join(" AND ") : prefixTerms.join(" OR ");
|
|
7527
|
-
const results = await ftsQuery(client, matchExpr, agentId, options, limit);
|
|
7528
|
-
if (useAnd && results.length < limit) {
|
|
7529
|
-
const orExpr = prefixTerms.join(" OR ");
|
|
7530
|
-
const orResults = await ftsQuery(client, orExpr, agentId, options, limit);
|
|
7531
|
-
const seen = new Set(results.map((r) => r.id));
|
|
7532
|
-
for (const r of orResults) {
|
|
7533
|
-
if (!seen.has(r.id) && results.length < limit) {
|
|
7534
|
-
results.push(r);
|
|
7535
|
-
seen.add(r.id);
|
|
7536
|
-
}
|
|
7537
|
-
}
|
|
7538
|
-
}
|
|
7539
|
-
if (options?.includeSource && results.length > 0) {
|
|
7540
|
-
await attachDocumentMetadata(results);
|
|
7541
|
-
}
|
|
7542
|
-
return results;
|
|
7543
|
-
}
|
|
7544
|
-
async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
7545
|
-
const statusFilter = options?.includeArchived ? "" : `
|
|
7546
|
-
AND COALESCE(m.status, 'active') = 'active'`;
|
|
7547
|
-
let sql = `SELECT m.id, m.agent_id, m.agent_role, m.session_id, m.timestamp,
|
|
7548
|
-
m.tool_name, m.project_name,
|
|
7549
|
-
m.has_error, m.raw_text, m.vector, m.task_id,
|
|
7550
|
-
m.importance, m.status, m.confidence, m.last_accessed,
|
|
7551
|
-
m.workspace_id, m.document_id, m.user_id,
|
|
7552
|
-
m.char_offset, m.page_number,
|
|
7553
|
-
m.source_path, m.source_type
|
|
7554
|
-
FROM memories m
|
|
7555
|
-
JOIN memories_fts fts ON m.rowid = fts.rowid
|
|
7556
|
-
WHERE memories_fts MATCH ?
|
|
7557
|
-
AND m.agent_id = ?${statusFilter}
|
|
7558
|
-
AND COALESCE(m.confidence, 0.7) >= 0.3`;
|
|
7559
|
-
const args = [matchExpr, agentId];
|
|
7560
|
-
const scope = buildWikiScopeFilter(options, "m.");
|
|
7561
|
-
sql += scope.clause;
|
|
7562
|
-
args.push(...scope.args);
|
|
7563
|
-
if (options?.projectName) {
|
|
7564
|
-
sql += ` AND m.project_name = ?`;
|
|
7565
|
-
args.push(options.projectName);
|
|
7566
|
-
}
|
|
7567
|
-
if (options?.toolName) {
|
|
7568
|
-
sql += ` AND m.tool_name = ?`;
|
|
7569
|
-
args.push(options.toolName);
|
|
7570
|
-
}
|
|
7571
|
-
if (options?.hasError !== void 0) {
|
|
7572
|
-
sql += ` AND m.has_error = ?`;
|
|
7573
|
-
args.push(options.hasError ? 1 : 0);
|
|
7574
|
-
}
|
|
7575
|
-
if (options?.since) {
|
|
7576
|
-
sql += ` AND m.timestamp >= ?`;
|
|
7577
|
-
args.push(options.since);
|
|
7578
|
-
}
|
|
7579
|
-
sql += ` ORDER BY rank LIMIT ?`;
|
|
7580
|
-
args.push(limit);
|
|
7581
|
-
const result = await client.execute({ sql, args });
|
|
7582
|
-
return result.rows.map((row) => ({
|
|
7583
|
-
id: row.id,
|
|
7584
|
-
agent_id: row.agent_id,
|
|
7585
|
-
agent_role: row.agent_role,
|
|
7586
|
-
session_id: row.session_id,
|
|
7587
|
-
timestamp: row.timestamp,
|
|
7588
|
-
tool_name: row.tool_name,
|
|
7589
|
-
project_name: row.project_name,
|
|
7590
|
-
has_error: row.has_error === 1,
|
|
7591
|
-
raw_text: row.raw_text,
|
|
7592
|
-
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
7593
|
-
task_id: row.task_id ?? null,
|
|
7594
|
-
importance: row.importance ?? 5,
|
|
7595
|
-
status: row.status ?? "active",
|
|
7596
|
-
confidence: row.confidence ?? 0.7,
|
|
7597
|
-
last_accessed: row.last_accessed ?? row.timestamp,
|
|
7598
|
-
workspace_id: row.workspace_id ?? null,
|
|
7599
|
-
document_id: row.document_id ?? null,
|
|
7600
|
-
user_id: row.user_id ?? null,
|
|
7601
|
-
char_offset: row.char_offset ?? null,
|
|
7602
|
-
page_number: row.page_number ?? null,
|
|
7603
|
-
source_path: row.source_path ?? null,
|
|
7604
|
-
source_type: row.source_type ?? null
|
|
7605
|
-
}));
|
|
7606
|
-
}
|
|
7607
|
-
async function recentRecords(agentId, options, limit) {
|
|
7608
|
-
const client = getClient();
|
|
7609
|
-
const statusFilter = options?.includeArchived ? "" : `
|
|
7610
|
-
AND COALESCE(status, 'active') = 'active'`;
|
|
7611
|
-
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
7612
|
-
tool_name, project_name,
|
|
7613
|
-
has_error, raw_text, vector, task_id,
|
|
7614
|
-
importance, status, confidence, last_accessed,
|
|
7615
|
-
workspace_id, document_id, user_id,
|
|
7616
|
-
char_offset, page_number,
|
|
7617
|
-
source_path, source_type
|
|
7618
|
-
FROM memories
|
|
7619
|
-
WHERE agent_id = ?${statusFilter}
|
|
7620
|
-
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
7621
|
-
const args = [agentId];
|
|
7622
|
-
const scope = buildWikiScopeFilter(options, "");
|
|
7623
|
-
sql += scope.clause;
|
|
7624
|
-
args.push(...scope.args);
|
|
7625
|
-
if (options?.projectName) {
|
|
7626
|
-
sql += ` AND project_name = ?`;
|
|
7627
|
-
args.push(options.projectName);
|
|
7628
|
-
}
|
|
7629
|
-
if (options?.toolName) {
|
|
7630
|
-
sql += ` AND tool_name = ?`;
|
|
7631
|
-
args.push(options.toolName);
|
|
7632
|
-
}
|
|
7633
|
-
if (options?.hasError !== void 0) {
|
|
7634
|
-
sql += ` AND has_error = ?`;
|
|
7635
|
-
args.push(options.hasError ? 1 : 0);
|
|
7636
|
-
}
|
|
7637
|
-
if (options?.since) {
|
|
7638
|
-
sql += ` AND timestamp >= ?`;
|
|
7639
|
-
args.push(options.since);
|
|
7640
|
-
}
|
|
7641
|
-
sql += ` ORDER BY timestamp DESC LIMIT ?`;
|
|
7642
|
-
args.push(limit);
|
|
7643
|
-
const result = await client.execute({ sql, args });
|
|
7644
|
-
return result.rows.map((row) => ({
|
|
7645
|
-
id: row.id,
|
|
7646
|
-
agent_id: row.agent_id,
|
|
7647
|
-
agent_role: row.agent_role,
|
|
7648
|
-
session_id: row.session_id,
|
|
7649
|
-
timestamp: row.timestamp,
|
|
7650
|
-
tool_name: row.tool_name,
|
|
7651
|
-
project_name: row.project_name,
|
|
7652
|
-
has_error: row.has_error === 1,
|
|
7653
|
-
raw_text: row.raw_text,
|
|
7654
|
-
vector: row.vector == null ? [] : Array.isArray(row.vector) ? row.vector : Array.from(row.vector),
|
|
7655
|
-
task_id: row.task_id ?? null,
|
|
7656
|
-
importance: row.importance ?? 5,
|
|
7657
|
-
status: row.status ?? "active",
|
|
7658
|
-
confidence: row.confidence ?? 0.7,
|
|
7659
|
-
last_accessed: row.last_accessed ?? row.timestamp,
|
|
7660
|
-
workspace_id: row.workspace_id ?? null,
|
|
7661
|
-
document_id: row.document_id ?? null,
|
|
7662
|
-
user_id: row.user_id ?? null,
|
|
7663
|
-
char_offset: row.char_offset ?? null,
|
|
7664
|
-
page_number: row.page_number ?? null,
|
|
7665
|
-
source_path: row.source_path ?? null,
|
|
7666
|
-
source_type: row.source_type ?? null
|
|
7667
|
-
}));
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
writeFileSync13(BACKFILL_LOCK, String(process.pid));
|
|
8132
|
+
return true;
|
|
8133
|
+
} catch {
|
|
8134
|
+
return true;
|
|
8135
|
+
}
|
|
8136
|
+
}
|
|
8137
|
+
function releaseBackfillLock() {
|
|
8138
|
+
try {
|
|
8139
|
+
unlinkSync8(BACKFILL_LOCK);
|
|
8140
|
+
} catch {
|
|
8141
|
+
}
|
|
7668
8142
|
}
|
|
8143
|
+
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS, BACKFILL_LOCK;
|
|
8144
|
+
var init_worker_gate = __esm({
|
|
8145
|
+
"src/lib/worker-gate.ts"() {
|
|
8146
|
+
"use strict";
|
|
8147
|
+
init_config();
|
|
8148
|
+
WORKER_PID_DIR = path28.join(EXE_AI_DIR, "worker-pids");
|
|
8149
|
+
MAX_CONCURRENT_WORKERS = 3;
|
|
8150
|
+
BACKFILL_LOCK = path28.join(WORKER_PID_DIR, "backfill.lock");
|
|
8151
|
+
}
|
|
8152
|
+
});
|
|
8153
|
+
|
|
8154
|
+
// src/mcp/server.ts
|
|
8155
|
+
init_embedder();
|
|
8156
|
+
init_store();
|
|
8157
|
+
init_database();
|
|
8158
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8159
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8160
|
+
import { spawn as spawn2 } from "child_process";
|
|
8161
|
+
import { existsSync as existsSync22, openSync as openSync2, mkdirSync as mkdirSync12, closeSync as closeSync2 } from "fs";
|
|
8162
|
+
import path29 from "path";
|
|
8163
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7669
8164
|
|
|
7670
8165
|
// src/mcp/tools/recall-my-memory.ts
|
|
8166
|
+
init_hybrid_search();
|
|
7671
8167
|
init_active_agent();
|
|
8168
|
+
import { z } from "zod";
|
|
7672
8169
|
function formatSourceLine(record) {
|
|
7673
8170
|
const doc = record.document_metadata;
|
|
7674
8171
|
if (!doc) return "";
|
|
@@ -7727,6 +8224,7 @@ function registerRecallMyMemory(server2) {
|
|
|
7727
8224
|
includeArchived: include_archived,
|
|
7728
8225
|
workspaceId: workspace_id,
|
|
7729
8226
|
includeSource: include_source,
|
|
8227
|
+
includeDrafts: true,
|
|
7730
8228
|
...user_id !== void 0 ? { userId: user_id } : {}
|
|
7731
8229
|
};
|
|
7732
8230
|
const results = await hybridSearch(query, agentId, searchOptions);
|
|
@@ -7774,7 +8272,21 @@ ${formatted}`
|
|
|
7774
8272
|
}
|
|
7775
8273
|
|
|
7776
8274
|
// src/mcp/tools/ask-team-memory.ts
|
|
8275
|
+
init_hybrid_search();
|
|
8276
|
+
init_active_agent();
|
|
8277
|
+
init_employees();
|
|
7777
8278
|
import { z as z2 } from "zod";
|
|
8279
|
+
function canSeeRaw(queryingRole, targetRole) {
|
|
8280
|
+
if (queryingRole === "COO") return true;
|
|
8281
|
+
if (queryingRole === "CTO" && [
|
|
8282
|
+
"Principal Engineer",
|
|
8283
|
+
"Staff Code Reviewer",
|
|
8284
|
+
"Content Production Specialist",
|
|
8285
|
+
"CMO"
|
|
8286
|
+
].includes(targetRole))
|
|
8287
|
+
return true;
|
|
8288
|
+
return false;
|
|
8289
|
+
}
|
|
7778
8290
|
function registerAskTeamMemory(server2) {
|
|
7779
8291
|
server2.registerTool(
|
|
7780
8292
|
"ask_team_memory",
|
|
@@ -7783,7 +8295,7 @@ function registerAskTeamMemory(server2) {
|
|
|
7783
8295
|
description: "Search another employee's memories. Use this to find what a team member worked on, learned, or solved.",
|
|
7784
8296
|
inputSchema: {
|
|
7785
8297
|
team_member: z2.string().describe(
|
|
7786
|
-
"Name of the team member to query
|
|
8298
|
+
"Name of the team member to query"
|
|
7787
8299
|
),
|
|
7788
8300
|
query: z2.string().describe("What to search for"),
|
|
7789
8301
|
project_name: z2.string().optional().describe("Filter by project name"),
|
|
@@ -7791,16 +8303,28 @@ function registerAskTeamMemory(server2) {
|
|
|
7791
8303
|
since: z2.string().optional().describe("ISO 8601 timestamp \u2014 only return memories at or after this time"),
|
|
7792
8304
|
include_archived: z2.boolean().optional().default(false).describe(
|
|
7793
8305
|
"Include deprecated (archived) memories alongside active ones. Default false."
|
|
8306
|
+
),
|
|
8307
|
+
include_raw: z2.boolean().optional().default(false).describe(
|
|
8308
|
+
"Include raw technical memories (default: ADR summaries only for cross-agent reads)"
|
|
7794
8309
|
)
|
|
7795
8310
|
}
|
|
7796
8311
|
},
|
|
7797
|
-
async ({ team_member, query, project_name, limit, since, include_archived }) => {
|
|
8312
|
+
async ({ team_member, query, project_name, limit, since, include_archived, include_raw }) => {
|
|
7798
8313
|
try {
|
|
8314
|
+
const { agentId: queryingAgentId, agentRole: queryingAgentRole } = getActiveAgent();
|
|
8315
|
+
const employees = loadEmployeesSync();
|
|
8316
|
+
const targetEmployee = getEmployee(employees, team_member);
|
|
8317
|
+
const queryingRole = queryingAgentRole ?? employees.find((e) => e.name === queryingAgentId)?.role ?? "";
|
|
8318
|
+
const targetRole = targetEmployee?.role ?? "";
|
|
8319
|
+
const hasRawAccess = canSeeRaw(queryingRole, targetRole);
|
|
8320
|
+
const effectiveIncludeRaw = include_raw && hasRawAccess;
|
|
7799
8321
|
const results = await hybridSearch(query, team_member, {
|
|
7800
8322
|
projectName: project_name,
|
|
7801
8323
|
limit,
|
|
7802
8324
|
since,
|
|
7803
|
-
includeArchived: include_archived
|
|
8325
|
+
includeArchived: include_archived,
|
|
8326
|
+
includeDrafts: false,
|
|
8327
|
+
memoryType: effectiveIncludeRaw ? void 0 : "adr"
|
|
7804
8328
|
});
|
|
7805
8329
|
if (results.length === 0) {
|
|
7806
8330
|
return {
|
|
@@ -8015,6 +8539,7 @@ init_embedder();
|
|
|
8015
8539
|
init_store();
|
|
8016
8540
|
init_plan_limits();
|
|
8017
8541
|
init_active_agent();
|
|
8542
|
+
init_employees();
|
|
8018
8543
|
import { z as z5 } from "zod";
|
|
8019
8544
|
import crypto3 from "crypto";
|
|
8020
8545
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
@@ -8067,6 +8592,7 @@ function registerCommitMemory(server2) {
|
|
|
8067
8592
|
}
|
|
8068
8593
|
const memoryId = crypto3.randomUUID();
|
|
8069
8594
|
await assertMemoryLimit();
|
|
8595
|
+
const memoryType = canCoordinate(agentId, agentRole) ? "adr" : "raw";
|
|
8070
8596
|
await writeMemory({
|
|
8071
8597
|
id: memoryId,
|
|
8072
8598
|
agent_id: agentId,
|
|
@@ -8080,7 +8606,8 @@ function registerCommitMemory(server2) {
|
|
|
8080
8606
|
vector,
|
|
8081
8607
|
importance: importanceOverride ?? 9,
|
|
8082
8608
|
status: "active",
|
|
8083
|
-
supersedes_id: supersedes_id ?? null
|
|
8609
|
+
supersedes_id: supersedes_id ?? null,
|
|
8610
|
+
memory_type: memoryType
|
|
8084
8611
|
});
|
|
8085
8612
|
await flushBatch();
|
|
8086
8613
|
if (supersedes_id) {
|
|
@@ -8259,10 +8786,10 @@ function registerQueryRelationships(server2) {
|
|
|
8259
8786
|
"query_relationships",
|
|
8260
8787
|
{
|
|
8261
8788
|
title: "Query Relationships",
|
|
8262
|
-
description: "Query the knowledge graph for entity relationships, decision chains, and connections between concepts, people, and projects. Use this to answer relational questions like 'How did our auth strategy evolve?' or 'What did
|
|
8789
|
+
description: "Query the knowledge graph for entity relationships, decision chains, and connections between concepts, people, and projects. Use this to answer relational questions like 'How did our auth strategy evolve?' or 'What did the CTO work on this week?'",
|
|
8263
8790
|
inputSchema: {
|
|
8264
8791
|
query: z6.string().optional().describe("What relationships to find (natural language)"),
|
|
8265
|
-
entity_name: z6.string().optional().describe("Specific entity to explore (
|
|
8792
|
+
entity_name: z6.string().optional().describe("Specific entity to explore (for example an agent, project, or vendor name)"),
|
|
8266
8793
|
relationship_type: z6.string().optional().describe("Filter by relationship type (e.g., 'implemented', 'depends_on', 'worked_on')"),
|
|
8267
8794
|
max_depth: z6.number().default(2).describe("How many hops to traverse (default: 2)")
|
|
8268
8795
|
}
|
|
@@ -8527,6 +9054,7 @@ ${lines.join("\n")}`
|
|
|
8527
9054
|
init_tasks();
|
|
8528
9055
|
init_database();
|
|
8529
9056
|
init_tasks_crud();
|
|
9057
|
+
init_employees();
|
|
8530
9058
|
import { z as z9 } from "zod";
|
|
8531
9059
|
function registerUpdateTask(server2) {
|
|
8532
9060
|
server2.registerTool(
|
|
@@ -8546,7 +9074,7 @@ function registerUpdateTask(server2) {
|
|
|
8546
9074
|
try {
|
|
8547
9075
|
const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
8548
9076
|
const agent = getActiveAgent2();
|
|
8549
|
-
if (agent.agentId
|
|
9077
|
+
if (!canCoordinate(agent.agentId, agent.agentRole)) {
|
|
8550
9078
|
let isOwnReview = false;
|
|
8551
9079
|
try {
|
|
8552
9080
|
const client = getClient();
|
|
@@ -8573,7 +9101,7 @@ function registerUpdateTask(server2) {
|
|
|
8573
9101
|
if (assignedBy === "system" && taskFile.includes("review-")) {
|
|
8574
9102
|
const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
8575
9103
|
const agent = getActiveAgent2();
|
|
8576
|
-
if (agent.agentId !== assignedTo && agent.agentId
|
|
9104
|
+
if (agent.agentId !== assignedTo && !canCoordinate(agent.agentId, agent.agentRole)) {
|
|
8577
9105
|
process.stderr.write(
|
|
8578
9106
|
`[update_task] BLOCKED: ${agent.agentId} tried to close review "${String(row.title)}" assigned to ${assignedTo}
|
|
8579
9107
|
`
|
|
@@ -8596,13 +9124,20 @@ function registerUpdateTask(server2) {
|
|
|
8596
9124
|
};
|
|
8597
9125
|
}
|
|
8598
9126
|
}
|
|
9127
|
+
let callerAgentId;
|
|
9128
|
+
try {
|
|
9129
|
+
const { getActiveAgent: getAgent } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
|
|
9130
|
+
callerAgentId = getAgent().agentId;
|
|
9131
|
+
} catch {
|
|
9132
|
+
}
|
|
8599
9133
|
let task;
|
|
8600
9134
|
try {
|
|
8601
9135
|
task = await updateTask({
|
|
8602
9136
|
taskId: task_id,
|
|
8603
9137
|
status,
|
|
8604
9138
|
result,
|
|
8605
|
-
baseDir: process.cwd()
|
|
9139
|
+
baseDir: process.cwd(),
|
|
9140
|
+
callerAgentId
|
|
8606
9141
|
});
|
|
8607
9142
|
} catch (err) {
|
|
8608
9143
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -8651,8 +9186,9 @@ All tasks complete. No more open tasks in your queue.`;
|
|
|
8651
9186
|
init_tasks();
|
|
8652
9187
|
init_active_agent();
|
|
8653
9188
|
init_tmux_routing();
|
|
9189
|
+
init_employees();
|
|
8654
9190
|
import { z as z10 } from "zod";
|
|
8655
|
-
var CLOSE_TASK_ALLOWED_ROLES = /* @__PURE__ */ new Set(["
|
|
9191
|
+
var CLOSE_TASK_ALLOWED_ROLES = /* @__PURE__ */ new Set(["CTO"]);
|
|
8656
9192
|
function registerCloseTask(server2) {
|
|
8657
9193
|
server2.registerTool(
|
|
8658
9194
|
"close_task",
|
|
@@ -8667,12 +9203,13 @@ function registerCloseTask(server2) {
|
|
|
8667
9203
|
},
|
|
8668
9204
|
async ({ task_id, result, status }) => {
|
|
8669
9205
|
const agent = getActiveAgent();
|
|
8670
|
-
|
|
9206
|
+
const canClose = canCoordinate(agent.agentId, agent.agentRole) || CLOSE_TASK_ALLOWED_ROLES.has(agent.agentRole ?? "");
|
|
9207
|
+
if (agent.agentId && !canClose) {
|
|
8671
9208
|
return {
|
|
8672
9209
|
content: [
|
|
8673
9210
|
{
|
|
8674
9211
|
type: "text",
|
|
8675
|
-
text: `close_task is for reviewers only
|
|
9212
|
+
text: `close_task is for reviewers and coordinators only. Use update_task with status "done" and your result summary to complete your work. This triggers a review task for the coordinator.`
|
|
8676
9213
|
}
|
|
8677
9214
|
],
|
|
8678
9215
|
isError: true
|
|
@@ -8691,7 +9228,7 @@ function registerCloseTask(server2) {
|
|
|
8691
9228
|
return {
|
|
8692
9229
|
content: [{
|
|
8693
9230
|
type: "text",
|
|
8694
|
-
text: `\u26A0\uFE0F Session scope mismatch: this task belongs to ${taskScope}, but you are ${mySession}. Reviews should be processed by the
|
|
9231
|
+
text: `\u26A0\uFE0F Session scope mismatch: this task belongs to ${taskScope}, but you are ${mySession}. Reviews should be processed by the coordinator session that dispatched the task. Switch to ${taskScope} to review this task.`
|
|
8695
9232
|
}],
|
|
8696
9233
|
isError: true
|
|
8697
9234
|
};
|
|
@@ -8738,6 +9275,7 @@ All tasks complete. No more open tasks in your queue.`;
|
|
|
8738
9275
|
// src/mcp/tools/get-task.ts
|
|
8739
9276
|
init_tasks();
|
|
8740
9277
|
init_database();
|
|
9278
|
+
init_employees();
|
|
8741
9279
|
import { z as z11 } from "zod";
|
|
8742
9280
|
function registerGetTask(server2) {
|
|
8743
9281
|
server2.registerTool(
|
|
@@ -8817,13 +9355,13 @@ function registerGetTask(server2) {
|
|
|
8817
9355
|
} catch {
|
|
8818
9356
|
}
|
|
8819
9357
|
}
|
|
8820
|
-
if (String(row.assigned_to) !== "exe" && !String(row.title).startsWith("Review:") && String(row.status) !== "done" && String(row.status) !== "cancelled") {
|
|
9358
|
+
if (String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !String(row.title).startsWith("Review:") && String(row.status) !== "done" && String(row.status) !== "cancelled") {
|
|
8821
9359
|
lines.push(
|
|
8822
9360
|
"",
|
|
8823
9361
|
"## MANDATORY: When done",
|
|
8824
9362
|
"",
|
|
8825
9363
|
'You MUST call update_task with status "done" and a result summary when finished.',
|
|
8826
|
-
"If you skip this,
|
|
9364
|
+
"If you skip this, your reviewer will not know you're done and your work won't be reviewed.",
|
|
8827
9365
|
"Do NOT let a failed commit or any error prevent you from calling update_task(done)."
|
|
8828
9366
|
);
|
|
8829
9367
|
}
|
|
@@ -8887,7 +9425,7 @@ function registerResumeEmployee(server2) {
|
|
|
8887
9425
|
title: "Resume Employee",
|
|
8888
9426
|
description: "Create or refresh a RESUME task after an employee hits context capacity. Dedupes against any existing active RESUME for the same agent so repeated capacity hits never produce duplicate rows. Preferred over raw create_task for the context-full handler.",
|
|
8889
9427
|
inputSchema: {
|
|
8890
|
-
agent_id: z13.string().describe("Employee name to resume
|
|
9428
|
+
agent_id: z13.string().describe("Employee name to resume"),
|
|
8891
9429
|
project_name: z13.string().describe("Project name \u2014 used to namespace the task")
|
|
8892
9430
|
}
|
|
8893
9431
|
},
|
|
@@ -8895,9 +9433,9 @@ function registerResumeEmployee(server2) {
|
|
|
8895
9433
|
const client = getClient();
|
|
8896
9434
|
const reScope = sessionScopeFilter();
|
|
8897
9435
|
const openTasks = await client.execute({
|
|
8898
|
-
sql: `SELECT id, title, priority, task_file, status
|
|
9436
|
+
sql: `SELECT id, title, priority, task_file, status, assigned_tmux
|
|
8899
9437
|
FROM tasks
|
|
8900
|
-
WHERE assigned_to = ? AND status IN ('open', 'in_progress')${reScope.sql}
|
|
9438
|
+
WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'blocked')${reScope.sql}
|
|
8901
9439
|
ORDER BY
|
|
8902
9440
|
CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
|
|
8903
9441
|
created_at ASC
|
|
@@ -8914,6 +9452,25 @@ function registerResumeEmployee(server2) {
|
|
|
8914
9452
|
]
|
|
8915
9453
|
};
|
|
8916
9454
|
}
|
|
9455
|
+
try {
|
|
9456
|
+
const { isTmuxSessionAlive: isTmuxSessionAlive2 } = await Promise.resolve().then(() => (init_tasks_crud(), tasks_crud_exports));
|
|
9457
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9458
|
+
for (const row of openTasks.rows) {
|
|
9459
|
+
const status = String(row.status);
|
|
9460
|
+
const assignedTmux = row.assigned_tmux != null ? String(row.assigned_tmux) : null;
|
|
9461
|
+
if (status === "in_progress" && assignedTmux && !isTmuxSessionAlive2(assignedTmux)) {
|
|
9462
|
+
await client.execute({
|
|
9463
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
9464
|
+
args: [now, String(row.id)]
|
|
9465
|
+
});
|
|
9466
|
+
process.stderr.write(
|
|
9467
|
+
`[resume_employee] Released dead claim on "${String(row.title)}" (was ${assignedTmux})
|
|
9468
|
+
`
|
|
9469
|
+
);
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
} catch {
|
|
9473
|
+
}
|
|
8917
9474
|
const { created, taskId } = await createOrRefreshResumeTask(
|
|
8918
9475
|
agent_id,
|
|
8919
9476
|
process.cwd(),
|
|
@@ -9002,7 +9559,7 @@ function registerSendMessage(server2) {
|
|
|
9002
9559
|
title: "Send Message",
|
|
9003
9560
|
description: "Send a structured message to another agent. Messages are queued and delivered via intercom. NOTE: messages are fire-and-forget \u2014 do NOT use for actionable work dispatch. Use create_task instead to assign work to employees.",
|
|
9004
9561
|
inputSchema: {
|
|
9005
|
-
target_agent: z15.string().describe("Recipient agent name
|
|
9562
|
+
target_agent: z15.string().describe("Recipient agent name"),
|
|
9006
9563
|
content: z15.string().describe("Message content"),
|
|
9007
9564
|
target_project: z15.string().optional().describe("Project context (optional)"),
|
|
9008
9565
|
priority: z15.enum(["normal", "urgent"]).default("normal").describe("Message priority (default: normal)"),
|
|
@@ -9408,12 +9965,13 @@ ${identity.body}`
|
|
|
9408
9965
|
// src/mcp/tools/update-identity.ts
|
|
9409
9966
|
import { z as z21 } from "zod";
|
|
9410
9967
|
init_active_agent();
|
|
9968
|
+
init_employees();
|
|
9411
9969
|
function registerUpdateIdentity(server2) {
|
|
9412
9970
|
server2.registerTool(
|
|
9413
9971
|
"update_identity",
|
|
9414
9972
|
{
|
|
9415
9973
|
title: "Update Identity",
|
|
9416
|
-
description: "Write an agent's exe.md identity. RESTRICTED: only
|
|
9974
|
+
description: "Write an agent's exe.md identity. RESTRICTED: only coordinator or founder sessions can use this. Agents cannot rewrite their own identity. Content should be markdown with YAML frontmatter.",
|
|
9417
9975
|
inputSchema: {
|
|
9418
9976
|
agent_id: z21.string().describe("Target agent whose identity to write"),
|
|
9419
9977
|
content: z21.string().describe(
|
|
@@ -9423,12 +9981,12 @@ function registerUpdateIdentity(server2) {
|
|
|
9423
9981
|
},
|
|
9424
9982
|
async ({ agent_id, content }) => {
|
|
9425
9983
|
const caller = getActiveAgent();
|
|
9426
|
-
const allowed = caller.agentId
|
|
9984
|
+
const allowed = canCoordinate(caller.agentId, caller.agentRole);
|
|
9427
9985
|
if (!allowed) {
|
|
9428
9986
|
return {
|
|
9429
9987
|
content: [{
|
|
9430
9988
|
type: "text",
|
|
9431
|
-
text: `Permission denied. Only
|
|
9989
|
+
text: `Permission denied. Only coordinator or founder sessions can update identities. You are "${caller.agentId}". Agents cannot rewrite their own identity \u2014 this is an architectural constraint, not a bug.`
|
|
9432
9990
|
}],
|
|
9433
9991
|
isError: true
|
|
9434
9992
|
};
|
|
@@ -9459,25 +10017,26 @@ function registerUpdateIdentity(server2) {
|
|
|
9459
10017
|
init_behaviors();
|
|
9460
10018
|
init_active_agent();
|
|
9461
10019
|
init_database();
|
|
10020
|
+
init_employees();
|
|
9462
10021
|
import { z as z22 } from "zod";
|
|
9463
10022
|
function registerDeactivateBehavior(server2) {
|
|
9464
10023
|
server2.registerTool(
|
|
9465
10024
|
"deactivate_behavior",
|
|
9466
10025
|
{
|
|
9467
10026
|
title: "Deactivate Behavior",
|
|
9468
|
-
description: "Soft-delete a behavior by setting active = 0. RESTRICTED: only
|
|
10027
|
+
description: "Soft-delete a behavior by setting active = 0. RESTRICTED: only coordinator or founder sessions can use this. Use list_behaviors to find the behavior ID first.",
|
|
9469
10028
|
inputSchema: {
|
|
9470
10029
|
behavior_id: z22.string().describe("UUID of the behavior to deactivate")
|
|
9471
10030
|
}
|
|
9472
10031
|
},
|
|
9473
10032
|
async ({ behavior_id }) => {
|
|
9474
10033
|
const caller = getActiveAgent();
|
|
9475
|
-
const allowed = caller.agentId
|
|
10034
|
+
const allowed = canCoordinate(caller.agentId, caller.agentRole);
|
|
9476
10035
|
if (!allowed) {
|
|
9477
10036
|
return {
|
|
9478
10037
|
content: [{
|
|
9479
10038
|
type: "text",
|
|
9480
|
-
text: `Permission denied. Only
|
|
10039
|
+
text: `Permission denied. Only the coordinator or founder sessions can deactivate behaviors. You are "${caller.agentId}".`
|
|
9481
10040
|
}],
|
|
9482
10041
|
isError: true
|
|
9483
10042
|
};
|
|
@@ -9971,7 +10530,7 @@ function registerAcknowledgeMessages(server2) {
|
|
|
9971
10530
|
},
|
|
9972
10531
|
async () => {
|
|
9973
10532
|
const agent = getActiveAgent();
|
|
9974
|
-
const agentId = agent.agentId || "
|
|
10533
|
+
const agentId = agent.agentId || "default";
|
|
9975
10534
|
const client = getClient();
|
|
9976
10535
|
const result = await client.execute({
|
|
9977
10536
|
sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
|
|
@@ -12307,13 +12866,14 @@ Consolidated summaries stored as tier-1 (importance=9) memories.`
|
|
|
12307
12866
|
// src/mcp/tools/store-global-procedure.ts
|
|
12308
12867
|
init_global_procedures();
|
|
12309
12868
|
init_active_agent();
|
|
12869
|
+
init_employees();
|
|
12310
12870
|
import { z as z41 } from "zod";
|
|
12311
12871
|
function registerStoreGlobalProcedure(server2) {
|
|
12312
12872
|
server2.registerTool(
|
|
12313
12873
|
"store_global_procedure",
|
|
12314
12874
|
{
|
|
12315
12875
|
title: "Store Global Procedure",
|
|
12316
|
-
description: "Create an organization-wide procedure (Layer 0) that supersedes identity, expertise, and experience. Use for hard rules that every employee must follow. RESTRICTED: only
|
|
12876
|
+
description: "Create an organization-wide procedure (Layer 0) that supersedes identity, expertise, and experience. Use for hard rules that every employee must follow. RESTRICTED: only coordinator or founder sessions.",
|
|
12317
12877
|
inputSchema: {
|
|
12318
12878
|
title: z41.string().describe("Short title for the procedure"),
|
|
12319
12879
|
content: z41.string().max(500).describe("The procedure content \u2014 clear, actionable instruction"),
|
|
@@ -12323,12 +12883,12 @@ function registerStoreGlobalProcedure(server2) {
|
|
|
12323
12883
|
},
|
|
12324
12884
|
async ({ title, content, priority, domain }) => {
|
|
12325
12885
|
const caller = getActiveAgent();
|
|
12326
|
-
const allowed = caller.agentId
|
|
12886
|
+
const allowed = canCoordinate(caller.agentId, caller.agentRole);
|
|
12327
12887
|
if (!allowed) {
|
|
12328
12888
|
return {
|
|
12329
12889
|
content: [{
|
|
12330
12890
|
type: "text",
|
|
12331
|
-
text: `Permission denied. Only
|
|
12891
|
+
text: `Permission denied. Only the coordinator or founder sessions can create global procedures. You are "${caller.agentId}".`
|
|
12332
12892
|
}],
|
|
12333
12893
|
isError: true
|
|
12334
12894
|
};
|
|
@@ -12395,25 +12955,26 @@ ${lines.join("\n\n")}`
|
|
|
12395
12955
|
init_global_procedures();
|
|
12396
12956
|
init_active_agent();
|
|
12397
12957
|
init_database();
|
|
12958
|
+
init_employees();
|
|
12398
12959
|
import { z as z42 } from "zod";
|
|
12399
12960
|
function registerDeactivateGlobalProcedure(server2) {
|
|
12400
12961
|
server2.registerTool(
|
|
12401
12962
|
"deactivate_global_procedure",
|
|
12402
12963
|
{
|
|
12403
12964
|
title: "Deactivate Global Procedure",
|
|
12404
|
-
description: "Soft-delete a global procedure by setting active = 0. RESTRICTED: only
|
|
12965
|
+
description: "Soft-delete a global procedure by setting active = 0. RESTRICTED: only coordinator or founder sessions. Use list_global_procedures to find the procedure ID first.",
|
|
12405
12966
|
inputSchema: {
|
|
12406
12967
|
procedure_id: z42.string().describe("UUID of the global procedure to deactivate")
|
|
12407
12968
|
}
|
|
12408
12969
|
},
|
|
12409
12970
|
async ({ procedure_id }) => {
|
|
12410
12971
|
const caller = getActiveAgent();
|
|
12411
|
-
const allowed = caller.agentId
|
|
12972
|
+
const allowed = canCoordinate(caller.agentId, caller.agentRole);
|
|
12412
12973
|
if (!allowed) {
|
|
12413
12974
|
return {
|
|
12414
12975
|
content: [{
|
|
12415
12976
|
type: "text",
|
|
12416
|
-
text: `Permission denied. Only
|
|
12977
|
+
text: `Permission denied. Only coordinator or founder sessions can deactivate global procedures. You are "${caller.agentId}".`
|
|
12417
12978
|
}],
|
|
12418
12979
|
isError: true
|
|
12419
12980
|
};
|
|
@@ -12457,6 +13018,392 @@ Content: ${row.content}`
|
|
|
12457
13018
|
);
|
|
12458
13019
|
}
|
|
12459
13020
|
|
|
13021
|
+
// src/mcp/tools/search-everything.ts
|
|
13022
|
+
import { z as z43 } from "zod";
|
|
13023
|
+
|
|
13024
|
+
// src/lib/unified-search.ts
|
|
13025
|
+
var DEFAULT_LIMIT = 10;
|
|
13026
|
+
var MAX_SNIPPET = 500;
|
|
13027
|
+
var ALL_SOURCES = ["memory", "conversations", "wiki"];
|
|
13028
|
+
function truncate(text, max) {
|
|
13029
|
+
if (text.length <= max) return text;
|
|
13030
|
+
return text.slice(0, max - 1) + "\u2026";
|
|
13031
|
+
}
|
|
13032
|
+
async function searchMemories2(query, agentId, limit) {
|
|
13033
|
+
const { hybridSearch: hybridSearch2 } = await Promise.resolve().then(() => (init_hybrid_search(), hybrid_search_exports));
|
|
13034
|
+
const results = await hybridSearch2(query, agentId, { limit });
|
|
13035
|
+
return results.map((r, i) => ({
|
|
13036
|
+
source: "memory",
|
|
13037
|
+
score: 1 - i / Math.max(results.length, 1),
|
|
13038
|
+
// rank-based score
|
|
13039
|
+
timestamp: r.timestamp,
|
|
13040
|
+
snippet: truncate(r.raw_text, MAX_SNIPPET),
|
|
13041
|
+
entityLinks: [],
|
|
13042
|
+
metadata: {
|
|
13043
|
+
id: r.id,
|
|
13044
|
+
tool_name: r.tool_name,
|
|
13045
|
+
project_name: r.project_name,
|
|
13046
|
+
agent_id: r.agent_id,
|
|
13047
|
+
has_error: r.has_error
|
|
13048
|
+
}
|
|
13049
|
+
}));
|
|
13050
|
+
}
|
|
13051
|
+
async function searchConversations(query, limit) {
|
|
13052
|
+
try {
|
|
13053
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
13054
|
+
const client = getClient2();
|
|
13055
|
+
const result = await client.execute({
|
|
13056
|
+
sql: `SELECT c.id, c.platform, c.sender_id, c.sender_name,
|
|
13057
|
+
c.content_text, c.agent_response, c.agent_name, c.timestamp
|
|
13058
|
+
FROM conversations c
|
|
13059
|
+
WHERE c.rowid IN (SELECT rowid FROM conversations_fts WHERE conversations_fts MATCH ?)
|
|
13060
|
+
ORDER BY c.timestamp DESC
|
|
13061
|
+
LIMIT ?`,
|
|
13062
|
+
args: [query, limit]
|
|
13063
|
+
});
|
|
13064
|
+
return result.rows.map((row, i) => ({
|
|
13065
|
+
source: "conversations",
|
|
13066
|
+
score: 1 - i / Math.max(result.rows.length, 1),
|
|
13067
|
+
timestamp: row.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
13068
|
+
snippet: truncate(
|
|
13069
|
+
(row.content_text ?? "") + (row.agent_response ? ` \u2192 ${row.agent_response}` : ""),
|
|
13070
|
+
MAX_SNIPPET
|
|
13071
|
+
),
|
|
13072
|
+
entityLinks: [],
|
|
13073
|
+
metadata: {
|
|
13074
|
+
id: row.id,
|
|
13075
|
+
platform: row.platform,
|
|
13076
|
+
sender_id: row.sender_id,
|
|
13077
|
+
sender_name: row.sender_name,
|
|
13078
|
+
agent_name: row.agent_name
|
|
13079
|
+
}
|
|
13080
|
+
}));
|
|
13081
|
+
} catch {
|
|
13082
|
+
return [];
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
async function searchWiki(query, limit) {
|
|
13086
|
+
try {
|
|
13087
|
+
const { createWikiClient: createWikiClient2, chatInWorkspace: chatInWorkspace2, listWorkspaces: listWorkspaces2 } = await Promise.resolve().then(() => (init_wiki_client(), wiki_client_exports));
|
|
13088
|
+
const client = await createWikiClient2();
|
|
13089
|
+
if (!client) return [];
|
|
13090
|
+
const workspaces = await listWorkspaces2(client);
|
|
13091
|
+
if (workspaces.length === 0) return [];
|
|
13092
|
+
const results = [];
|
|
13093
|
+
const wsToQuery = workspaces.slice(0, 3);
|
|
13094
|
+
for (const ws of wsToQuery) {
|
|
13095
|
+
if (results.length >= limit) break;
|
|
13096
|
+
try {
|
|
13097
|
+
const response = await chatInWorkspace2(client, ws.slug, query, "query");
|
|
13098
|
+
if (response.textResponse && !response.error) {
|
|
13099
|
+
results.push({
|
|
13100
|
+
source: "wiki",
|
|
13101
|
+
score: 0.8,
|
|
13102
|
+
// wiki query mode relevance is implicit
|
|
13103
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13104
|
+
snippet: truncate(response.textResponse, MAX_SNIPPET),
|
|
13105
|
+
entityLinks: [],
|
|
13106
|
+
metadata: {
|
|
13107
|
+
workspace: ws.slug,
|
|
13108
|
+
workspace_name: ws.name,
|
|
13109
|
+
sources: response.sources.map((s) => s.title)
|
|
13110
|
+
}
|
|
13111
|
+
});
|
|
13112
|
+
}
|
|
13113
|
+
} catch {
|
|
13114
|
+
}
|
|
13115
|
+
}
|
|
13116
|
+
return results.slice(0, limit);
|
|
13117
|
+
} catch {
|
|
13118
|
+
return [];
|
|
13119
|
+
}
|
|
13120
|
+
}
|
|
13121
|
+
async function enrichWithEntities(results) {
|
|
13122
|
+
try {
|
|
13123
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
13124
|
+
const client = getClient2();
|
|
13125
|
+
const memoryIds = results.filter((r) => r.source === "memory" && r.metadata.id).map((r) => r.metadata.id);
|
|
13126
|
+
if (memoryIds.length === 0) return;
|
|
13127
|
+
const placeholders = memoryIds.map(() => "?").join(",");
|
|
13128
|
+
const entityResult = await client.execute({
|
|
13129
|
+
sql: `SELECT em.memory_id, e.name, e.entity_type
|
|
13130
|
+
FROM entity_memories em
|
|
13131
|
+
JOIN entities e ON e.id = em.entity_id
|
|
13132
|
+
WHERE em.memory_id IN (${placeholders})`,
|
|
13133
|
+
args: memoryIds
|
|
13134
|
+
});
|
|
13135
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
13136
|
+
for (const row of entityResult.rows) {
|
|
13137
|
+
const mid = row.memory_id;
|
|
13138
|
+
if (!entityMap.has(mid)) entityMap.set(mid, []);
|
|
13139
|
+
entityMap.get(mid).push({
|
|
13140
|
+
name: row.name,
|
|
13141
|
+
type: row.entity_type
|
|
13142
|
+
});
|
|
13143
|
+
}
|
|
13144
|
+
for (const r of results) {
|
|
13145
|
+
if (r.source === "memory" && r.metadata.id) {
|
|
13146
|
+
r.entityLinks = entityMap.get(r.metadata.id) ?? [];
|
|
13147
|
+
}
|
|
13148
|
+
}
|
|
13149
|
+
} catch {
|
|
13150
|
+
}
|
|
13151
|
+
}
|
|
13152
|
+
async function unifiedSearch(query, agentId, opts) {
|
|
13153
|
+
const limit = opts?.limit ?? DEFAULT_LIMIT;
|
|
13154
|
+
const sources = opts?.sources ?? ALL_SOURCES;
|
|
13155
|
+
const perSourceLimit = Math.max(Math.ceil(limit * 1.5), 5);
|
|
13156
|
+
const promises = [];
|
|
13157
|
+
if (sources.includes("memory")) {
|
|
13158
|
+
promises.push(searchMemories2(query, agentId, perSourceLimit));
|
|
13159
|
+
}
|
|
13160
|
+
if (sources.includes("conversations")) {
|
|
13161
|
+
promises.push(searchConversations(query, perSourceLimit));
|
|
13162
|
+
}
|
|
13163
|
+
if (sources.includes("wiki")) {
|
|
13164
|
+
promises.push(searchWiki(query, perSourceLimit));
|
|
13165
|
+
}
|
|
13166
|
+
const allResults = (await Promise.all(promises)).flat();
|
|
13167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13168
|
+
const deduped = allResults.filter((r) => {
|
|
13169
|
+
const key = r.snippet.slice(0, 100);
|
|
13170
|
+
if (seen.has(key)) return false;
|
|
13171
|
+
seen.add(key);
|
|
13172
|
+
return true;
|
|
13173
|
+
});
|
|
13174
|
+
deduped.sort((a, b) => b.score - a.score);
|
|
13175
|
+
const trimmed = deduped.slice(0, limit);
|
|
13176
|
+
await enrichWithEntities(trimmed);
|
|
13177
|
+
return trimmed;
|
|
13178
|
+
}
|
|
13179
|
+
|
|
13180
|
+
// src/mcp/tools/search-everything.ts
|
|
13181
|
+
init_active_agent();
|
|
13182
|
+
function registerSearchEverything(server2) {
|
|
13183
|
+
server2.registerTool(
|
|
13184
|
+
"search_everything",
|
|
13185
|
+
{
|
|
13186
|
+
title: "Search Everything",
|
|
13187
|
+
description: "Search across all data stores \u2014 memories, conversations, and wiki \u2014 in one query. Returns merged results sorted by relevance with entity links.",
|
|
13188
|
+
inputSchema: {
|
|
13189
|
+
query: z43.string().describe("What to search for across all data stores"),
|
|
13190
|
+
sources: z43.array(z43.enum(["memory", "conversations", "wiki"])).optional().describe(
|
|
13191
|
+
"Which sources to search (default: all). Options: memory, conversations, wiki"
|
|
13192
|
+
),
|
|
13193
|
+
limit: z43.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)")
|
|
13194
|
+
}
|
|
13195
|
+
},
|
|
13196
|
+
async (params) => {
|
|
13197
|
+
try {
|
|
13198
|
+
const { agentId } = getActiveAgent();
|
|
13199
|
+
const sources = params.sources;
|
|
13200
|
+
const results = await unifiedSearch(params.query, agentId, {
|
|
13201
|
+
limit: params.limit,
|
|
13202
|
+
sources
|
|
13203
|
+
});
|
|
13204
|
+
if (results.length === 0) {
|
|
13205
|
+
return {
|
|
13206
|
+
content: [
|
|
13207
|
+
{
|
|
13208
|
+
type: "text",
|
|
13209
|
+
text: "No results found across any data store."
|
|
13210
|
+
}
|
|
13211
|
+
]
|
|
13212
|
+
};
|
|
13213
|
+
}
|
|
13214
|
+
const formatted = results.map((r) => {
|
|
13215
|
+
const entityStr = r.entityLinks.length > 0 ? ` [entities: ${r.entityLinks.map((e) => `${e.name} (${e.type})`).join(", ")}]` : "";
|
|
13216
|
+
return `[${r.source}] (${(r.score * 100).toFixed(0)}%) ${r.timestamp}
|
|
13217
|
+
${r.snippet}${entityStr}`;
|
|
13218
|
+
});
|
|
13219
|
+
const sourceSummary = [...new Set(results.map((r) => r.source))].join(
|
|
13220
|
+
", "
|
|
13221
|
+
);
|
|
13222
|
+
return {
|
|
13223
|
+
content: [
|
|
13224
|
+
{
|
|
13225
|
+
type: "text",
|
|
13226
|
+
text: `Found ${results.length} results across ${sourceSummary}:
|
|
13227
|
+
|
|
13228
|
+
${formatted.join("\n\n---\n\n")}`
|
|
13229
|
+
}
|
|
13230
|
+
]
|
|
13231
|
+
};
|
|
13232
|
+
} catch (err) {
|
|
13233
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13234
|
+
return {
|
|
13235
|
+
content: [
|
|
13236
|
+
{
|
|
13237
|
+
type: "text",
|
|
13238
|
+
text: `Error searching: ${msg}`
|
|
13239
|
+
}
|
|
13240
|
+
],
|
|
13241
|
+
isError: true
|
|
13242
|
+
};
|
|
13243
|
+
}
|
|
13244
|
+
}
|
|
13245
|
+
);
|
|
13246
|
+
}
|
|
13247
|
+
|
|
13248
|
+
// src/mcp/tools/store-decision.ts
|
|
13249
|
+
init_embedder();
|
|
13250
|
+
init_store();
|
|
13251
|
+
init_active_agent();
|
|
13252
|
+
init_database();
|
|
13253
|
+
init_plan_limits();
|
|
13254
|
+
import { z as z44 } from "zod";
|
|
13255
|
+
import crypto14 from "crypto";
|
|
13256
|
+
function registerStoreDecision(server2) {
|
|
13257
|
+
server2.registerTool(
|
|
13258
|
+
"store_decision",
|
|
13259
|
+
{
|
|
13260
|
+
title: "Store Decision",
|
|
13261
|
+
description: "Store an authoritative decision keyed by domain. Use this when a decision is made that should be canonical \u2014 future lookups via get_decision return the latest decision for that domain. Supports supersession chains.",
|
|
13262
|
+
inputSchema: {
|
|
13263
|
+
domain: z44.string().describe(
|
|
13264
|
+
"Domain key, e.g. 'auth-strategy', 'db-migration-approach', 'api-versioning'"
|
|
13265
|
+
),
|
|
13266
|
+
decision: z44.string().describe("The decision text \u2014 what was decided"),
|
|
13267
|
+
rationale: z44.string().optional().describe("Why this decision was made \u2014 constraints, trade-offs, context"),
|
|
13268
|
+
supersedes: z44.string().optional().describe("UUID of the decision this supersedes (previous decision for this domain)"),
|
|
13269
|
+
project_name: z44.string().optional().describe("Project name")
|
|
13270
|
+
}
|
|
13271
|
+
},
|
|
13272
|
+
async ({ domain, decision, rationale, supersedes, project_name }) => {
|
|
13273
|
+
const { agentId, agentRole } = getActiveAgent();
|
|
13274
|
+
try {
|
|
13275
|
+
await assertMemoryLimit();
|
|
13276
|
+
} catch (err) {
|
|
13277
|
+
if (err instanceof PlanLimitError) {
|
|
13278
|
+
return {
|
|
13279
|
+
content: [{ type: "text", text: err.message }],
|
|
13280
|
+
isError: true
|
|
13281
|
+
};
|
|
13282
|
+
}
|
|
13283
|
+
}
|
|
13284
|
+
const parts = [`[decision:${domain}] ${decision}`];
|
|
13285
|
+
if (rationale) parts.push(`Rationale: ${rationale}`);
|
|
13286
|
+
const rawText = parts.join("\n\n");
|
|
13287
|
+
let vector;
|
|
13288
|
+
try {
|
|
13289
|
+
vector = await embed(rawText);
|
|
13290
|
+
} catch {
|
|
13291
|
+
vector = null;
|
|
13292
|
+
}
|
|
13293
|
+
const memoryId = crypto14.randomUUID();
|
|
13294
|
+
if (supersedes) {
|
|
13295
|
+
try {
|
|
13296
|
+
const client = getClient();
|
|
13297
|
+
await client.execute({
|
|
13298
|
+
sql: "UPDATE memories SET status = 'archived' WHERE id = ? AND memory_type = 'decision'",
|
|
13299
|
+
args: [supersedes]
|
|
13300
|
+
});
|
|
13301
|
+
} catch {
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13304
|
+
await writeMemory({
|
|
13305
|
+
id: memoryId,
|
|
13306
|
+
agent_id: agentId,
|
|
13307
|
+
agent_role: agentRole,
|
|
13308
|
+
session_id: process.env.SESSION_ID ?? "manual",
|
|
13309
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13310
|
+
tool_name: "store_decision",
|
|
13311
|
+
project_name: project_name ?? "unknown",
|
|
13312
|
+
has_error: false,
|
|
13313
|
+
raw_text: rawText,
|
|
13314
|
+
vector,
|
|
13315
|
+
source_path: domain,
|
|
13316
|
+
memory_type: "decision",
|
|
13317
|
+
supersedes_id: supersedes ?? null,
|
|
13318
|
+
tier: 1,
|
|
13319
|
+
importance: 8
|
|
13320
|
+
});
|
|
13321
|
+
await flushBatch();
|
|
13322
|
+
return {
|
|
13323
|
+
content: [
|
|
13324
|
+
{
|
|
13325
|
+
type: "text",
|
|
13326
|
+
text: `Decision stored for domain "${domain}". ID: ${memoryId}${supersedes ? `
|
|
13327
|
+
Supersedes: ${supersedes}` : ""}`
|
|
13328
|
+
}
|
|
13329
|
+
]
|
|
13330
|
+
};
|
|
13331
|
+
}
|
|
13332
|
+
);
|
|
13333
|
+
}
|
|
13334
|
+
|
|
13335
|
+
// src/mcp/tools/get-decision.ts
|
|
13336
|
+
init_database();
|
|
13337
|
+
import { z as z45 } from "zod";
|
|
13338
|
+
function registerGetDecision(server2) {
|
|
13339
|
+
server2.registerTool(
|
|
13340
|
+
"get_decision",
|
|
13341
|
+
{
|
|
13342
|
+
title: "Get Decision",
|
|
13343
|
+
description: "Retrieve the latest authoritative decision for a domain. Returns the current active decision and the supersession history.",
|
|
13344
|
+
inputSchema: {
|
|
13345
|
+
domain: z45.string().describe(
|
|
13346
|
+
"Domain key to look up, e.g. 'auth-strategy', 'db-migration-approach'"
|
|
13347
|
+
)
|
|
13348
|
+
}
|
|
13349
|
+
},
|
|
13350
|
+
async ({ domain }) => {
|
|
13351
|
+
const client = getClient();
|
|
13352
|
+
const result = await client.execute({
|
|
13353
|
+
sql: `SELECT id, agent_id, timestamp, raw_text, status, supersedes_id, project_name
|
|
13354
|
+
FROM memories
|
|
13355
|
+
WHERE memory_type = 'decision' AND source_path = ?
|
|
13356
|
+
ORDER BY timestamp DESC`,
|
|
13357
|
+
args: [domain]
|
|
13358
|
+
});
|
|
13359
|
+
if (result.rows.length === 0) {
|
|
13360
|
+
return {
|
|
13361
|
+
content: [
|
|
13362
|
+
{
|
|
13363
|
+
type: "text",
|
|
13364
|
+
text: `No decisions found for domain "${domain}".`
|
|
13365
|
+
}
|
|
13366
|
+
]
|
|
13367
|
+
};
|
|
13368
|
+
}
|
|
13369
|
+
const decisions = result.rows.map((r) => ({
|
|
13370
|
+
id: String(r.id),
|
|
13371
|
+
agent_id: String(r.agent_id),
|
|
13372
|
+
timestamp: String(r.timestamp),
|
|
13373
|
+
raw_text: String(r.raw_text),
|
|
13374
|
+
status: String(r.status ?? "active"),
|
|
13375
|
+
supersedes_id: r.supersedes_id ? String(r.supersedes_id) : null,
|
|
13376
|
+
project_name: String(r.project_name)
|
|
13377
|
+
}));
|
|
13378
|
+
const current = decisions.find((d) => d.status === "active") ?? decisions[0];
|
|
13379
|
+
const history = decisions.filter((d) => d.id !== current.id);
|
|
13380
|
+
const lines = [];
|
|
13381
|
+
lines.push(`## Current Decision: ${domain}`);
|
|
13382
|
+
lines.push(`ID: ${current.id}`);
|
|
13383
|
+
lines.push(`By: ${current.agent_id} | ${current.timestamp}`);
|
|
13384
|
+
lines.push(`Project: ${current.project_name}`);
|
|
13385
|
+
lines.push(`Status: ${current.status}`);
|
|
13386
|
+
lines.push("");
|
|
13387
|
+
lines.push(current.raw_text);
|
|
13388
|
+
if (history.length > 0) {
|
|
13389
|
+
lines.push("");
|
|
13390
|
+
lines.push(`## Supersession History (${history.length} prior)`);
|
|
13391
|
+
for (const h of history) {
|
|
13392
|
+
lines.push("");
|
|
13393
|
+
lines.push(`---`);
|
|
13394
|
+
lines.push(`ID: ${h.id} | ${h.timestamp} | ${h.status}`);
|
|
13395
|
+
lines.push(`By: ${h.agent_id}`);
|
|
13396
|
+
if (h.supersedes_id) lines.push(`Supersedes: ${h.supersedes_id}`);
|
|
13397
|
+
lines.push(h.raw_text);
|
|
13398
|
+
}
|
|
13399
|
+
}
|
|
13400
|
+
return {
|
|
13401
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
13402
|
+
};
|
|
13403
|
+
}
|
|
13404
|
+
);
|
|
13405
|
+
}
|
|
13406
|
+
|
|
12460
13407
|
// src/lib/telemetry.ts
|
|
12461
13408
|
var ENABLED = process.env.EXE_TELEMETRY === "1";
|
|
12462
13409
|
var initialized = false;
|
|
@@ -12519,6 +13466,8 @@ var server = new McpServer({
|
|
|
12519
13466
|
version: "1.3.0"
|
|
12520
13467
|
});
|
|
12521
13468
|
var _backfillTimer = null;
|
|
13469
|
+
var _ppidWatchdog = null;
|
|
13470
|
+
var _shuttingDown = false;
|
|
12522
13471
|
instrumentServer(server);
|
|
12523
13472
|
registerRecallMyMemory(server);
|
|
12524
13473
|
registerAskTeamMemory(server);
|
|
@@ -12564,6 +13513,9 @@ registerConsolidateMemories(server);
|
|
|
12564
13513
|
registerStoreGlobalProcedure(server);
|
|
12565
13514
|
registerListGlobalProcedures(server);
|
|
12566
13515
|
registerDeactivateGlobalProcedure(server);
|
|
13516
|
+
registerSearchEverything(server);
|
|
13517
|
+
registerStoreDecision(server);
|
|
13518
|
+
registerGetDecision(server);
|
|
12567
13519
|
try {
|
|
12568
13520
|
await initStore();
|
|
12569
13521
|
process.stderr.write("[exe-os] MCP server starting...\n");
|
|
@@ -12579,6 +13531,31 @@ try {
|
|
|
12579
13531
|
const transport = new StdioServerTransport();
|
|
12580
13532
|
await server.connect(transport);
|
|
12581
13533
|
process.stderr.write("[exe-os] MCP server connected.\n");
|
|
13534
|
+
process.stdin.on("end", () => {
|
|
13535
|
+
process.stderr.write("[exe-os] stdin closed \u2014 parent process died. Exiting.\n");
|
|
13536
|
+
void shutdown("stdin_end");
|
|
13537
|
+
});
|
|
13538
|
+
process.stdin.on("close", () => {
|
|
13539
|
+
process.stderr.write("[exe-os] stdin pipe closed. Exiting.\n");
|
|
13540
|
+
void shutdown("stdin_close");
|
|
13541
|
+
});
|
|
13542
|
+
process.stdout.on("error", (err) => {
|
|
13543
|
+
if (err.code === "EPIPE") {
|
|
13544
|
+
process.stderr.write("[exe-os] stdout EPIPE \u2014 client disconnected. Exiting.\n");
|
|
13545
|
+
void shutdown("epipe");
|
|
13546
|
+
}
|
|
13547
|
+
});
|
|
13548
|
+
const originalPpid = process.ppid;
|
|
13549
|
+
_ppidWatchdog = setInterval(() => {
|
|
13550
|
+
if (process.ppid !== originalPpid) {
|
|
13551
|
+
process.stderr.write(
|
|
13552
|
+
`[exe-os] Parent PID changed (${originalPpid} \u2192 ${process.ppid}). Orphaned \u2014 exiting.
|
|
13553
|
+
`
|
|
13554
|
+
);
|
|
13555
|
+
void shutdown("ppid_changed");
|
|
13556
|
+
}
|
|
13557
|
+
}, 3e4);
|
|
13558
|
+
_ppidWatchdog.unref();
|
|
12582
13559
|
const BACKFILL_CHECK_MS = 5 * 60 * 1e3;
|
|
12583
13560
|
_backfillTimer = setInterval(async () => {
|
|
12584
13561
|
try {
|
|
@@ -12638,14 +13615,31 @@ try {
|
|
|
12638
13615
|
);
|
|
12639
13616
|
process.exit(1);
|
|
12640
13617
|
}
|
|
12641
|
-
async function shutdown() {
|
|
13618
|
+
async function shutdown(reason = "signal") {
|
|
13619
|
+
if (_shuttingDown) return;
|
|
13620
|
+
_shuttingDown = true;
|
|
13621
|
+
process.stderr.write(`[exe-os] Shutdown initiated (reason: ${reason})
|
|
13622
|
+
`);
|
|
12642
13623
|
if (_backfillTimer) clearInterval(_backfillTimer);
|
|
12643
|
-
|
|
12644
|
-
|
|
13624
|
+
if (_ppidWatchdog) clearInterval(_ppidWatchdog);
|
|
13625
|
+
const hardExit = setTimeout(() => {
|
|
13626
|
+
process.stderr.write("[exe-os] Shutdown timeout (5s) \u2014 forcing exit.\n");
|
|
13627
|
+
process.exit(1);
|
|
13628
|
+
}, 5e3);
|
|
13629
|
+
hardExit.unref();
|
|
13630
|
+
try {
|
|
13631
|
+
await disposeStore();
|
|
13632
|
+
await disposeEmbedder();
|
|
13633
|
+
} catch (err) {
|
|
13634
|
+
process.stderr.write(
|
|
13635
|
+
`[exe-os] Shutdown cleanup error: ${err instanceof Error ? err.message : String(err)}
|
|
13636
|
+
`
|
|
13637
|
+
);
|
|
13638
|
+
}
|
|
12645
13639
|
process.exit(0);
|
|
12646
13640
|
}
|
|
12647
|
-
process.on("SIGINT", () => void shutdown());
|
|
12648
|
-
process.on("SIGTERM", () => void shutdown());
|
|
13641
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
13642
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
12649
13643
|
process.on("uncaughtException", (err) => {
|
|
12650
13644
|
process.stderr.write(
|
|
12651
13645
|
`[exe-os] uncaughtException (non-fatal): ${err.message}
|