@askexenow/exe-os 0.8.83 → 0.8.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
|
@@ -606,6 +606,443 @@ var init_employees = __esm({
|
|
|
606
606
|
}
|
|
607
607
|
});
|
|
608
608
|
|
|
609
|
+
// src/lib/exe-daemon-client.ts
|
|
610
|
+
import net from "net";
|
|
611
|
+
import { spawn } from "child_process";
|
|
612
|
+
import { randomUUID } from "crypto";
|
|
613
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
614
|
+
import path5 from "path";
|
|
615
|
+
import { fileURLToPath } from "url";
|
|
616
|
+
function handleData(chunk) {
|
|
617
|
+
_buffer += chunk.toString();
|
|
618
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
619
|
+
_buffer = "";
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
let newlineIdx;
|
|
623
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
624
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
625
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
626
|
+
if (!line) continue;
|
|
627
|
+
try {
|
|
628
|
+
const response = JSON.parse(line);
|
|
629
|
+
const id = response.id;
|
|
630
|
+
if (!id) continue;
|
|
631
|
+
const entry = _pending.get(id);
|
|
632
|
+
if (entry) {
|
|
633
|
+
clearTimeout(entry.timer);
|
|
634
|
+
_pending.delete(id);
|
|
635
|
+
entry.resolve(response);
|
|
636
|
+
}
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function cleanupStaleFiles() {
|
|
642
|
+
if (existsSync5(PID_PATH)) {
|
|
643
|
+
try {
|
|
644
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
645
|
+
if (pid > 0) {
|
|
646
|
+
try {
|
|
647
|
+
process.kill(pid, 0);
|
|
648
|
+
return;
|
|
649
|
+
} catch {
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
unlinkSync2(PID_PATH);
|
|
656
|
+
} catch {
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
unlinkSync2(SOCKET_PATH);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function findPackageRoot() {
|
|
665
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
666
|
+
const { root } = path5.parse(dir);
|
|
667
|
+
while (dir !== root) {
|
|
668
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
669
|
+
dir = path5.dirname(dir);
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
function spawnDaemon() {
|
|
674
|
+
const pkgRoot = findPackageRoot();
|
|
675
|
+
if (!pkgRoot) {
|
|
676
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
680
|
+
if (!existsSync5(daemonPath)) {
|
|
681
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
682
|
+
`);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const resolvedPath = daemonPath;
|
|
686
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
687
|
+
`);
|
|
688
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
689
|
+
let stderrFd = "ignore";
|
|
690
|
+
try {
|
|
691
|
+
stderrFd = openSync(logPath, "a");
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
695
|
+
detached: true,
|
|
696
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
697
|
+
env: {
|
|
698
|
+
...process.env,
|
|
699
|
+
TMUX: void 0,
|
|
700
|
+
// Daemon is global — must not inherit session scope
|
|
701
|
+
TMUX_PANE: void 0,
|
|
702
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
703
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
704
|
+
EXE_DAEMON_PID: PID_PATH
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
child.unref();
|
|
708
|
+
if (typeof stderrFd === "number") {
|
|
709
|
+
try {
|
|
710
|
+
closeSync(stderrFd);
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function acquireSpawnLock() {
|
|
716
|
+
try {
|
|
717
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
718
|
+
closeSync(fd);
|
|
719
|
+
return true;
|
|
720
|
+
} catch {
|
|
721
|
+
try {
|
|
722
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
723
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
724
|
+
try {
|
|
725
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
730
|
+
closeSync(fd);
|
|
731
|
+
return true;
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
} catch {
|
|
736
|
+
}
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function releaseSpawnLock() {
|
|
741
|
+
try {
|
|
742
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function connectToSocket() {
|
|
747
|
+
return new Promise((resolve) => {
|
|
748
|
+
if (_socket && _connected) {
|
|
749
|
+
resolve(true);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
753
|
+
const connectTimeout = setTimeout(() => {
|
|
754
|
+
socket.destroy();
|
|
755
|
+
resolve(false);
|
|
756
|
+
}, 2e3);
|
|
757
|
+
socket.on("connect", () => {
|
|
758
|
+
clearTimeout(connectTimeout);
|
|
759
|
+
_socket = socket;
|
|
760
|
+
_connected = true;
|
|
761
|
+
_buffer = "";
|
|
762
|
+
socket.on("data", handleData);
|
|
763
|
+
socket.on("close", () => {
|
|
764
|
+
_connected = false;
|
|
765
|
+
_socket = null;
|
|
766
|
+
for (const [id, entry] of _pending) {
|
|
767
|
+
clearTimeout(entry.timer);
|
|
768
|
+
_pending.delete(id);
|
|
769
|
+
entry.resolve({ error: "Connection closed" });
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
socket.on("error", () => {
|
|
773
|
+
_connected = false;
|
|
774
|
+
_socket = null;
|
|
775
|
+
});
|
|
776
|
+
resolve(true);
|
|
777
|
+
});
|
|
778
|
+
socket.on("error", () => {
|
|
779
|
+
clearTimeout(connectTimeout);
|
|
780
|
+
resolve(false);
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async function connectEmbedDaemon() {
|
|
785
|
+
if (_socket && _connected) return true;
|
|
786
|
+
if (await connectToSocket()) return true;
|
|
787
|
+
if (acquireSpawnLock()) {
|
|
788
|
+
try {
|
|
789
|
+
cleanupStaleFiles();
|
|
790
|
+
spawnDaemon();
|
|
791
|
+
} finally {
|
|
792
|
+
releaseSpawnLock();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const start = Date.now();
|
|
796
|
+
let delay2 = 100;
|
|
797
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
798
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
799
|
+
if (await connectToSocket()) return true;
|
|
800
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
801
|
+
}
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
805
|
+
return new Promise((resolve) => {
|
|
806
|
+
if (!_socket || !_connected) {
|
|
807
|
+
resolve({ error: "Not connected" });
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const id = randomUUID();
|
|
811
|
+
const timer = setTimeout(() => {
|
|
812
|
+
_pending.delete(id);
|
|
813
|
+
resolve({ error: "Request timeout" });
|
|
814
|
+
}, timeoutMs);
|
|
815
|
+
_pending.set(id, { resolve, timer });
|
|
816
|
+
try {
|
|
817
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
818
|
+
} catch {
|
|
819
|
+
clearTimeout(timer);
|
|
820
|
+
_pending.delete(id);
|
|
821
|
+
resolve({ error: "Write failed" });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
function isClientConnected() {
|
|
826
|
+
return _connected;
|
|
827
|
+
}
|
|
828
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
829
|
+
var init_exe_daemon_client = __esm({
|
|
830
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
831
|
+
"use strict";
|
|
832
|
+
init_config();
|
|
833
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
834
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
835
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
836
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
837
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
838
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
839
|
+
_socket = null;
|
|
840
|
+
_connected = false;
|
|
841
|
+
_buffer = "";
|
|
842
|
+
_pending = /* @__PURE__ */ new Map();
|
|
843
|
+
MAX_BUFFER = 1e7;
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// src/lib/daemon-protocol.ts
|
|
848
|
+
function serializeValue(v) {
|
|
849
|
+
if (v === null || v === void 0) return null;
|
|
850
|
+
if (typeof v === "bigint") return Number(v);
|
|
851
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
852
|
+
if (v instanceof Uint8Array) {
|
|
853
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
854
|
+
}
|
|
855
|
+
if (ArrayBuffer.isView(v)) {
|
|
856
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
857
|
+
}
|
|
858
|
+
if (v instanceof ArrayBuffer) {
|
|
859
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
860
|
+
}
|
|
861
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
862
|
+
return String(v);
|
|
863
|
+
}
|
|
864
|
+
function deserializeValue(v) {
|
|
865
|
+
if (v === null) return null;
|
|
866
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
867
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
868
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
869
|
+
}
|
|
870
|
+
return v;
|
|
871
|
+
}
|
|
872
|
+
function deserializeResultSet(srs) {
|
|
873
|
+
const rows = srs.rows.map((obj) => {
|
|
874
|
+
const values = srs.columns.map(
|
|
875
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
876
|
+
);
|
|
877
|
+
const row = values;
|
|
878
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
879
|
+
const col = srs.columns[i];
|
|
880
|
+
if (col !== void 0) {
|
|
881
|
+
row[col] = values[i] ?? null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
Object.defineProperty(row, "length", {
|
|
885
|
+
value: values.length,
|
|
886
|
+
enumerable: false
|
|
887
|
+
});
|
|
888
|
+
return row;
|
|
889
|
+
});
|
|
890
|
+
return {
|
|
891
|
+
columns: srs.columns,
|
|
892
|
+
columnTypes: srs.columnTypes ?? [],
|
|
893
|
+
rows,
|
|
894
|
+
rowsAffected: srs.rowsAffected,
|
|
895
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
896
|
+
toJSON: () => ({
|
|
897
|
+
columns: srs.columns,
|
|
898
|
+
columnTypes: srs.columnTypes ?? [],
|
|
899
|
+
rows: srs.rows,
|
|
900
|
+
rowsAffected: srs.rowsAffected,
|
|
901
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
902
|
+
})
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
var init_daemon_protocol = __esm({
|
|
906
|
+
"src/lib/daemon-protocol.ts"() {
|
|
907
|
+
"use strict";
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// src/lib/db-daemon-client.ts
|
|
912
|
+
var db_daemon_client_exports = {};
|
|
913
|
+
__export(db_daemon_client_exports, {
|
|
914
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
915
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
916
|
+
});
|
|
917
|
+
function normalizeStatement(stmt) {
|
|
918
|
+
if (typeof stmt === "string") {
|
|
919
|
+
return { sql: stmt, args: [] };
|
|
920
|
+
}
|
|
921
|
+
const sql = stmt.sql;
|
|
922
|
+
let args = [];
|
|
923
|
+
if (Array.isArray(stmt.args)) {
|
|
924
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
925
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
926
|
+
const named = {};
|
|
927
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
928
|
+
named[key] = serializeValue(val);
|
|
929
|
+
}
|
|
930
|
+
return { sql, args: named };
|
|
931
|
+
}
|
|
932
|
+
return { sql, args };
|
|
933
|
+
}
|
|
934
|
+
function createDaemonDbClient(fallbackClient) {
|
|
935
|
+
let _useDaemon = false;
|
|
936
|
+
const client = {
|
|
937
|
+
async execute(stmt) {
|
|
938
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
939
|
+
return fallbackClient.execute(stmt);
|
|
940
|
+
}
|
|
941
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
942
|
+
const response = await sendDaemonRequest({
|
|
943
|
+
type: "db-execute",
|
|
944
|
+
sql,
|
|
945
|
+
args
|
|
946
|
+
});
|
|
947
|
+
if (response.error) {
|
|
948
|
+
const errMsg = String(response.error);
|
|
949
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
950
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
951
|
+
`);
|
|
952
|
+
return fallbackClient.execute(stmt);
|
|
953
|
+
}
|
|
954
|
+
throw new Error(errMsg);
|
|
955
|
+
}
|
|
956
|
+
if (response.db) {
|
|
957
|
+
return deserializeResultSet(response.db);
|
|
958
|
+
}
|
|
959
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
960
|
+
return fallbackClient.execute(stmt);
|
|
961
|
+
},
|
|
962
|
+
async batch(stmts, mode) {
|
|
963
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
964
|
+
return fallbackClient.batch(stmts, mode);
|
|
965
|
+
}
|
|
966
|
+
const statements = stmts.map(normalizeStatement);
|
|
967
|
+
const response = await sendDaemonRequest({
|
|
968
|
+
type: "db-batch",
|
|
969
|
+
statements,
|
|
970
|
+
mode: mode ?? "deferred"
|
|
971
|
+
});
|
|
972
|
+
if (response.error) {
|
|
973
|
+
const errMsg = String(response.error);
|
|
974
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
975
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
976
|
+
`);
|
|
977
|
+
return fallbackClient.batch(stmts, mode);
|
|
978
|
+
}
|
|
979
|
+
throw new Error(errMsg);
|
|
980
|
+
}
|
|
981
|
+
const batchResults = response["db-batch"];
|
|
982
|
+
if (batchResults) {
|
|
983
|
+
return batchResults.map(deserializeResultSet);
|
|
984
|
+
}
|
|
985
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
986
|
+
return fallbackClient.batch(stmts, mode);
|
|
987
|
+
},
|
|
988
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
989
|
+
async transaction(mode) {
|
|
990
|
+
return fallbackClient.transaction(mode);
|
|
991
|
+
},
|
|
992
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
993
|
+
async executeMultiple(sql) {
|
|
994
|
+
return fallbackClient.executeMultiple(sql);
|
|
995
|
+
},
|
|
996
|
+
// migrate — delegate to fallback
|
|
997
|
+
async migrate(stmts) {
|
|
998
|
+
return fallbackClient.migrate(stmts);
|
|
999
|
+
},
|
|
1000
|
+
// Sync mode — delegate to fallback
|
|
1001
|
+
sync() {
|
|
1002
|
+
return fallbackClient.sync();
|
|
1003
|
+
},
|
|
1004
|
+
close() {
|
|
1005
|
+
_useDaemon = false;
|
|
1006
|
+
},
|
|
1007
|
+
get closed() {
|
|
1008
|
+
return fallbackClient.closed;
|
|
1009
|
+
},
|
|
1010
|
+
get protocol() {
|
|
1011
|
+
return fallbackClient.protocol;
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
return {
|
|
1015
|
+
...client,
|
|
1016
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1017
|
+
_enableDaemon() {
|
|
1018
|
+
_useDaemon = true;
|
|
1019
|
+
},
|
|
1020
|
+
/** Check if daemon routing is active */
|
|
1021
|
+
_isDaemonActive() {
|
|
1022
|
+
return _useDaemon && isClientConnected();
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1027
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1028
|
+
const connected = await connectEmbedDaemon();
|
|
1029
|
+
if (!connected) {
|
|
1030
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1034
|
+
client._enableDaemon();
|
|
1035
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1036
|
+
return client;
|
|
1037
|
+
}
|
|
1038
|
+
var init_db_daemon_client = __esm({
|
|
1039
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1040
|
+
"use strict";
|
|
1041
|
+
init_exe_daemon_client();
|
|
1042
|
+
init_daemon_protocol();
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
|
|
609
1046
|
// src/lib/database.ts
|
|
610
1047
|
var database_exports = {};
|
|
611
1048
|
__export(database_exports, {
|
|
@@ -614,6 +1051,7 @@ __export(database_exports, {
|
|
|
614
1051
|
ensureSchema: () => ensureSchema,
|
|
615
1052
|
getClient: () => getClient,
|
|
616
1053
|
getRawClient: () => getRawClient,
|
|
1054
|
+
initDaemonClient: () => initDaemonClient,
|
|
617
1055
|
initDatabase: () => initDatabase,
|
|
618
1056
|
initTurso: () => initTurso,
|
|
619
1057
|
isInitialized: () => isInitialized
|
|
@@ -641,8 +1079,27 @@ function getClient() {
|
|
|
641
1079
|
if (!_resilientClient) {
|
|
642
1080
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
643
1081
|
}
|
|
1082
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1083
|
+
return _resilientClient;
|
|
1084
|
+
}
|
|
1085
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1086
|
+
return _daemonClient;
|
|
1087
|
+
}
|
|
644
1088
|
return _resilientClient;
|
|
645
1089
|
}
|
|
1090
|
+
async function initDaemonClient() {
|
|
1091
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1092
|
+
if (!_resilientClient) return;
|
|
1093
|
+
try {
|
|
1094
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1095
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
process.stderr.write(
|
|
1098
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1099
|
+
`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
646
1103
|
function getRawClient() {
|
|
647
1104
|
if (!_client) {
|
|
648
1105
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1129,6 +1586,12 @@ async function ensureSchema() {
|
|
|
1129
1586
|
} catch {
|
|
1130
1587
|
}
|
|
1131
1588
|
}
|
|
1589
|
+
try {
|
|
1590
|
+
await client.execute(
|
|
1591
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1592
|
+
);
|
|
1593
|
+
} catch {
|
|
1594
|
+
}
|
|
1132
1595
|
await client.executeMultiple(`
|
|
1133
1596
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1134
1597
|
id TEXT PRIMARY KEY,
|
|
@@ -1181,7 +1644,30 @@ async function ensureSchema() {
|
|
|
1181
1644
|
entity_id TEXT NOT NULL,
|
|
1182
1645
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1183
1646
|
);
|
|
1647
|
+
|
|
1648
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1649
|
+
name,
|
|
1650
|
+
content=entities,
|
|
1651
|
+
content_rowid=rowid
|
|
1652
|
+
);
|
|
1653
|
+
|
|
1654
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1655
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1656
|
+
END;
|
|
1657
|
+
|
|
1658
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1659
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1660
|
+
END;
|
|
1661
|
+
|
|
1662
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1663
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1664
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1665
|
+
END;
|
|
1184
1666
|
`);
|
|
1667
|
+
try {
|
|
1668
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1669
|
+
} catch {
|
|
1670
|
+
}
|
|
1185
1671
|
await client.executeMultiple(`
|
|
1186
1672
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1187
1673
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1362,6 +1848,33 @@ async function ensureSchema() {
|
|
|
1362
1848
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1363
1849
|
ON conversations(channel_id);
|
|
1364
1850
|
`);
|
|
1851
|
+
await client.executeMultiple(`
|
|
1852
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1853
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1854
|
+
agent_id TEXT NOT NULL,
|
|
1855
|
+
session_name TEXT,
|
|
1856
|
+
task_id TEXT,
|
|
1857
|
+
project_name TEXT,
|
|
1858
|
+
started_at TEXT NOT NULL
|
|
1859
|
+
);
|
|
1860
|
+
|
|
1861
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1862
|
+
ON session_agent_map(agent_id);
|
|
1863
|
+
`);
|
|
1864
|
+
try {
|
|
1865
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1866
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1867
|
+
await client.execute({
|
|
1868
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1869
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1870
|
+
FROM memories
|
|
1871
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1872
|
+
GROUP BY session_id, agent_id`,
|
|
1873
|
+
args: []
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
} catch {
|
|
1877
|
+
}
|
|
1365
1878
|
try {
|
|
1366
1879
|
await client.execute({
|
|
1367
1880
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1495,15 +2008,41 @@ async function ensureSchema() {
|
|
|
1495
2008
|
});
|
|
1496
2009
|
} catch {
|
|
1497
2010
|
}
|
|
2011
|
+
for (const col of [
|
|
2012
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2013
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2014
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2015
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2016
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2017
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2018
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2019
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2020
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2021
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2022
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2023
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2024
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2025
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2026
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2027
|
+
]) {
|
|
2028
|
+
try {
|
|
2029
|
+
await client.execute(col);
|
|
2030
|
+
} catch {
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
1498
2033
|
}
|
|
1499
2034
|
async function disposeDatabase() {
|
|
2035
|
+
if (_daemonClient) {
|
|
2036
|
+
_daemonClient.close();
|
|
2037
|
+
_daemonClient = null;
|
|
2038
|
+
}
|
|
1500
2039
|
if (_client) {
|
|
1501
2040
|
_client.close();
|
|
1502
2041
|
_client = null;
|
|
1503
2042
|
_resilientClient = null;
|
|
1504
2043
|
}
|
|
1505
2044
|
}
|
|
1506
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2045
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1507
2046
|
var init_database = __esm({
|
|
1508
2047
|
"src/lib/database.ts"() {
|
|
1509
2048
|
"use strict";
|
|
@@ -1511,24 +2050,25 @@ var init_database = __esm({
|
|
|
1511
2050
|
init_employees();
|
|
1512
2051
|
_client = null;
|
|
1513
2052
|
_resilientClient = null;
|
|
2053
|
+
_daemonClient = null;
|
|
1514
2054
|
initTurso = initDatabase;
|
|
1515
2055
|
disposeTurso = disposeDatabase;
|
|
1516
2056
|
}
|
|
1517
2057
|
});
|
|
1518
2058
|
|
|
1519
2059
|
// src/lib/license.ts
|
|
1520
|
-
import { readFileSync as
|
|
1521
|
-
import { randomUUID } from "crypto";
|
|
1522
|
-
import
|
|
2060
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2061
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2062
|
+
import path6 from "path";
|
|
1523
2063
|
import { jwtVerify, importSPKI } from "jose";
|
|
1524
2064
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1525
2065
|
var init_license = __esm({
|
|
1526
2066
|
"src/lib/license.ts"() {
|
|
1527
2067
|
"use strict";
|
|
1528
2068
|
init_config();
|
|
1529
|
-
LICENSE_PATH =
|
|
1530
|
-
CACHE_PATH =
|
|
1531
|
-
DEVICE_ID_PATH =
|
|
2069
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
2070
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
2071
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1532
2072
|
PLAN_LIMITS = {
|
|
1533
2073
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1534
2074
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1540,12 +2080,12 @@ var init_license = __esm({
|
|
|
1540
2080
|
});
|
|
1541
2081
|
|
|
1542
2082
|
// src/lib/plan-limits.ts
|
|
1543
|
-
import { readFileSync as
|
|
1544
|
-
import
|
|
2083
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
2084
|
+
import path7 from "path";
|
|
1545
2085
|
function getLicenseSync() {
|
|
1546
2086
|
try {
|
|
1547
|
-
if (!
|
|
1548
|
-
const raw = JSON.parse(
|
|
2087
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
2088
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1549
2089
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1550
2090
|
const parts = raw.token.split(".");
|
|
1551
2091
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1583,8 +2123,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1583
2123
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1584
2124
|
let count = 0;
|
|
1585
2125
|
try {
|
|
1586
|
-
if (
|
|
1587
|
-
const raw =
|
|
2126
|
+
if (existsSync7(filePath)) {
|
|
2127
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1588
2128
|
const employees = JSON.parse(raw);
|
|
1589
2129
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1590
2130
|
}
|
|
@@ -1613,19 +2153,19 @@ var init_plan_limits = __esm({
|
|
|
1613
2153
|
this.name = "PlanLimitError";
|
|
1614
2154
|
}
|
|
1615
2155
|
};
|
|
1616
|
-
CACHE_PATH2 =
|
|
2156
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1617
2157
|
}
|
|
1618
2158
|
});
|
|
1619
2159
|
|
|
1620
2160
|
// src/lib/notifications.ts
|
|
1621
2161
|
import crypto from "crypto";
|
|
1622
|
-
import
|
|
2162
|
+
import path8 from "path";
|
|
1623
2163
|
import os5 from "os";
|
|
1624
2164
|
import {
|
|
1625
|
-
readFileSync as
|
|
2165
|
+
readFileSync as readFileSync8,
|
|
1626
2166
|
readdirSync,
|
|
1627
|
-
unlinkSync as
|
|
1628
|
-
existsSync as
|
|
2167
|
+
unlinkSync as unlinkSync3,
|
|
2168
|
+
existsSync as existsSync8,
|
|
1629
2169
|
rmdirSync
|
|
1630
2170
|
} from "fs";
|
|
1631
2171
|
async function writeNotification(notification) {
|
|
@@ -1776,10 +2316,11 @@ __export(tasks_crud_exports, {
|
|
|
1776
2316
|
writeCheckpoint: () => writeCheckpoint
|
|
1777
2317
|
});
|
|
1778
2318
|
import crypto3 from "crypto";
|
|
1779
|
-
import
|
|
2319
|
+
import path9 from "path";
|
|
2320
|
+
import os6 from "os";
|
|
1780
2321
|
import { execSync as execSync4 } from "child_process";
|
|
1781
2322
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1782
|
-
import { existsSync as
|
|
2323
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1783
2324
|
async function writeCheckpoint(input) {
|
|
1784
2325
|
const client = getClient();
|
|
1785
2326
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1820,6 +2361,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1820
2361
|
function slugify(title) {
|
|
1821
2362
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1822
2363
|
}
|
|
2364
|
+
function buildKeywordIndex() {
|
|
2365
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2366
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2367
|
+
for (const kw of keywords) {
|
|
2368
|
+
const existing = idx.get(kw) ?? [];
|
|
2369
|
+
existing.push(role);
|
|
2370
|
+
idx.set(kw, existing);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return idx;
|
|
2374
|
+
}
|
|
2375
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2376
|
+
const employees = loadEmployeesSync();
|
|
2377
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2378
|
+
if (!employee) return void 0;
|
|
2379
|
+
const assigneeRole = employee.role;
|
|
2380
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2381
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2382
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2383
|
+
if (text.includes(keyword)) {
|
|
2384
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2388
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2389
|
+
if (assigneeRole === "COO") return void 0;
|
|
2390
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2391
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2392
|
+
}
|
|
1823
2393
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1824
2394
|
const scope = sessionScopeFilter(scopeSession);
|
|
1825
2395
|
let result = await client.execute({
|
|
@@ -1869,7 +2439,14 @@ async function createTaskCore(input) {
|
|
|
1869
2439
|
const id = crypto3.randomUUID();
|
|
1870
2440
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1871
2441
|
const slug = slugify(input.title);
|
|
1872
|
-
|
|
2442
|
+
let earlySessionScope = null;
|
|
2443
|
+
try {
|
|
2444
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2445
|
+
earlySessionScope = resolveExeSession2();
|
|
2446
|
+
} catch {
|
|
2447
|
+
}
|
|
2448
|
+
const scope = earlySessionScope ?? "default";
|
|
2449
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1873
2450
|
let blockedById = null;
|
|
1874
2451
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1875
2452
|
if (input.blockedBy) {
|
|
@@ -1909,22 +2486,24 @@ async function createTaskCore(input) {
|
|
|
1909
2486
|
if (dupCheck.rows.length > 0) {
|
|
1910
2487
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1911
2488
|
}
|
|
2489
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2490
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2491
|
+
if (laneWarning) {
|
|
2492
|
+
warning = warning ? `${warning}
|
|
2493
|
+
${laneWarning}` : laneWarning;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
1912
2496
|
if (input.baseDir) {
|
|
1913
2497
|
try {
|
|
1914
|
-
await mkdir3(
|
|
1915
|
-
await mkdir3(
|
|
2498
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2499
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1916
2500
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1917
2501
|
await ensureGitignoreExe(input.baseDir);
|
|
1918
2502
|
} catch {
|
|
1919
2503
|
}
|
|
1920
2504
|
}
|
|
1921
2505
|
const complexity = input.complexity ?? "standard";
|
|
1922
|
-
|
|
1923
|
-
try {
|
|
1924
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1925
|
-
sessionScope = resolveExeSession2();
|
|
1926
|
-
} catch {
|
|
1927
|
-
}
|
|
2506
|
+
const sessionScope = earlySessionScope;
|
|
1928
2507
|
await client.execute({
|
|
1929
2508
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
1930
2509
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1951,6 +2530,39 @@ async function createTaskCore(input) {
|
|
|
1951
2530
|
now
|
|
1952
2531
|
]
|
|
1953
2532
|
});
|
|
2533
|
+
if (input.baseDir) {
|
|
2534
|
+
try {
|
|
2535
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
2536
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
2537
|
+
const mdDir = path9.dirname(mdPath);
|
|
2538
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2539
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2540
|
+
const mdContent = `# ${input.title}
|
|
2541
|
+
|
|
2542
|
+
**ID:** ${id}
|
|
2543
|
+
**Status:** ${initialStatus}
|
|
2544
|
+
**Priority:** ${input.priority}
|
|
2545
|
+
**Assigned by:** ${input.assignedBy}
|
|
2546
|
+
**Assigned to:** ${input.assignedTo}
|
|
2547
|
+
**Project:** ${input.projectName}
|
|
2548
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2549
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2550
|
+
**Reviewer:** ${reviewer}
|
|
2551
|
+
|
|
2552
|
+
## Context
|
|
2553
|
+
|
|
2554
|
+
${input.context}
|
|
2555
|
+
|
|
2556
|
+
## MANDATORY: When done
|
|
2557
|
+
|
|
2558
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2559
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2560
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2561
|
+
`;
|
|
2562
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
1954
2566
|
return {
|
|
1955
2567
|
id,
|
|
1956
2568
|
title: input.title,
|
|
@@ -2143,7 +2755,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2143
2755
|
return { row, taskFile, now, taskId };
|
|
2144
2756
|
}
|
|
2145
2757
|
}
|
|
2146
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2758
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2147
2759
|
process.stderr.write(
|
|
2148
2760
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2149
2761
|
`
|
|
@@ -2208,9 +2820,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2208
2820
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2209
2821
|
}
|
|
2210
2822
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2211
|
-
const archPath =
|
|
2823
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2212
2824
|
try {
|
|
2213
|
-
if (
|
|
2825
|
+
if (existsSync9(archPath)) return;
|
|
2214
2826
|
const template = [
|
|
2215
2827
|
`# ${projectName} \u2014 System Architecture`,
|
|
2216
2828
|
"",
|
|
@@ -2243,10 +2855,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2243
2855
|
}
|
|
2244
2856
|
}
|
|
2245
2857
|
async function ensureGitignoreExe(baseDir) {
|
|
2246
|
-
const gitignorePath =
|
|
2858
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
2247
2859
|
try {
|
|
2248
|
-
if (
|
|
2249
|
-
const content =
|
|
2860
|
+
if (existsSync9(gitignorePath)) {
|
|
2861
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2250
2862
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2251
2863
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2252
2864
|
} else {
|
|
@@ -2255,20 +2867,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2255
2867
|
} catch {
|
|
2256
2868
|
}
|
|
2257
2869
|
}
|
|
2258
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2870
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2259
2871
|
var init_tasks_crud = __esm({
|
|
2260
2872
|
"src/lib/tasks-crud.ts"() {
|
|
2261
2873
|
"use strict";
|
|
2262
2874
|
init_database();
|
|
2263
2875
|
init_task_scope();
|
|
2876
|
+
init_employees();
|
|
2877
|
+
LANE_KEYWORDS = {
|
|
2878
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2879
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2880
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2881
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2882
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2883
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2884
|
+
};
|
|
2885
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2264
2886
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2265
2887
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2266
2888
|
}
|
|
2267
2889
|
});
|
|
2268
2890
|
|
|
2269
2891
|
// src/lib/tasks-review.ts
|
|
2270
|
-
import
|
|
2271
|
-
import { existsSync as
|
|
2892
|
+
import path10 from "path";
|
|
2893
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2272
2894
|
async function countPendingReviews(sessionScope) {
|
|
2273
2895
|
const client = getClient();
|
|
2274
2896
|
if (sessionScope) {
|
|
@@ -2290,7 +2912,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2290
2912
|
const result2 = await client.execute({
|
|
2291
2913
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2292
2914
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2293
|
-
AND
|
|
2915
|
+
AND session_scope = ?`,
|
|
2294
2916
|
args: [sinceIso, sessionScope]
|
|
2295
2917
|
});
|
|
2296
2918
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2308,7 +2930,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2308
2930
|
const result2 = await client.execute({
|
|
2309
2931
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2310
2932
|
WHERE status = 'needs_review'
|
|
2311
|
-
AND
|
|
2933
|
+
AND session_scope = ?
|
|
2312
2934
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2313
2935
|
args: [sessionScope, limit]
|
|
2314
2936
|
});
|
|
@@ -2429,14 +3051,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2429
3051
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2430
3052
|
const agent = parts[1];
|
|
2431
3053
|
const slug = parts.slice(2).join("-");
|
|
2432
|
-
const
|
|
3054
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2433
3055
|
const result = await client.execute({
|
|
2434
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2435
|
-
args: [now,
|
|
3056
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3057
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2436
3058
|
});
|
|
2437
3059
|
if (result.rowsAffected > 0) {
|
|
2438
3060
|
process.stderr.write(
|
|
2439
|
-
`[review-cleanup] Cascaded original task to done
|
|
3061
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2440
3062
|
`
|
|
2441
3063
|
);
|
|
2442
3064
|
}
|
|
@@ -2449,11 +3071,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2449
3071
|
);
|
|
2450
3072
|
}
|
|
2451
3073
|
try {
|
|
2452
|
-
const cacheDir =
|
|
2453
|
-
if (
|
|
3074
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
3075
|
+
if (existsSync10(cacheDir)) {
|
|
2454
3076
|
for (const f of readdirSync2(cacheDir)) {
|
|
2455
3077
|
if (f.startsWith("review-notified-")) {
|
|
2456
|
-
|
|
3078
|
+
unlinkSync4(path10.join(cacheDir, f));
|
|
2457
3079
|
}
|
|
2458
3080
|
}
|
|
2459
3081
|
}
|
|
@@ -2474,7 +3096,7 @@ var init_tasks_review = __esm({
|
|
|
2474
3096
|
});
|
|
2475
3097
|
|
|
2476
3098
|
// src/lib/tasks-chain.ts
|
|
2477
|
-
import
|
|
3099
|
+
import path11 from "path";
|
|
2478
3100
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2479
3101
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2480
3102
|
const client = getClient();
|
|
@@ -2491,7 +3113,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2491
3113
|
});
|
|
2492
3114
|
for (const ur of unblockedRows.rows) {
|
|
2493
3115
|
try {
|
|
2494
|
-
const ubFile =
|
|
3116
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
2495
3117
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2496
3118
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2497
3119
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2560,7 +3182,7 @@ var init_tasks_chain = __esm({
|
|
|
2560
3182
|
|
|
2561
3183
|
// src/lib/project-name.ts
|
|
2562
3184
|
import { execSync as execSync5 } from "child_process";
|
|
2563
|
-
import
|
|
3185
|
+
import path12 from "path";
|
|
2564
3186
|
function getProjectName(cwd) {
|
|
2565
3187
|
const dir = cwd ?? process.cwd();
|
|
2566
3188
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2573,7 +3195,7 @@ function getProjectName(cwd) {
|
|
|
2573
3195
|
timeout: 2e3,
|
|
2574
3196
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2575
3197
|
}).trim();
|
|
2576
|
-
repoRoot =
|
|
3198
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
2577
3199
|
} catch {
|
|
2578
3200
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2579
3201
|
cwd: dir,
|
|
@@ -2582,11 +3204,11 @@ function getProjectName(cwd) {
|
|
|
2582
3204
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2583
3205
|
}).trim();
|
|
2584
3206
|
}
|
|
2585
|
-
_cached2 =
|
|
3207
|
+
_cached2 = path12.basename(repoRoot);
|
|
2586
3208
|
_cachedCwd = dir;
|
|
2587
3209
|
return _cached2;
|
|
2588
3210
|
} catch {
|
|
2589
|
-
_cached2 =
|
|
3211
|
+
_cached2 = path12.basename(dir);
|
|
2590
3212
|
_cachedCwd = dir;
|
|
2591
3213
|
return _cached2;
|
|
2592
3214
|
}
|
|
@@ -2618,7 +3240,7 @@ function findSessionForProject(projectName) {
|
|
|
2618
3240
|
const sessions = listSessions();
|
|
2619
3241
|
for (const s of sessions) {
|
|
2620
3242
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2621
|
-
if (proj === projectName &&
|
|
3243
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2622
3244
|
}
|
|
2623
3245
|
return null;
|
|
2624
3246
|
}
|
|
@@ -2664,7 +3286,7 @@ var init_session_scope = __esm({
|
|
|
2664
3286
|
|
|
2665
3287
|
// src/lib/tasks-notify.ts
|
|
2666
3288
|
async function dispatchTaskToEmployee(input) {
|
|
2667
|
-
if (
|
|
3289
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2668
3290
|
let crossProject = false;
|
|
2669
3291
|
if (input.projectName) {
|
|
2670
3292
|
try {
|
|
@@ -3059,8 +3681,8 @@ __export(tasks_exports, {
|
|
|
3059
3681
|
updateTaskStatus: () => updateTaskStatus,
|
|
3060
3682
|
writeCheckpoint: () => writeCheckpoint
|
|
3061
3683
|
});
|
|
3062
|
-
import
|
|
3063
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
3684
|
+
import path13 from "path";
|
|
3685
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3064
3686
|
async function createTask(input) {
|
|
3065
3687
|
const result = await createTaskCore(input);
|
|
3066
3688
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3079,14 +3701,14 @@ async function updateTask(input) {
|
|
|
3079
3701
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3080
3702
|
try {
|
|
3081
3703
|
const agent = String(row.assigned_to);
|
|
3082
|
-
const cacheDir =
|
|
3083
|
-
const cachePath =
|
|
3704
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3705
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
3084
3706
|
if (input.status === "in_progress") {
|
|
3085
3707
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3086
3708
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3087
3709
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3088
3710
|
try {
|
|
3089
|
-
|
|
3711
|
+
unlinkSync5(cachePath);
|
|
3090
3712
|
} catch {
|
|
3091
3713
|
}
|
|
3092
3714
|
}
|
|
@@ -3143,7 +3765,7 @@ async function updateTask(input) {
|
|
|
3143
3765
|
}
|
|
3144
3766
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3145
3767
|
if (isTerminal) {
|
|
3146
|
-
const isCoordinator =
|
|
3768
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3147
3769
|
if (!isCoordinator) {
|
|
3148
3770
|
notifyTaskDone();
|
|
3149
3771
|
}
|
|
@@ -3168,7 +3790,7 @@ async function updateTask(input) {
|
|
|
3168
3790
|
}
|
|
3169
3791
|
}
|
|
3170
3792
|
}
|
|
3171
|
-
if (input.status === "done" &&
|
|
3793
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3172
3794
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3173
3795
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3174
3796
|
taskId,
|
|
@@ -3184,7 +3806,7 @@ async function updateTask(input) {
|
|
|
3184
3806
|
});
|
|
3185
3807
|
}
|
|
3186
3808
|
let nextTask;
|
|
3187
|
-
if (isTerminal &&
|
|
3809
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3188
3810
|
try {
|
|
3189
3811
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3190
3812
|
} catch {
|
|
@@ -3528,7 +4150,7 @@ var init_capacity_monitor = __esm({
|
|
|
3528
4150
|
// src/lib/tmux-routing.ts
|
|
3529
4151
|
var tmux_routing_exports = {};
|
|
3530
4152
|
__export(tmux_routing_exports, {
|
|
3531
|
-
acquireSpawnLock: () =>
|
|
4153
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3532
4154
|
employeeSessionName: () => employeeSessionName,
|
|
3533
4155
|
ensureEmployee: () => ensureEmployee,
|
|
3534
4156
|
extractRootExe: () => extractRootExe,
|
|
@@ -3543,20 +4165,20 @@ __export(tmux_routing_exports, {
|
|
|
3543
4165
|
notifyParentExe: () => notifyParentExe,
|
|
3544
4166
|
parseParentExe: () => parseParentExe,
|
|
3545
4167
|
registerParentExe: () => registerParentExe,
|
|
3546
|
-
releaseSpawnLock: () =>
|
|
4168
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3547
4169
|
resolveExeSession: () => resolveExeSession,
|
|
3548
4170
|
sendIntercom: () => sendIntercom,
|
|
3549
4171
|
spawnEmployee: () => spawnEmployee,
|
|
3550
4172
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3551
4173
|
});
|
|
3552
4174
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3553
|
-
import { readFileSync as
|
|
3554
|
-
import
|
|
3555
|
-
import
|
|
3556
|
-
import { fileURLToPath } from "url";
|
|
3557
|
-
import { unlinkSync as
|
|
4175
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4176
|
+
import path14 from "path";
|
|
4177
|
+
import os7 from "os";
|
|
4178
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4179
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3558
4180
|
function spawnLockPath(sessionName) {
|
|
3559
|
-
return
|
|
4181
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3560
4182
|
}
|
|
3561
4183
|
function isProcessAlive(pid) {
|
|
3562
4184
|
try {
|
|
@@ -3566,14 +4188,14 @@ function isProcessAlive(pid) {
|
|
|
3566
4188
|
return false;
|
|
3567
4189
|
}
|
|
3568
4190
|
}
|
|
3569
|
-
function
|
|
3570
|
-
if (!
|
|
4191
|
+
function acquireSpawnLock2(sessionName) {
|
|
4192
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3571
4193
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3572
4194
|
}
|
|
3573
4195
|
const lockFile = spawnLockPath(sessionName);
|
|
3574
|
-
if (
|
|
4196
|
+
if (existsSync11(lockFile)) {
|
|
3575
4197
|
try {
|
|
3576
|
-
const lock = JSON.parse(
|
|
4198
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3577
4199
|
const age = Date.now() - lock.timestamp;
|
|
3578
4200
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3579
4201
|
return false;
|
|
@@ -3584,22 +4206,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3584
4206
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3585
4207
|
return true;
|
|
3586
4208
|
}
|
|
3587
|
-
function
|
|
4209
|
+
function releaseSpawnLock2(sessionName) {
|
|
3588
4210
|
try {
|
|
3589
|
-
|
|
4211
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3590
4212
|
} catch {
|
|
3591
4213
|
}
|
|
3592
4214
|
}
|
|
3593
4215
|
function resolveBehaviorsExporterScript() {
|
|
3594
4216
|
try {
|
|
3595
|
-
const thisFile =
|
|
3596
|
-
const scriptPath =
|
|
3597
|
-
|
|
4217
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4218
|
+
const scriptPath = path14.join(
|
|
4219
|
+
path14.dirname(thisFile),
|
|
3598
4220
|
"..",
|
|
3599
4221
|
"bin",
|
|
3600
4222
|
"exe-export-behaviors.js"
|
|
3601
4223
|
);
|
|
3602
|
-
return
|
|
4224
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3603
4225
|
} catch {
|
|
3604
4226
|
return null;
|
|
3605
4227
|
}
|
|
@@ -3665,11 +4287,11 @@ function extractRootExe(name) {
|
|
|
3665
4287
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3666
4288
|
}
|
|
3667
4289
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3668
|
-
if (!
|
|
4290
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3669
4291
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3670
4292
|
}
|
|
3671
4293
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3672
|
-
const filePath =
|
|
4294
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3673
4295
|
writeFileSync6(filePath, JSON.stringify({
|
|
3674
4296
|
parentExe: rootExe,
|
|
3675
4297
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3678,7 +4300,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3678
4300
|
}
|
|
3679
4301
|
function getParentExe(sessionKey) {
|
|
3680
4302
|
try {
|
|
3681
|
-
const data = JSON.parse(
|
|
4303
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3682
4304
|
return data.parentExe || null;
|
|
3683
4305
|
} catch {
|
|
3684
4306
|
return null;
|
|
@@ -3686,8 +4308,8 @@ function getParentExe(sessionKey) {
|
|
|
3686
4308
|
}
|
|
3687
4309
|
function getDispatchedBy(sessionKey) {
|
|
3688
4310
|
try {
|
|
3689
|
-
const data = JSON.parse(
|
|
3690
|
-
|
|
4311
|
+
const data = JSON.parse(readFileSync10(
|
|
4312
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3691
4313
|
"utf8"
|
|
3692
4314
|
));
|
|
3693
4315
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3713,10 +4335,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3713
4335
|
}
|
|
3714
4336
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3715
4337
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3716
|
-
if (!isAlive(base) &&
|
|
4338
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3717
4339
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3718
4340
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3719
|
-
if (!isAlive(candidate) &&
|
|
4341
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3720
4342
|
}
|
|
3721
4343
|
return null;
|
|
3722
4344
|
}
|
|
@@ -3748,15 +4370,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3748
4370
|
}
|
|
3749
4371
|
function readDebounceState() {
|
|
3750
4372
|
try {
|
|
3751
|
-
if (!
|
|
3752
|
-
return JSON.parse(
|
|
4373
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4374
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3753
4375
|
} catch {
|
|
3754
4376
|
return {};
|
|
3755
4377
|
}
|
|
3756
4378
|
}
|
|
3757
4379
|
function writeDebounceState(state) {
|
|
3758
4380
|
try {
|
|
3759
|
-
if (!
|
|
4381
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3760
4382
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3761
4383
|
} catch {
|
|
3762
4384
|
}
|
|
@@ -3876,7 +4498,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3876
4498
|
return true;
|
|
3877
4499
|
}
|
|
3878
4500
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3879
|
-
if (
|
|
4501
|
+
if (isCoordinatorName(employeeName)) {
|
|
3880
4502
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3881
4503
|
}
|
|
3882
4504
|
try {
|
|
@@ -3948,26 +4570,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3948
4570
|
const transport = getTransport();
|
|
3949
4571
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3950
4572
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3951
|
-
const logDir =
|
|
3952
|
-
const logFile =
|
|
3953
|
-
if (!
|
|
4573
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4574
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4575
|
+
if (!existsSync11(logDir)) {
|
|
3954
4576
|
mkdirSync5(logDir, { recursive: true });
|
|
3955
4577
|
}
|
|
3956
4578
|
transport.kill(sessionName);
|
|
3957
4579
|
let cleanupSuffix = "";
|
|
3958
4580
|
try {
|
|
3959
|
-
const thisFile =
|
|
3960
|
-
const cleanupScript =
|
|
3961
|
-
if (
|
|
4581
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4582
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4583
|
+
if (existsSync11(cleanupScript)) {
|
|
3962
4584
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3963
4585
|
}
|
|
3964
4586
|
} catch {
|
|
3965
4587
|
}
|
|
3966
4588
|
try {
|
|
3967
|
-
const claudeJsonPath =
|
|
4589
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3968
4590
|
let claudeJson = {};
|
|
3969
4591
|
try {
|
|
3970
|
-
claudeJson = JSON.parse(
|
|
4592
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3971
4593
|
} catch {
|
|
3972
4594
|
}
|
|
3973
4595
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3979,13 +4601,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3979
4601
|
} catch {
|
|
3980
4602
|
}
|
|
3981
4603
|
try {
|
|
3982
|
-
const settingsDir =
|
|
4604
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3983
4605
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3984
|
-
const projSettingsDir =
|
|
3985
|
-
const settingsPath =
|
|
4606
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
4607
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3986
4608
|
let settings = {};
|
|
3987
4609
|
try {
|
|
3988
|
-
settings = JSON.parse(
|
|
4610
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3989
4611
|
} catch {
|
|
3990
4612
|
}
|
|
3991
4613
|
const perms = settings.permissions ?? {};
|
|
@@ -4026,8 +4648,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4026
4648
|
let behaviorsFlag = "";
|
|
4027
4649
|
let legacyFallbackWarned = false;
|
|
4028
4650
|
if (!useExeAgent && !useBinSymlink) {
|
|
4029
|
-
const identityPath =
|
|
4030
|
-
|
|
4651
|
+
const identityPath = path14.join(
|
|
4652
|
+
os7.homedir(),
|
|
4031
4653
|
".exe-os",
|
|
4032
4654
|
"identity",
|
|
4033
4655
|
`${employeeName}.md`
|
|
@@ -4036,13 +4658,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4036
4658
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4037
4659
|
if (hasAgentFlag) {
|
|
4038
4660
|
identityFlag = ` --agent ${employeeName}`;
|
|
4039
|
-
} else if (
|
|
4661
|
+
} else if (existsSync11(identityPath)) {
|
|
4040
4662
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4041
4663
|
legacyFallbackWarned = true;
|
|
4042
4664
|
}
|
|
4043
4665
|
const behaviorsFile = exportBehaviorsSync(
|
|
4044
4666
|
employeeName,
|
|
4045
|
-
|
|
4667
|
+
path14.basename(spawnCwd),
|
|
4046
4668
|
sessionName
|
|
4047
4669
|
);
|
|
4048
4670
|
if (behaviorsFile) {
|
|
@@ -4057,9 +4679,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4057
4679
|
}
|
|
4058
4680
|
let sessionContextFlag = "";
|
|
4059
4681
|
try {
|
|
4060
|
-
const ctxDir =
|
|
4682
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4061
4683
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4062
|
-
const ctxFile =
|
|
4684
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4063
4685
|
const ctxContent = [
|
|
4064
4686
|
`## Session Context`,
|
|
4065
4687
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4098,13 +4720,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4098
4720
|
command: spawnCommand
|
|
4099
4721
|
});
|
|
4100
4722
|
if (spawnResult.error) {
|
|
4101
|
-
|
|
4723
|
+
releaseSpawnLock2(sessionName);
|
|
4102
4724
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4103
4725
|
}
|
|
4104
4726
|
transport.pipeLog(sessionName, logFile);
|
|
4105
4727
|
try {
|
|
4106
4728
|
const mySession = getMySession();
|
|
4107
|
-
const dispatchInfo =
|
|
4729
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4108
4730
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4109
4731
|
dispatchedBy: mySession,
|
|
4110
4732
|
rootExe: exeSession,
|
|
@@ -4136,7 +4758,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4136
4758
|
}
|
|
4137
4759
|
}
|
|
4138
4760
|
if (!booted) {
|
|
4139
|
-
|
|
4761
|
+
releaseSpawnLock2(sessionName);
|
|
4140
4762
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4141
4763
|
}
|
|
4142
4764
|
if (!useExeAgent) {
|
|
@@ -4153,7 +4775,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4153
4775
|
pid: 0,
|
|
4154
4776
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4155
4777
|
});
|
|
4156
|
-
|
|
4778
|
+
releaseSpawnLock2(sessionName);
|
|
4157
4779
|
return { sessionName };
|
|
4158
4780
|
}
|
|
4159
4781
|
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
@@ -4169,14 +4791,14 @@ var init_tmux_routing = __esm({
|
|
|
4169
4791
|
init_intercom_queue();
|
|
4170
4792
|
init_plan_limits();
|
|
4171
4793
|
init_employees();
|
|
4172
|
-
SPAWN_LOCK_DIR =
|
|
4173
|
-
SESSION_CACHE =
|
|
4794
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4795
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4174
4796
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4175
4797
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4176
4798
|
VERIFY_PANE_LINES = 200;
|
|
4177
4799
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4178
|
-
INTERCOM_LOG2 =
|
|
4179
|
-
DEBOUNCE_FILE =
|
|
4800
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4801
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4180
4802
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4181
4803
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4182
4804
|
}
|
|
@@ -4217,14 +4839,14 @@ var init_memory = __esm({
|
|
|
4217
4839
|
|
|
4218
4840
|
// src/lib/keychain.ts
|
|
4219
4841
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4220
|
-
import { existsSync as
|
|
4221
|
-
import
|
|
4222
|
-
import
|
|
4842
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4843
|
+
import path15 from "path";
|
|
4844
|
+
import os8 from "os";
|
|
4223
4845
|
function getKeyDir() {
|
|
4224
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4846
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
|
|
4225
4847
|
}
|
|
4226
4848
|
function getKeyPath() {
|
|
4227
|
-
return
|
|
4849
|
+
return path15.join(getKeyDir(), "master.key");
|
|
4228
4850
|
}
|
|
4229
4851
|
async function tryKeytar() {
|
|
4230
4852
|
try {
|
|
@@ -4245,13 +4867,21 @@ async function getMasterKey() {
|
|
|
4245
4867
|
}
|
|
4246
4868
|
}
|
|
4247
4869
|
const keyPath = getKeyPath();
|
|
4248
|
-
if (!
|
|
4870
|
+
if (!existsSync12(keyPath)) {
|
|
4871
|
+
process.stderr.write(
|
|
4872
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4873
|
+
`
|
|
4874
|
+
);
|
|
4249
4875
|
return null;
|
|
4250
4876
|
}
|
|
4251
4877
|
try {
|
|
4252
4878
|
const content = await readFile4(keyPath, "utf-8");
|
|
4253
4879
|
return Buffer.from(content.trim(), "base64");
|
|
4254
|
-
} catch {
|
|
4880
|
+
} catch (err) {
|
|
4881
|
+
process.stderr.write(
|
|
4882
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4883
|
+
`
|
|
4884
|
+
);
|
|
4255
4885
|
return null;
|
|
4256
4886
|
}
|
|
4257
4887
|
}
|
|
@@ -4277,12 +4907,12 @@ __export(shard_manager_exports, {
|
|
|
4277
4907
|
listShards: () => listShards,
|
|
4278
4908
|
shardExists: () => shardExists
|
|
4279
4909
|
});
|
|
4280
|
-
import
|
|
4281
|
-
import { existsSync as
|
|
4910
|
+
import path16 from "path";
|
|
4911
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4282
4912
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4283
4913
|
function initShardManager(encryptionKey) {
|
|
4284
4914
|
_encryptionKey = encryptionKey;
|
|
4285
|
-
if (!
|
|
4915
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4286
4916
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
4287
4917
|
}
|
|
4288
4918
|
_shardingEnabled = true;
|
|
@@ -4303,7 +4933,7 @@ function getShardClient(projectName) {
|
|
|
4303
4933
|
}
|
|
4304
4934
|
const cached = _shards.get(safeName);
|
|
4305
4935
|
if (cached) return cached;
|
|
4306
|
-
const dbPath =
|
|
4936
|
+
const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
|
|
4307
4937
|
const client = createClient2({
|
|
4308
4938
|
url: `file:${dbPath}`,
|
|
4309
4939
|
encryptionKey: _encryptionKey
|
|
@@ -4313,10 +4943,10 @@ function getShardClient(projectName) {
|
|
|
4313
4943
|
}
|
|
4314
4944
|
function shardExists(projectName) {
|
|
4315
4945
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4316
|
-
return
|
|
4946
|
+
return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
|
|
4317
4947
|
}
|
|
4318
4948
|
function listShards() {
|
|
4319
|
-
if (!
|
|
4949
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4320
4950
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4321
4951
|
}
|
|
4322
4952
|
async function ensureShardSchema(client) {
|
|
@@ -4502,7 +5132,7 @@ var init_shard_manager = __esm({
|
|
|
4502
5132
|
"src/lib/shard-manager.ts"() {
|
|
4503
5133
|
"use strict";
|
|
4504
5134
|
init_config();
|
|
4505
|
-
SHARDS_DIR =
|
|
5135
|
+
SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
|
|
4506
5136
|
_shards = /* @__PURE__ */ new Map();
|
|
4507
5137
|
_encryptionKey = null;
|
|
4508
5138
|
_shardingEnabled = false;
|
|
@@ -4627,7 +5257,7 @@ __export(global_procedures_exports, {
|
|
|
4627
5257
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4628
5258
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4629
5259
|
});
|
|
4630
|
-
import { randomUUID as
|
|
5260
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4631
5261
|
async function loadGlobalProcedures() {
|
|
4632
5262
|
const client = getClient();
|
|
4633
5263
|
const result = await client.execute({
|
|
@@ -4656,7 +5286,7 @@ ${sections.join("\n\n")}
|
|
|
4656
5286
|
`;
|
|
4657
5287
|
}
|
|
4658
5288
|
async function storeGlobalProcedure(input) {
|
|
4659
|
-
const id =
|
|
5289
|
+
const id = randomUUID3();
|
|
4660
5290
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4661
5291
|
const client = getClient();
|
|
4662
5292
|
await client.execute({
|
|
@@ -4707,6 +5337,7 @@ __export(store_exports, {
|
|
|
4707
5337
|
vectorToBlob: () => vectorToBlob,
|
|
4708
5338
|
writeMemory: () => writeMemory
|
|
4709
5339
|
});
|
|
5340
|
+
import { createHash } from "crypto";
|
|
4710
5341
|
function isBusyError2(err) {
|
|
4711
5342
|
if (err instanceof Error) {
|
|
4712
5343
|
const msg = err.message.toLowerCase();
|
|
@@ -4780,12 +5411,52 @@ function classifyTier(record) {
|
|
|
4780
5411
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4781
5412
|
return 3;
|
|
4782
5413
|
}
|
|
5414
|
+
function inferFilePaths(record) {
|
|
5415
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5416
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5417
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5418
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5419
|
+
}
|
|
5420
|
+
function inferCommitHash(record) {
|
|
5421
|
+
if (record.tool_name !== "Bash") return null;
|
|
5422
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5423
|
+
return match ? match[1] : null;
|
|
5424
|
+
}
|
|
5425
|
+
function inferLanguageType(record) {
|
|
5426
|
+
const text = record.raw_text;
|
|
5427
|
+
if (!text || text.length < 10) return null;
|
|
5428
|
+
const trimmed = text.trimStart();
|
|
5429
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5430
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5431
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5432
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5433
|
+
return "mixed";
|
|
5434
|
+
}
|
|
5435
|
+
function inferDomain(record) {
|
|
5436
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5437
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5438
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5439
|
+
return null;
|
|
5440
|
+
}
|
|
4783
5441
|
async function writeMemory(record) {
|
|
4784
5442
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4785
5443
|
throw new Error(
|
|
4786
5444
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4787
5445
|
);
|
|
4788
5446
|
}
|
|
5447
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5448
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5449
|
+
return;
|
|
5450
|
+
}
|
|
5451
|
+
try {
|
|
5452
|
+
const client = getClient();
|
|
5453
|
+
const existing = await client.execute({
|
|
5454
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5455
|
+
args: [contentHash, record.agent_id]
|
|
5456
|
+
});
|
|
5457
|
+
if (existing.rows.length > 0) return;
|
|
5458
|
+
} catch {
|
|
5459
|
+
}
|
|
4789
5460
|
const dbRow = {
|
|
4790
5461
|
id: record.id,
|
|
4791
5462
|
agent_id: record.agent_id,
|
|
@@ -4815,7 +5486,23 @@ async function writeMemory(record) {
|
|
|
4815
5486
|
supersedes_id: record.supersedes_id ?? null,
|
|
4816
5487
|
draft: record.draft ? 1 : 0,
|
|
4817
5488
|
memory_type: record.memory_type ?? "raw",
|
|
4818
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5489
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5490
|
+
content_hash: contentHash,
|
|
5491
|
+
intent: record.intent ?? null,
|
|
5492
|
+
outcome: record.outcome ?? null,
|
|
5493
|
+
domain: record.domain ?? inferDomain(record),
|
|
5494
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5495
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5496
|
+
chain_position: record.chain_position ?? null,
|
|
5497
|
+
review_status: record.review_status ?? null,
|
|
5498
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5499
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5500
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5501
|
+
duration_ms: record.duration_ms ?? null,
|
|
5502
|
+
token_cost: record.token_cost ?? null,
|
|
5503
|
+
audience: record.audience ?? null,
|
|
5504
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5505
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4819
5506
|
};
|
|
4820
5507
|
_pendingRecords.push(dbRow);
|
|
4821
5508
|
orgBus.emit({
|
|
@@ -4873,80 +5560,85 @@ async function flushBatch() {
|
|
|
4873
5560
|
const draft = row.draft ? 1 : 0;
|
|
4874
5561
|
const memoryType = row.memory_type ?? "raw";
|
|
4875
5562
|
const trajectory = row.trajectory ?? null;
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
5563
|
+
const contentHash = row.content_hash ?? null;
|
|
5564
|
+
const intent = row.intent ?? null;
|
|
5565
|
+
const outcome = row.outcome ?? null;
|
|
5566
|
+
const domain = row.domain ?? null;
|
|
5567
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5568
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5569
|
+
const chainPosition = row.chain_position ?? null;
|
|
5570
|
+
const reviewStatus = row.review_status ?? null;
|
|
5571
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5572
|
+
const filePaths = row.file_paths ?? null;
|
|
5573
|
+
const commitHash = row.commit_hash ?? null;
|
|
5574
|
+
const durationMs = row.duration_ms ?? null;
|
|
5575
|
+
const tokenCost = row.token_cost ?? null;
|
|
5576
|
+
const audience = row.audience ?? null;
|
|
5577
|
+
const languageType = row.language_type ?? null;
|
|
5578
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5579
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4886
5580
|
tool_name, project_name,
|
|
4887
5581
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4888
5582
|
confidence, last_accessed,
|
|
4889
5583
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4890
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
trajectory
|
|
4949
|
-
]
|
|
5584
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5585
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5586
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5587
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5588
|
+
const metaArgs = [
|
|
5589
|
+
intent,
|
|
5590
|
+
outcome,
|
|
5591
|
+
domain,
|
|
5592
|
+
referencedEntities,
|
|
5593
|
+
retrievalCount,
|
|
5594
|
+
chainPosition,
|
|
5595
|
+
reviewStatus,
|
|
5596
|
+
contextWindowPct,
|
|
5597
|
+
filePaths,
|
|
5598
|
+
commitHash,
|
|
5599
|
+
durationMs,
|
|
5600
|
+
tokenCost,
|
|
5601
|
+
audience,
|
|
5602
|
+
languageType,
|
|
5603
|
+
parentMemoryId
|
|
5604
|
+
];
|
|
5605
|
+
const baseArgs = [
|
|
5606
|
+
row.id,
|
|
5607
|
+
row.agent_id,
|
|
5608
|
+
row.agent_role,
|
|
5609
|
+
row.session_id,
|
|
5610
|
+
row.timestamp,
|
|
5611
|
+
row.tool_name,
|
|
5612
|
+
row.project_name,
|
|
5613
|
+
row.has_error,
|
|
5614
|
+
row.raw_text
|
|
5615
|
+
];
|
|
5616
|
+
const sharedArgs = [
|
|
5617
|
+
row.version,
|
|
5618
|
+
taskId,
|
|
5619
|
+
importance,
|
|
5620
|
+
status,
|
|
5621
|
+
confidence,
|
|
5622
|
+
lastAccessed,
|
|
5623
|
+
workspaceId,
|
|
5624
|
+
documentId,
|
|
5625
|
+
userId,
|
|
5626
|
+
charOffset,
|
|
5627
|
+
pageNumber,
|
|
5628
|
+
sourcePath,
|
|
5629
|
+
sourceType,
|
|
5630
|
+
tier,
|
|
5631
|
+
supersedesId,
|
|
5632
|
+
draft,
|
|
5633
|
+
memoryType,
|
|
5634
|
+
trajectory,
|
|
5635
|
+
contentHash
|
|
5636
|
+
];
|
|
5637
|
+
return {
|
|
5638
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5639
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5640
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5641
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4950
5642
|
};
|
|
4951
5643
|
};
|
|
4952
5644
|
const globalClient = getClient();
|