@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_db_retry = __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 unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
614
|
+
import path6 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(readFileSync6(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
|
+
unlinkSync3(PID_PATH);
|
|
656
|
+
} catch {
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
unlinkSync3(SOCKET_PATH);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function findPackageRoot() {
|
|
665
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
666
|
+
const { root } = path6.parse(dir);
|
|
667
|
+
while (dir !== root) {
|
|
668
|
+
if (existsSync5(path6.join(dir, "package.json"))) return dir;
|
|
669
|
+
dir = path6.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 = path6.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 = path6.join(path6.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
|
+
unlinkSync3(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
|
+
unlinkSync3(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 ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
834
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
835
|
+
SPAWN_LOCK_PATH = path6.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 readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2061
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2062
|
+
import path7 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 = path7.join(EXE_AI_DIR, "license.key");
|
|
2070
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2071
|
+
DEVICE_ID_PATH = path7.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 readFileSync8, existsSync as existsSync7 } from "fs";
|
|
2084
|
+
import path8 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(readFileSync8(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 = readFileSync8(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 = path8.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 path9 from "path";
|
|
1623
2163
|
import os5 from "os";
|
|
1624
2164
|
import {
|
|
1625
|
-
readFileSync as
|
|
2165
|
+
readFileSync as readFileSync9,
|
|
1626
2166
|
readdirSync as readdirSync2,
|
|
1627
|
-
unlinkSync as
|
|
1628
|
-
existsSync as
|
|
2167
|
+
unlinkSync as unlinkSync4,
|
|
2168
|
+
existsSync as existsSync8,
|
|
1629
2169
|
rmdirSync
|
|
1630
2170
|
} from "fs";
|
|
1631
2171
|
async function writeNotification(notification) {
|
|
@@ -1760,10 +2300,11 @@ var init_state_bus = __esm({
|
|
|
1760
2300
|
|
|
1761
2301
|
// src/lib/tasks-crud.ts
|
|
1762
2302
|
import crypto3 from "crypto";
|
|
1763
|
-
import
|
|
2303
|
+
import path10 from "path";
|
|
2304
|
+
import os6 from "os";
|
|
1764
2305
|
import { execSync as execSync5 } from "child_process";
|
|
1765
2306
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1766
|
-
import { existsSync as
|
|
2307
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1767
2308
|
async function writeCheckpoint(input2) {
|
|
1768
2309
|
const client = getClient();
|
|
1769
2310
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -1804,6 +2345,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1804
2345
|
function slugify(title) {
|
|
1805
2346
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1806
2347
|
}
|
|
2348
|
+
function buildKeywordIndex() {
|
|
2349
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2350
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2351
|
+
for (const kw of keywords) {
|
|
2352
|
+
const existing = idx.get(kw) ?? [];
|
|
2353
|
+
existing.push(role);
|
|
2354
|
+
idx.set(kw, existing);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return idx;
|
|
2358
|
+
}
|
|
2359
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2360
|
+
const employees = loadEmployeesSync();
|
|
2361
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2362
|
+
if (!employee) return void 0;
|
|
2363
|
+
const assigneeRole = employee.role;
|
|
2364
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2365
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2366
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2367
|
+
if (text.includes(keyword)) {
|
|
2368
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2372
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2373
|
+
if (assigneeRole === "COO") return void 0;
|
|
2374
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2375
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2376
|
+
}
|
|
1807
2377
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1808
2378
|
const scope = sessionScopeFilter(scopeSession);
|
|
1809
2379
|
let result = await client.execute({
|
|
@@ -1853,7 +2423,14 @@ async function createTaskCore(input2) {
|
|
|
1853
2423
|
const id = crypto3.randomUUID();
|
|
1854
2424
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1855
2425
|
const slug = slugify(input2.title);
|
|
1856
|
-
|
|
2426
|
+
let earlySessionScope = null;
|
|
2427
|
+
try {
|
|
2428
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2429
|
+
earlySessionScope = resolveExeSession2();
|
|
2430
|
+
} catch {
|
|
2431
|
+
}
|
|
2432
|
+
const scope = earlySessionScope ?? "default";
|
|
2433
|
+
const taskFile = input2.taskFile ?? `tasks/${scope}/${input2.assignedTo}/${slug}.md`;
|
|
1857
2434
|
let blockedById = null;
|
|
1858
2435
|
const initialStatus = input2.blockedBy ? "blocked" : "open";
|
|
1859
2436
|
if (input2.blockedBy) {
|
|
@@ -1893,22 +2470,24 @@ async function createTaskCore(input2) {
|
|
|
1893
2470
|
if (dupCheck.rows.length > 0) {
|
|
1894
2471
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1895
2472
|
}
|
|
2473
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2474
|
+
const laneWarning = checkLaneAffinity(input2.title, input2.context, input2.assignedTo);
|
|
2475
|
+
if (laneWarning) {
|
|
2476
|
+
warning = warning ? `${warning}
|
|
2477
|
+
${laneWarning}` : laneWarning;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
1896
2480
|
if (input2.baseDir) {
|
|
1897
2481
|
try {
|
|
1898
|
-
await mkdir3(
|
|
1899
|
-
await mkdir3(
|
|
2482
|
+
await mkdir3(path10.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
2483
|
+
await mkdir3(path10.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
1900
2484
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
1901
2485
|
await ensureGitignoreExe(input2.baseDir);
|
|
1902
2486
|
} catch {
|
|
1903
2487
|
}
|
|
1904
2488
|
}
|
|
1905
2489
|
const complexity = input2.complexity ?? "standard";
|
|
1906
|
-
|
|
1907
|
-
try {
|
|
1908
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1909
|
-
sessionScope = resolveExeSession2();
|
|
1910
|
-
} catch {
|
|
1911
|
-
}
|
|
2490
|
+
const sessionScope = earlySessionScope;
|
|
1912
2491
|
await client.execute({
|
|
1913
2492
|
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)
|
|
1914
2493
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1935,6 +2514,39 @@ async function createTaskCore(input2) {
|
|
|
1935
2514
|
now
|
|
1936
2515
|
]
|
|
1937
2516
|
});
|
|
2517
|
+
if (input2.baseDir) {
|
|
2518
|
+
try {
|
|
2519
|
+
const EXE_OS_DIR = path10.join(os6.homedir(), ".exe-os");
|
|
2520
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2521
|
+
const mdDir = path10.dirname(mdPath);
|
|
2522
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2523
|
+
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
2524
|
+
const mdContent = `# ${input2.title}
|
|
2525
|
+
|
|
2526
|
+
**ID:** ${id}
|
|
2527
|
+
**Status:** ${initialStatus}
|
|
2528
|
+
**Priority:** ${input2.priority}
|
|
2529
|
+
**Assigned by:** ${input2.assignedBy}
|
|
2530
|
+
**Assigned to:** ${input2.assignedTo}
|
|
2531
|
+
**Project:** ${input2.projectName}
|
|
2532
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2533
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2534
|
+
**Reviewer:** ${reviewer}
|
|
2535
|
+
|
|
2536
|
+
## Context
|
|
2537
|
+
|
|
2538
|
+
${input2.context}
|
|
2539
|
+
|
|
2540
|
+
## MANDATORY: When done
|
|
2541
|
+
|
|
2542
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2543
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2544
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2545
|
+
`;
|
|
2546
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2547
|
+
} catch {
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
1938
2550
|
return {
|
|
1939
2551
|
id,
|
|
1940
2552
|
title: input2.title,
|
|
@@ -2127,7 +2739,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2127
2739
|
return { row, taskFile, now, taskId };
|
|
2128
2740
|
}
|
|
2129
2741
|
}
|
|
2130
|
-
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId
|
|
2742
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || isCoordinatorName(input2.callerAgentId))) {
|
|
2131
2743
|
process.stderr.write(
|
|
2132
2744
|
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
2133
2745
|
`
|
|
@@ -2192,9 +2804,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2192
2804
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2193
2805
|
}
|
|
2194
2806
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2195
|
-
const archPath =
|
|
2807
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2196
2808
|
try {
|
|
2197
|
-
if (
|
|
2809
|
+
if (existsSync9(archPath)) return;
|
|
2198
2810
|
const template = [
|
|
2199
2811
|
`# ${projectName} \u2014 System Architecture`,
|
|
2200
2812
|
"",
|
|
@@ -2227,10 +2839,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2227
2839
|
}
|
|
2228
2840
|
}
|
|
2229
2841
|
async function ensureGitignoreExe(baseDir) {
|
|
2230
|
-
const gitignorePath =
|
|
2842
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2231
2843
|
try {
|
|
2232
|
-
if (
|
|
2233
|
-
const content =
|
|
2844
|
+
if (existsSync9(gitignorePath)) {
|
|
2845
|
+
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2234
2846
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2235
2847
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2236
2848
|
} else {
|
|
@@ -2239,20 +2851,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2239
2851
|
} catch {
|
|
2240
2852
|
}
|
|
2241
2853
|
}
|
|
2242
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2854
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2243
2855
|
var init_tasks_crud = __esm({
|
|
2244
2856
|
"src/lib/tasks-crud.ts"() {
|
|
2245
2857
|
"use strict";
|
|
2246
2858
|
init_database();
|
|
2247
2859
|
init_task_scope();
|
|
2860
|
+
init_employees();
|
|
2861
|
+
LANE_KEYWORDS = {
|
|
2862
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2863
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2864
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2865
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2866
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2867
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2868
|
+
};
|
|
2869
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2248
2870
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2249
2871
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2250
2872
|
}
|
|
2251
2873
|
});
|
|
2252
2874
|
|
|
2253
2875
|
// src/lib/tasks-review.ts
|
|
2254
|
-
import
|
|
2255
|
-
import { existsSync as
|
|
2876
|
+
import path11 from "path";
|
|
2877
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
2256
2878
|
async function countPendingReviews(sessionScope) {
|
|
2257
2879
|
const client = getClient();
|
|
2258
2880
|
if (sessionScope) {
|
|
@@ -2274,7 +2896,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2274
2896
|
const result2 = await client.execute({
|
|
2275
2897
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2276
2898
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2277
|
-
AND
|
|
2899
|
+
AND session_scope = ?`,
|
|
2278
2900
|
args: [sinceIso, sessionScope]
|
|
2279
2901
|
});
|
|
2280
2902
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2292,7 +2914,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2292
2914
|
const result2 = await client.execute({
|
|
2293
2915
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2294
2916
|
WHERE status = 'needs_review'
|
|
2295
|
-
AND
|
|
2917
|
+
AND session_scope = ?
|
|
2296
2918
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2297
2919
|
args: [sessionScope, limit]
|
|
2298
2920
|
});
|
|
@@ -2413,14 +3035,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2413
3035
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2414
3036
|
const agent = parts[1];
|
|
2415
3037
|
const slug = parts.slice(2).join("-");
|
|
2416
|
-
const
|
|
3038
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2417
3039
|
const result = await client.execute({
|
|
2418
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2419
|
-
args: [now,
|
|
3040
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3041
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2420
3042
|
});
|
|
2421
3043
|
if (result.rowsAffected > 0) {
|
|
2422
3044
|
process.stderr.write(
|
|
2423
|
-
`[review-cleanup] Cascaded original task to done
|
|
3045
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2424
3046
|
`
|
|
2425
3047
|
);
|
|
2426
3048
|
}
|
|
@@ -2433,11 +3055,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2433
3055
|
);
|
|
2434
3056
|
}
|
|
2435
3057
|
try {
|
|
2436
|
-
const cacheDir =
|
|
2437
|
-
if (
|
|
3058
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3059
|
+
if (existsSync10(cacheDir)) {
|
|
2438
3060
|
for (const f of readdirSync3(cacheDir)) {
|
|
2439
3061
|
if (f.startsWith("review-notified-")) {
|
|
2440
|
-
|
|
3062
|
+
unlinkSync5(path11.join(cacheDir, f));
|
|
2441
3063
|
}
|
|
2442
3064
|
}
|
|
2443
3065
|
}
|
|
@@ -2458,7 +3080,7 @@ var init_tasks_review = __esm({
|
|
|
2458
3080
|
});
|
|
2459
3081
|
|
|
2460
3082
|
// src/lib/tasks-chain.ts
|
|
2461
|
-
import
|
|
3083
|
+
import path12 from "path";
|
|
2462
3084
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2463
3085
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2464
3086
|
const client = getClient();
|
|
@@ -2475,7 +3097,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2475
3097
|
});
|
|
2476
3098
|
for (const ur of unblockedRows.rows) {
|
|
2477
3099
|
try {
|
|
2478
|
-
const ubFile =
|
|
3100
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2479
3101
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2480
3102
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2481
3103
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2544,7 +3166,7 @@ var init_tasks_chain = __esm({
|
|
|
2544
3166
|
|
|
2545
3167
|
// src/lib/project-name.ts
|
|
2546
3168
|
import { execSync as execSync6 } from "child_process";
|
|
2547
|
-
import
|
|
3169
|
+
import path13 from "path";
|
|
2548
3170
|
function getProjectName(cwd) {
|
|
2549
3171
|
const dir = cwd ?? process.cwd();
|
|
2550
3172
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2557,7 +3179,7 @@ function getProjectName(cwd) {
|
|
|
2557
3179
|
timeout: 2e3,
|
|
2558
3180
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2559
3181
|
}).trim();
|
|
2560
|
-
repoRoot =
|
|
3182
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2561
3183
|
} catch {
|
|
2562
3184
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2563
3185
|
cwd: dir,
|
|
@@ -2566,11 +3188,11 @@ function getProjectName(cwd) {
|
|
|
2566
3188
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2567
3189
|
}).trim();
|
|
2568
3190
|
}
|
|
2569
|
-
_cached2 =
|
|
3191
|
+
_cached2 = path13.basename(repoRoot);
|
|
2570
3192
|
_cachedCwd = dir;
|
|
2571
3193
|
return _cached2;
|
|
2572
3194
|
} catch {
|
|
2573
|
-
_cached2 =
|
|
3195
|
+
_cached2 = path13.basename(dir);
|
|
2574
3196
|
_cachedCwd = dir;
|
|
2575
3197
|
return _cached2;
|
|
2576
3198
|
}
|
|
@@ -2602,7 +3224,7 @@ function findSessionForProject(projectName) {
|
|
|
2602
3224
|
const sessions = listSessions();
|
|
2603
3225
|
for (const s of sessions) {
|
|
2604
3226
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2605
|
-
if (proj === projectName &&
|
|
3227
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2606
3228
|
}
|
|
2607
3229
|
return null;
|
|
2608
3230
|
}
|
|
@@ -2648,7 +3270,7 @@ var init_session_scope = __esm({
|
|
|
2648
3270
|
|
|
2649
3271
|
// src/lib/tasks-notify.ts
|
|
2650
3272
|
async function dispatchTaskToEmployee(input2) {
|
|
2651
|
-
if (
|
|
3273
|
+
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
2652
3274
|
let crossProject = false;
|
|
2653
3275
|
if (input2.projectName) {
|
|
2654
3276
|
try {
|
|
@@ -3043,8 +3665,8 @@ __export(tasks_exports, {
|
|
|
3043
3665
|
updateTaskStatus: () => updateTaskStatus,
|
|
3044
3666
|
writeCheckpoint: () => writeCheckpoint
|
|
3045
3667
|
});
|
|
3046
|
-
import
|
|
3047
|
-
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as
|
|
3668
|
+
import path14 from "path";
|
|
3669
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync6 } from "fs";
|
|
3048
3670
|
async function createTask(input2) {
|
|
3049
3671
|
const result = await createTaskCore(input2);
|
|
3050
3672
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3063,14 +3685,14 @@ async function updateTask(input2) {
|
|
|
3063
3685
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
3064
3686
|
try {
|
|
3065
3687
|
const agent = String(row.assigned_to);
|
|
3066
|
-
const cacheDir =
|
|
3067
|
-
const cachePath =
|
|
3688
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3689
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3068
3690
|
if (input2.status === "in_progress") {
|
|
3069
3691
|
mkdirSync5(cacheDir, { recursive: true });
|
|
3070
3692
|
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3071
3693
|
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
3072
3694
|
try {
|
|
3073
|
-
|
|
3695
|
+
unlinkSync6(cachePath);
|
|
3074
3696
|
} catch {
|
|
3075
3697
|
}
|
|
3076
3698
|
}
|
|
@@ -3127,7 +3749,7 @@ async function updateTask(input2) {
|
|
|
3127
3749
|
}
|
|
3128
3750
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
3129
3751
|
if (isTerminal) {
|
|
3130
|
-
const isCoordinator =
|
|
3752
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3131
3753
|
if (!isCoordinator) {
|
|
3132
3754
|
notifyTaskDone();
|
|
3133
3755
|
}
|
|
@@ -3152,7 +3774,7 @@ async function updateTask(input2) {
|
|
|
3152
3774
|
}
|
|
3153
3775
|
}
|
|
3154
3776
|
}
|
|
3155
|
-
if (input2.status === "done" &&
|
|
3777
|
+
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3156
3778
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3157
3779
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3158
3780
|
taskId,
|
|
@@ -3168,7 +3790,7 @@ async function updateTask(input2) {
|
|
|
3168
3790
|
});
|
|
3169
3791
|
}
|
|
3170
3792
|
let nextTask;
|
|
3171
|
-
if (isTerminal &&
|
|
3793
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3172
3794
|
try {
|
|
3173
3795
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3174
3796
|
} catch {
|
|
@@ -3512,7 +4134,7 @@ var init_capacity_monitor = __esm({
|
|
|
3512
4134
|
// src/lib/tmux-routing.ts
|
|
3513
4135
|
var tmux_routing_exports = {};
|
|
3514
4136
|
__export(tmux_routing_exports, {
|
|
3515
|
-
acquireSpawnLock: () =>
|
|
4137
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3516
4138
|
employeeSessionName: () => employeeSessionName,
|
|
3517
4139
|
ensureEmployee: () => ensureEmployee,
|
|
3518
4140
|
extractRootExe: () => extractRootExe,
|
|
@@ -3527,20 +4149,20 @@ __export(tmux_routing_exports, {
|
|
|
3527
4149
|
notifyParentExe: () => notifyParentExe,
|
|
3528
4150
|
parseParentExe: () => parseParentExe,
|
|
3529
4151
|
registerParentExe: () => registerParentExe,
|
|
3530
|
-
releaseSpawnLock: () =>
|
|
4152
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3531
4153
|
resolveExeSession: () => resolveExeSession,
|
|
3532
4154
|
sendIntercom: () => sendIntercom,
|
|
3533
4155
|
spawnEmployee: () => spawnEmployee,
|
|
3534
4156
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3535
4157
|
});
|
|
3536
4158
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3537
|
-
import { readFileSync as
|
|
3538
|
-
import
|
|
3539
|
-
import
|
|
3540
|
-
import { fileURLToPath } from "url";
|
|
3541
|
-
import { unlinkSync as
|
|
4159
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4160
|
+
import path15 from "path";
|
|
4161
|
+
import os7 from "os";
|
|
4162
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4163
|
+
import { unlinkSync as unlinkSync7 } from "fs";
|
|
3542
4164
|
function spawnLockPath(sessionName) {
|
|
3543
|
-
return
|
|
4165
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3544
4166
|
}
|
|
3545
4167
|
function isProcessAlive(pid) {
|
|
3546
4168
|
try {
|
|
@@ -3550,14 +4172,14 @@ function isProcessAlive(pid) {
|
|
|
3550
4172
|
return false;
|
|
3551
4173
|
}
|
|
3552
4174
|
}
|
|
3553
|
-
function
|
|
3554
|
-
if (!
|
|
4175
|
+
function acquireSpawnLock2(sessionName) {
|
|
4176
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3555
4177
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
3556
4178
|
}
|
|
3557
4179
|
const lockFile = spawnLockPath(sessionName);
|
|
3558
|
-
if (
|
|
4180
|
+
if (existsSync11(lockFile)) {
|
|
3559
4181
|
try {
|
|
3560
|
-
const lock = JSON.parse(
|
|
4182
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
3561
4183
|
const age = Date.now() - lock.timestamp;
|
|
3562
4184
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3563
4185
|
return false;
|
|
@@ -3568,22 +4190,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3568
4190
|
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3569
4191
|
return true;
|
|
3570
4192
|
}
|
|
3571
|
-
function
|
|
4193
|
+
function releaseSpawnLock2(sessionName) {
|
|
3572
4194
|
try {
|
|
3573
|
-
|
|
4195
|
+
unlinkSync7(spawnLockPath(sessionName));
|
|
3574
4196
|
} catch {
|
|
3575
4197
|
}
|
|
3576
4198
|
}
|
|
3577
4199
|
function resolveBehaviorsExporterScript() {
|
|
3578
4200
|
try {
|
|
3579
|
-
const thisFile =
|
|
3580
|
-
const scriptPath =
|
|
3581
|
-
|
|
4201
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4202
|
+
const scriptPath = path15.join(
|
|
4203
|
+
path15.dirname(thisFile),
|
|
3582
4204
|
"..",
|
|
3583
4205
|
"bin",
|
|
3584
4206
|
"exe-export-behaviors.js"
|
|
3585
4207
|
);
|
|
3586
|
-
return
|
|
4208
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3587
4209
|
} catch {
|
|
3588
4210
|
return null;
|
|
3589
4211
|
}
|
|
@@ -3649,11 +4271,11 @@ function extractRootExe(name) {
|
|
|
3649
4271
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3650
4272
|
}
|
|
3651
4273
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3652
|
-
if (!
|
|
4274
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3653
4275
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3654
4276
|
}
|
|
3655
4277
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3656
|
-
const filePath =
|
|
4278
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3657
4279
|
writeFileSync7(filePath, JSON.stringify({
|
|
3658
4280
|
parentExe: rootExe,
|
|
3659
4281
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3662,7 +4284,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3662
4284
|
}
|
|
3663
4285
|
function getParentExe(sessionKey) {
|
|
3664
4286
|
try {
|
|
3665
|
-
const data = JSON.parse(
|
|
4287
|
+
const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3666
4288
|
return data.parentExe || null;
|
|
3667
4289
|
} catch {
|
|
3668
4290
|
return null;
|
|
@@ -3670,8 +4292,8 @@ function getParentExe(sessionKey) {
|
|
|
3670
4292
|
}
|
|
3671
4293
|
function getDispatchedBy(sessionKey) {
|
|
3672
4294
|
try {
|
|
3673
|
-
const data = JSON.parse(
|
|
3674
|
-
|
|
4295
|
+
const data = JSON.parse(readFileSync11(
|
|
4296
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3675
4297
|
"utf8"
|
|
3676
4298
|
));
|
|
3677
4299
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3697,10 +4319,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3697
4319
|
}
|
|
3698
4320
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3699
4321
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3700
|
-
if (!isAlive(base) &&
|
|
4322
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3701
4323
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3702
4324
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3703
|
-
if (!isAlive(candidate) &&
|
|
4325
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3704
4326
|
}
|
|
3705
4327
|
return null;
|
|
3706
4328
|
}
|
|
@@ -3732,15 +4354,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3732
4354
|
}
|
|
3733
4355
|
function readDebounceState() {
|
|
3734
4356
|
try {
|
|
3735
|
-
if (!
|
|
3736
|
-
return JSON.parse(
|
|
4357
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4358
|
+
return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
3737
4359
|
} catch {
|
|
3738
4360
|
return {};
|
|
3739
4361
|
}
|
|
3740
4362
|
}
|
|
3741
4363
|
function writeDebounceState(state) {
|
|
3742
4364
|
try {
|
|
3743
|
-
if (!
|
|
4365
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
3744
4366
|
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3745
4367
|
} catch {
|
|
3746
4368
|
}
|
|
@@ -3860,7 +4482,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3860
4482
|
return true;
|
|
3861
4483
|
}
|
|
3862
4484
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3863
|
-
if (
|
|
4485
|
+
if (isCoordinatorName(employeeName)) {
|
|
3864
4486
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3865
4487
|
}
|
|
3866
4488
|
try {
|
|
@@ -3932,26 +4554,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3932
4554
|
const transport = getTransport();
|
|
3933
4555
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3934
4556
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3935
|
-
const logDir =
|
|
3936
|
-
const logFile =
|
|
3937
|
-
if (!
|
|
4557
|
+
const logDir = path15.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4558
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4559
|
+
if (!existsSync11(logDir)) {
|
|
3938
4560
|
mkdirSync6(logDir, { recursive: true });
|
|
3939
4561
|
}
|
|
3940
4562
|
transport.kill(sessionName);
|
|
3941
4563
|
let cleanupSuffix = "";
|
|
3942
4564
|
try {
|
|
3943
|
-
const thisFile =
|
|
3944
|
-
const cleanupScript =
|
|
3945
|
-
if (
|
|
4565
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4566
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4567
|
+
if (existsSync11(cleanupScript)) {
|
|
3946
4568
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3947
4569
|
}
|
|
3948
4570
|
} catch {
|
|
3949
4571
|
}
|
|
3950
4572
|
try {
|
|
3951
|
-
const claudeJsonPath =
|
|
4573
|
+
const claudeJsonPath = path15.join(os7.homedir(), ".claude.json");
|
|
3952
4574
|
let claudeJson = {};
|
|
3953
4575
|
try {
|
|
3954
|
-
claudeJson = JSON.parse(
|
|
4576
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
3955
4577
|
} catch {
|
|
3956
4578
|
}
|
|
3957
4579
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3963,13 +4585,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3963
4585
|
} catch {
|
|
3964
4586
|
}
|
|
3965
4587
|
try {
|
|
3966
|
-
const settingsDir =
|
|
4588
|
+
const settingsDir = path15.join(os7.homedir(), ".claude", "projects");
|
|
3967
4589
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3968
|
-
const projSettingsDir =
|
|
3969
|
-
const settingsPath =
|
|
4590
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
4591
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
3970
4592
|
let settings = {};
|
|
3971
4593
|
try {
|
|
3972
|
-
settings = JSON.parse(
|
|
4594
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
3973
4595
|
} catch {
|
|
3974
4596
|
}
|
|
3975
4597
|
const perms = settings.permissions ?? {};
|
|
@@ -4010,8 +4632,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4010
4632
|
let behaviorsFlag = "";
|
|
4011
4633
|
let legacyFallbackWarned = false;
|
|
4012
4634
|
if (!useExeAgent && !useBinSymlink) {
|
|
4013
|
-
const identityPath =
|
|
4014
|
-
|
|
4635
|
+
const identityPath = path15.join(
|
|
4636
|
+
os7.homedir(),
|
|
4015
4637
|
".exe-os",
|
|
4016
4638
|
"identity",
|
|
4017
4639
|
`${employeeName}.md`
|
|
@@ -4020,13 +4642,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4020
4642
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4021
4643
|
if (hasAgentFlag) {
|
|
4022
4644
|
identityFlag = ` --agent ${employeeName}`;
|
|
4023
|
-
} else if (
|
|
4645
|
+
} else if (existsSync11(identityPath)) {
|
|
4024
4646
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4025
4647
|
legacyFallbackWarned = true;
|
|
4026
4648
|
}
|
|
4027
4649
|
const behaviorsFile = exportBehaviorsSync(
|
|
4028
4650
|
employeeName,
|
|
4029
|
-
|
|
4651
|
+
path15.basename(spawnCwd),
|
|
4030
4652
|
sessionName
|
|
4031
4653
|
);
|
|
4032
4654
|
if (behaviorsFile) {
|
|
@@ -4041,9 +4663,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4041
4663
|
}
|
|
4042
4664
|
let sessionContextFlag = "";
|
|
4043
4665
|
try {
|
|
4044
|
-
const ctxDir =
|
|
4666
|
+
const ctxDir = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4045
4667
|
mkdirSync6(ctxDir, { recursive: true });
|
|
4046
|
-
const ctxFile =
|
|
4668
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4047
4669
|
const ctxContent = [
|
|
4048
4670
|
`## Session Context`,
|
|
4049
4671
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4082,13 +4704,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4082
4704
|
command: spawnCommand
|
|
4083
4705
|
});
|
|
4084
4706
|
if (spawnResult.error) {
|
|
4085
|
-
|
|
4707
|
+
releaseSpawnLock2(sessionName);
|
|
4086
4708
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4087
4709
|
}
|
|
4088
4710
|
transport.pipeLog(sessionName, logFile);
|
|
4089
4711
|
try {
|
|
4090
4712
|
const mySession = getMySession();
|
|
4091
|
-
const dispatchInfo =
|
|
4713
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4092
4714
|
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4093
4715
|
dispatchedBy: mySession,
|
|
4094
4716
|
rootExe: exeSession,
|
|
@@ -4120,7 +4742,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4120
4742
|
}
|
|
4121
4743
|
}
|
|
4122
4744
|
if (!booted) {
|
|
4123
|
-
|
|
4745
|
+
releaseSpawnLock2(sessionName);
|
|
4124
4746
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4125
4747
|
}
|
|
4126
4748
|
if (!useExeAgent) {
|
|
@@ -4137,7 +4759,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4137
4759
|
pid: 0,
|
|
4138
4760
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4139
4761
|
});
|
|
4140
|
-
|
|
4762
|
+
releaseSpawnLock2(sessionName);
|
|
4141
4763
|
return { sessionName };
|
|
4142
4764
|
}
|
|
4143
4765
|
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;
|
|
@@ -4153,14 +4775,14 @@ var init_tmux_routing = __esm({
|
|
|
4153
4775
|
init_intercom_queue();
|
|
4154
4776
|
init_plan_limits();
|
|
4155
4777
|
init_employees();
|
|
4156
|
-
SPAWN_LOCK_DIR =
|
|
4157
|
-
SESSION_CACHE =
|
|
4778
|
+
SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4779
|
+
SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4158
4780
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4159
4781
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4160
4782
|
VERIFY_PANE_LINES = 200;
|
|
4161
4783
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4162
|
-
INTERCOM_LOG2 =
|
|
4163
|
-
DEBOUNCE_FILE =
|
|
4784
|
+
INTERCOM_LOG2 = path15.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4785
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4164
4786
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4165
4787
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4166
4788
|
}
|
|
@@ -4201,14 +4823,14 @@ var init_memory = __esm({
|
|
|
4201
4823
|
|
|
4202
4824
|
// src/lib/keychain.ts
|
|
4203
4825
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import
|
|
4206
|
-
import
|
|
4826
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4827
|
+
import path16 from "path";
|
|
4828
|
+
import os8 from "os";
|
|
4207
4829
|
function getKeyDir() {
|
|
4208
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4830
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os8.homedir(), ".exe-os");
|
|
4209
4831
|
}
|
|
4210
4832
|
function getKeyPath() {
|
|
4211
|
-
return
|
|
4833
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4212
4834
|
}
|
|
4213
4835
|
async function tryKeytar() {
|
|
4214
4836
|
try {
|
|
@@ -4229,13 +4851,21 @@ async function getMasterKey() {
|
|
|
4229
4851
|
}
|
|
4230
4852
|
}
|
|
4231
4853
|
const keyPath = getKeyPath();
|
|
4232
|
-
if (!
|
|
4854
|
+
if (!existsSync12(keyPath)) {
|
|
4855
|
+
process.stderr.write(
|
|
4856
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4857
|
+
`
|
|
4858
|
+
);
|
|
4233
4859
|
return null;
|
|
4234
4860
|
}
|
|
4235
4861
|
try {
|
|
4236
4862
|
const content = await readFile4(keyPath, "utf-8");
|
|
4237
4863
|
return Buffer.from(content.trim(), "base64");
|
|
4238
|
-
} catch {
|
|
4864
|
+
} catch (err) {
|
|
4865
|
+
process.stderr.write(
|
|
4866
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4867
|
+
`
|
|
4868
|
+
);
|
|
4239
4869
|
return null;
|
|
4240
4870
|
}
|
|
4241
4871
|
}
|
|
@@ -4261,12 +4891,12 @@ __export(shard_manager_exports, {
|
|
|
4261
4891
|
listShards: () => listShards,
|
|
4262
4892
|
shardExists: () => shardExists
|
|
4263
4893
|
});
|
|
4264
|
-
import
|
|
4265
|
-
import { existsSync as
|
|
4894
|
+
import path17 from "path";
|
|
4895
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
4266
4896
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4267
4897
|
function initShardManager(encryptionKey) {
|
|
4268
4898
|
_encryptionKey = encryptionKey;
|
|
4269
|
-
if (!
|
|
4899
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4270
4900
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
4271
4901
|
}
|
|
4272
4902
|
_shardingEnabled = true;
|
|
@@ -4287,7 +4917,7 @@ function getShardClient(projectName) {
|
|
|
4287
4917
|
}
|
|
4288
4918
|
const cached = _shards.get(safeName);
|
|
4289
4919
|
if (cached) return cached;
|
|
4290
|
-
const dbPath =
|
|
4920
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4291
4921
|
const client = createClient2({
|
|
4292
4922
|
url: `file:${dbPath}`,
|
|
4293
4923
|
encryptionKey: _encryptionKey
|
|
@@ -4297,10 +4927,10 @@ function getShardClient(projectName) {
|
|
|
4297
4927
|
}
|
|
4298
4928
|
function shardExists(projectName) {
|
|
4299
4929
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4300
|
-
return
|
|
4930
|
+
return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4301
4931
|
}
|
|
4302
4932
|
function listShards() {
|
|
4303
|
-
if (!
|
|
4933
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4304
4934
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4305
4935
|
}
|
|
4306
4936
|
async function ensureShardSchema(client) {
|
|
@@ -4486,7 +5116,7 @@ var init_shard_manager = __esm({
|
|
|
4486
5116
|
"src/lib/shard-manager.ts"() {
|
|
4487
5117
|
"use strict";
|
|
4488
5118
|
init_config();
|
|
4489
|
-
SHARDS_DIR =
|
|
5119
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4490
5120
|
_shards = /* @__PURE__ */ new Map();
|
|
4491
5121
|
_encryptionKey = null;
|
|
4492
5122
|
_shardingEnabled = false;
|
|
@@ -4611,7 +5241,7 @@ __export(global_procedures_exports, {
|
|
|
4611
5241
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4612
5242
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4613
5243
|
});
|
|
4614
|
-
import { randomUUID as
|
|
5244
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4615
5245
|
async function loadGlobalProcedures() {
|
|
4616
5246
|
const client = getClient();
|
|
4617
5247
|
const result = await client.execute({
|
|
@@ -4640,7 +5270,7 @@ ${sections.join("\n\n")}
|
|
|
4640
5270
|
`;
|
|
4641
5271
|
}
|
|
4642
5272
|
async function storeGlobalProcedure(input2) {
|
|
4643
|
-
const id =
|
|
5273
|
+
const id = randomUUID3();
|
|
4644
5274
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4645
5275
|
const client = getClient();
|
|
4646
5276
|
await client.execute({
|
|
@@ -4691,6 +5321,7 @@ __export(store_exports, {
|
|
|
4691
5321
|
vectorToBlob: () => vectorToBlob,
|
|
4692
5322
|
writeMemory: () => writeMemory
|
|
4693
5323
|
});
|
|
5324
|
+
import { createHash } from "crypto";
|
|
4694
5325
|
function isBusyError2(err) {
|
|
4695
5326
|
if (err instanceof Error) {
|
|
4696
5327
|
const msg = err.message.toLowerCase();
|
|
@@ -4764,12 +5395,52 @@ function classifyTier(record) {
|
|
|
4764
5395
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4765
5396
|
return 3;
|
|
4766
5397
|
}
|
|
5398
|
+
function inferFilePaths(record) {
|
|
5399
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5400
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5401
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5402
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5403
|
+
}
|
|
5404
|
+
function inferCommitHash(record) {
|
|
5405
|
+
if (record.tool_name !== "Bash") return null;
|
|
5406
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5407
|
+
return match ? match[1] : null;
|
|
5408
|
+
}
|
|
5409
|
+
function inferLanguageType(record) {
|
|
5410
|
+
const text = record.raw_text;
|
|
5411
|
+
if (!text || text.length < 10) return null;
|
|
5412
|
+
const trimmed = text.trimStart();
|
|
5413
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5414
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5415
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5416
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5417
|
+
return "mixed";
|
|
5418
|
+
}
|
|
5419
|
+
function inferDomain(record) {
|
|
5420
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5421
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5422
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5423
|
+
return null;
|
|
5424
|
+
}
|
|
4767
5425
|
async function writeMemory(record) {
|
|
4768
5426
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4769
5427
|
throw new Error(
|
|
4770
5428
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4771
5429
|
);
|
|
4772
5430
|
}
|
|
5431
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5432
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5433
|
+
return;
|
|
5434
|
+
}
|
|
5435
|
+
try {
|
|
5436
|
+
const client = getClient();
|
|
5437
|
+
const existing = await client.execute({
|
|
5438
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5439
|
+
args: [contentHash, record.agent_id]
|
|
5440
|
+
});
|
|
5441
|
+
if (existing.rows.length > 0) return;
|
|
5442
|
+
} catch {
|
|
5443
|
+
}
|
|
4773
5444
|
const dbRow = {
|
|
4774
5445
|
id: record.id,
|
|
4775
5446
|
agent_id: record.agent_id,
|
|
@@ -4799,7 +5470,23 @@ async function writeMemory(record) {
|
|
|
4799
5470
|
supersedes_id: record.supersedes_id ?? null,
|
|
4800
5471
|
draft: record.draft ? 1 : 0,
|
|
4801
5472
|
memory_type: record.memory_type ?? "raw",
|
|
4802
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5473
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5474
|
+
content_hash: contentHash,
|
|
5475
|
+
intent: record.intent ?? null,
|
|
5476
|
+
outcome: record.outcome ?? null,
|
|
5477
|
+
domain: record.domain ?? inferDomain(record),
|
|
5478
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5479
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5480
|
+
chain_position: record.chain_position ?? null,
|
|
5481
|
+
review_status: record.review_status ?? null,
|
|
5482
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5483
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5484
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5485
|
+
duration_ms: record.duration_ms ?? null,
|
|
5486
|
+
token_cost: record.token_cost ?? null,
|
|
5487
|
+
audience: record.audience ?? null,
|
|
5488
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5489
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4803
5490
|
};
|
|
4804
5491
|
_pendingRecords.push(dbRow);
|
|
4805
5492
|
orgBus.emit({
|
|
@@ -4857,80 +5544,85 @@ async function flushBatch() {
|
|
|
4857
5544
|
const draft = row.draft ? 1 : 0;
|
|
4858
5545
|
const memoryType = row.memory_type ?? "raw";
|
|
4859
5546
|
const trajectory = row.trajectory ?? null;
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
5547
|
+
const contentHash = row.content_hash ?? null;
|
|
5548
|
+
const intent = row.intent ?? null;
|
|
5549
|
+
const outcome = row.outcome ?? null;
|
|
5550
|
+
const domain = row.domain ?? null;
|
|
5551
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5552
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5553
|
+
const chainPosition = row.chain_position ?? null;
|
|
5554
|
+
const reviewStatus = row.review_status ?? null;
|
|
5555
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5556
|
+
const filePaths = row.file_paths ?? null;
|
|
5557
|
+
const commitHash = row.commit_hash ?? null;
|
|
5558
|
+
const durationMs = row.duration_ms ?? null;
|
|
5559
|
+
const tokenCost = row.token_cost ?? null;
|
|
5560
|
+
const audience = row.audience ?? null;
|
|
5561
|
+
const languageType = row.language_type ?? null;
|
|
5562
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5563
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4870
5564
|
tool_name, project_name,
|
|
4871
5565
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4872
5566
|
confidence, last_accessed,
|
|
4873
5567
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4874
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
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
|
-
trajectory
|
|
4933
|
-
]
|
|
5568
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5569
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5570
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5571
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5572
|
+
const metaArgs = [
|
|
5573
|
+
intent,
|
|
5574
|
+
outcome,
|
|
5575
|
+
domain,
|
|
5576
|
+
referencedEntities,
|
|
5577
|
+
retrievalCount,
|
|
5578
|
+
chainPosition,
|
|
5579
|
+
reviewStatus,
|
|
5580
|
+
contextWindowPct,
|
|
5581
|
+
filePaths,
|
|
5582
|
+
commitHash,
|
|
5583
|
+
durationMs,
|
|
5584
|
+
tokenCost,
|
|
5585
|
+
audience,
|
|
5586
|
+
languageType,
|
|
5587
|
+
parentMemoryId
|
|
5588
|
+
];
|
|
5589
|
+
const baseArgs = [
|
|
5590
|
+
row.id,
|
|
5591
|
+
row.agent_id,
|
|
5592
|
+
row.agent_role,
|
|
5593
|
+
row.session_id,
|
|
5594
|
+
row.timestamp,
|
|
5595
|
+
row.tool_name,
|
|
5596
|
+
row.project_name,
|
|
5597
|
+
row.has_error,
|
|
5598
|
+
row.raw_text
|
|
5599
|
+
];
|
|
5600
|
+
const sharedArgs = [
|
|
5601
|
+
row.version,
|
|
5602
|
+
taskId,
|
|
5603
|
+
importance,
|
|
5604
|
+
status,
|
|
5605
|
+
confidence,
|
|
5606
|
+
lastAccessed,
|
|
5607
|
+
workspaceId,
|
|
5608
|
+
documentId,
|
|
5609
|
+
userId,
|
|
5610
|
+
charOffset,
|
|
5611
|
+
pageNumber,
|
|
5612
|
+
sourcePath,
|
|
5613
|
+
sourceType,
|
|
5614
|
+
tier,
|
|
5615
|
+
supersedesId,
|
|
5616
|
+
draft,
|
|
5617
|
+
memoryType,
|
|
5618
|
+
trajectory,
|
|
5619
|
+
contentHash
|
|
5620
|
+
];
|
|
5621
|
+
return {
|
|
5622
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5623
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5624
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5625
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4934
5626
|
};
|
|
4935
5627
|
};
|
|
4936
5628
|
const globalClient = getClient();
|