@askexenow/exe-os 0.8.83 → 0.8.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -607,6 +607,443 @@ var init_employees = __esm({
|
|
|
607
607
|
}
|
|
608
608
|
});
|
|
609
609
|
|
|
610
|
+
// src/lib/exe-daemon-client.ts
|
|
611
|
+
import net from "net";
|
|
612
|
+
import { spawn } from "child_process";
|
|
613
|
+
import { randomUUID } from "crypto";
|
|
614
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
615
|
+
import path5 from "path";
|
|
616
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
617
|
+
function handleData(chunk) {
|
|
618
|
+
_buffer += chunk.toString();
|
|
619
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
620
|
+
_buffer = "";
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
let newlineIdx;
|
|
624
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
625
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
626
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
627
|
+
if (!line) continue;
|
|
628
|
+
try {
|
|
629
|
+
const response = JSON.parse(line);
|
|
630
|
+
const id = response.id;
|
|
631
|
+
if (!id) continue;
|
|
632
|
+
const entry = _pending.get(id);
|
|
633
|
+
if (entry) {
|
|
634
|
+
clearTimeout(entry.timer);
|
|
635
|
+
_pending.delete(id);
|
|
636
|
+
entry.resolve(response);
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function cleanupStaleFiles() {
|
|
643
|
+
if (existsSync5(PID_PATH)) {
|
|
644
|
+
try {
|
|
645
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
646
|
+
if (pid > 0) {
|
|
647
|
+
try {
|
|
648
|
+
process.kill(pid, 0);
|
|
649
|
+
return;
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
unlinkSync2(PID_PATH);
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
unlinkSync2(SOCKET_PATH);
|
|
661
|
+
} catch {
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function findPackageRoot() {
|
|
666
|
+
let dir = path5.dirname(fileURLToPath2(import.meta.url));
|
|
667
|
+
const { root } = path5.parse(dir);
|
|
668
|
+
while (dir !== root) {
|
|
669
|
+
if (existsSync5(path5.join(dir, "package.json"))) return dir;
|
|
670
|
+
dir = path5.dirname(dir);
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function spawnDaemon() {
|
|
675
|
+
const pkgRoot = findPackageRoot();
|
|
676
|
+
if (!pkgRoot) {
|
|
677
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
681
|
+
if (!existsSync5(daemonPath)) {
|
|
682
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
683
|
+
`);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const resolvedPath = daemonPath;
|
|
687
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
688
|
+
`);
|
|
689
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
690
|
+
let stderrFd = "ignore";
|
|
691
|
+
try {
|
|
692
|
+
stderrFd = openSync(logPath, "a");
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
696
|
+
detached: true,
|
|
697
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
698
|
+
env: {
|
|
699
|
+
...process.env,
|
|
700
|
+
TMUX: void 0,
|
|
701
|
+
// Daemon is global — must not inherit session scope
|
|
702
|
+
TMUX_PANE: void 0,
|
|
703
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
704
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
705
|
+
EXE_DAEMON_PID: PID_PATH
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
child.unref();
|
|
709
|
+
if (typeof stderrFd === "number") {
|
|
710
|
+
try {
|
|
711
|
+
closeSync(stderrFd);
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function acquireSpawnLock() {
|
|
717
|
+
try {
|
|
718
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
719
|
+
closeSync(fd);
|
|
720
|
+
return true;
|
|
721
|
+
} catch {
|
|
722
|
+
try {
|
|
723
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
724
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
725
|
+
try {
|
|
726
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
731
|
+
closeSync(fd);
|
|
732
|
+
return true;
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function releaseSpawnLock() {
|
|
742
|
+
try {
|
|
743
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function connectToSocket() {
|
|
748
|
+
return new Promise((resolve) => {
|
|
749
|
+
if (_socket && _connected) {
|
|
750
|
+
resolve(true);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
754
|
+
const connectTimeout = setTimeout(() => {
|
|
755
|
+
socket.destroy();
|
|
756
|
+
resolve(false);
|
|
757
|
+
}, 2e3);
|
|
758
|
+
socket.on("connect", () => {
|
|
759
|
+
clearTimeout(connectTimeout);
|
|
760
|
+
_socket = socket;
|
|
761
|
+
_connected = true;
|
|
762
|
+
_buffer = "";
|
|
763
|
+
socket.on("data", handleData);
|
|
764
|
+
socket.on("close", () => {
|
|
765
|
+
_connected = false;
|
|
766
|
+
_socket = null;
|
|
767
|
+
for (const [id, entry] of _pending) {
|
|
768
|
+
clearTimeout(entry.timer);
|
|
769
|
+
_pending.delete(id);
|
|
770
|
+
entry.resolve({ error: "Connection closed" });
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
socket.on("error", () => {
|
|
774
|
+
_connected = false;
|
|
775
|
+
_socket = null;
|
|
776
|
+
});
|
|
777
|
+
resolve(true);
|
|
778
|
+
});
|
|
779
|
+
socket.on("error", () => {
|
|
780
|
+
clearTimeout(connectTimeout);
|
|
781
|
+
resolve(false);
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
async function connectEmbedDaemon() {
|
|
786
|
+
if (_socket && _connected) return true;
|
|
787
|
+
if (await connectToSocket()) return true;
|
|
788
|
+
if (acquireSpawnLock()) {
|
|
789
|
+
try {
|
|
790
|
+
cleanupStaleFiles();
|
|
791
|
+
spawnDaemon();
|
|
792
|
+
} finally {
|
|
793
|
+
releaseSpawnLock();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const start = Date.now();
|
|
797
|
+
let delay2 = 100;
|
|
798
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
799
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
800
|
+
if (await connectToSocket()) return true;
|
|
801
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
802
|
+
}
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
806
|
+
return new Promise((resolve) => {
|
|
807
|
+
if (!_socket || !_connected) {
|
|
808
|
+
resolve({ error: "Not connected" });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const id = randomUUID();
|
|
812
|
+
const timer = setTimeout(() => {
|
|
813
|
+
_pending.delete(id);
|
|
814
|
+
resolve({ error: "Request timeout" });
|
|
815
|
+
}, timeoutMs);
|
|
816
|
+
_pending.set(id, { resolve, timer });
|
|
817
|
+
try {
|
|
818
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
819
|
+
} catch {
|
|
820
|
+
clearTimeout(timer);
|
|
821
|
+
_pending.delete(id);
|
|
822
|
+
resolve({ error: "Write failed" });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
function isClientConnected() {
|
|
827
|
+
return _connected;
|
|
828
|
+
}
|
|
829
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
830
|
+
var init_exe_daemon_client = __esm({
|
|
831
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
832
|
+
"use strict";
|
|
833
|
+
init_config();
|
|
834
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
835
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
836
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
837
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
838
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
839
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
840
|
+
_socket = null;
|
|
841
|
+
_connected = false;
|
|
842
|
+
_buffer = "";
|
|
843
|
+
_pending = /* @__PURE__ */ new Map();
|
|
844
|
+
MAX_BUFFER = 1e7;
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// src/lib/daemon-protocol.ts
|
|
849
|
+
function serializeValue(v) {
|
|
850
|
+
if (v === null || v === void 0) return null;
|
|
851
|
+
if (typeof v === "bigint") return Number(v);
|
|
852
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
853
|
+
if (v instanceof Uint8Array) {
|
|
854
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
855
|
+
}
|
|
856
|
+
if (ArrayBuffer.isView(v)) {
|
|
857
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
858
|
+
}
|
|
859
|
+
if (v instanceof ArrayBuffer) {
|
|
860
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
861
|
+
}
|
|
862
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
863
|
+
return String(v);
|
|
864
|
+
}
|
|
865
|
+
function deserializeValue(v) {
|
|
866
|
+
if (v === null) return null;
|
|
867
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
868
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
869
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
870
|
+
}
|
|
871
|
+
return v;
|
|
872
|
+
}
|
|
873
|
+
function deserializeResultSet(srs) {
|
|
874
|
+
const rows = srs.rows.map((obj) => {
|
|
875
|
+
const values = srs.columns.map(
|
|
876
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
877
|
+
);
|
|
878
|
+
const row = values;
|
|
879
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
880
|
+
const col = srs.columns[i];
|
|
881
|
+
if (col !== void 0) {
|
|
882
|
+
row[col] = values[i] ?? null;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
Object.defineProperty(row, "length", {
|
|
886
|
+
value: values.length,
|
|
887
|
+
enumerable: false
|
|
888
|
+
});
|
|
889
|
+
return row;
|
|
890
|
+
});
|
|
891
|
+
return {
|
|
892
|
+
columns: srs.columns,
|
|
893
|
+
columnTypes: srs.columnTypes ?? [],
|
|
894
|
+
rows,
|
|
895
|
+
rowsAffected: srs.rowsAffected,
|
|
896
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
897
|
+
toJSON: () => ({
|
|
898
|
+
columns: srs.columns,
|
|
899
|
+
columnTypes: srs.columnTypes ?? [],
|
|
900
|
+
rows: srs.rows,
|
|
901
|
+
rowsAffected: srs.rowsAffected,
|
|
902
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
903
|
+
})
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
var init_daemon_protocol = __esm({
|
|
907
|
+
"src/lib/daemon-protocol.ts"() {
|
|
908
|
+
"use strict";
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// src/lib/db-daemon-client.ts
|
|
913
|
+
var db_daemon_client_exports = {};
|
|
914
|
+
__export(db_daemon_client_exports, {
|
|
915
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
916
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
917
|
+
});
|
|
918
|
+
function normalizeStatement(stmt) {
|
|
919
|
+
if (typeof stmt === "string") {
|
|
920
|
+
return { sql: stmt, args: [] };
|
|
921
|
+
}
|
|
922
|
+
const sql = stmt.sql;
|
|
923
|
+
let args = [];
|
|
924
|
+
if (Array.isArray(stmt.args)) {
|
|
925
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
926
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
927
|
+
const named = {};
|
|
928
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
929
|
+
named[key] = serializeValue(val);
|
|
930
|
+
}
|
|
931
|
+
return { sql, args: named };
|
|
932
|
+
}
|
|
933
|
+
return { sql, args };
|
|
934
|
+
}
|
|
935
|
+
function createDaemonDbClient(fallbackClient) {
|
|
936
|
+
let _useDaemon = false;
|
|
937
|
+
const client = {
|
|
938
|
+
async execute(stmt) {
|
|
939
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
940
|
+
return fallbackClient.execute(stmt);
|
|
941
|
+
}
|
|
942
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
943
|
+
const response = await sendDaemonRequest({
|
|
944
|
+
type: "db-execute",
|
|
945
|
+
sql,
|
|
946
|
+
args
|
|
947
|
+
});
|
|
948
|
+
if (response.error) {
|
|
949
|
+
const errMsg = String(response.error);
|
|
950
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
951
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
952
|
+
`);
|
|
953
|
+
return fallbackClient.execute(stmt);
|
|
954
|
+
}
|
|
955
|
+
throw new Error(errMsg);
|
|
956
|
+
}
|
|
957
|
+
if (response.db) {
|
|
958
|
+
return deserializeResultSet(response.db);
|
|
959
|
+
}
|
|
960
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
961
|
+
return fallbackClient.execute(stmt);
|
|
962
|
+
},
|
|
963
|
+
async batch(stmts, mode) {
|
|
964
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
965
|
+
return fallbackClient.batch(stmts, mode);
|
|
966
|
+
}
|
|
967
|
+
const statements = stmts.map(normalizeStatement);
|
|
968
|
+
const response = await sendDaemonRequest({
|
|
969
|
+
type: "db-batch",
|
|
970
|
+
statements,
|
|
971
|
+
mode: mode ?? "deferred"
|
|
972
|
+
});
|
|
973
|
+
if (response.error) {
|
|
974
|
+
const errMsg = String(response.error);
|
|
975
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
976
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
977
|
+
`);
|
|
978
|
+
return fallbackClient.batch(stmts, mode);
|
|
979
|
+
}
|
|
980
|
+
throw new Error(errMsg);
|
|
981
|
+
}
|
|
982
|
+
const batchResults = response["db-batch"];
|
|
983
|
+
if (batchResults) {
|
|
984
|
+
return batchResults.map(deserializeResultSet);
|
|
985
|
+
}
|
|
986
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
987
|
+
return fallbackClient.batch(stmts, mode);
|
|
988
|
+
},
|
|
989
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
990
|
+
async transaction(mode) {
|
|
991
|
+
return fallbackClient.transaction(mode);
|
|
992
|
+
},
|
|
993
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
994
|
+
async executeMultiple(sql) {
|
|
995
|
+
return fallbackClient.executeMultiple(sql);
|
|
996
|
+
},
|
|
997
|
+
// migrate — delegate to fallback
|
|
998
|
+
async migrate(stmts) {
|
|
999
|
+
return fallbackClient.migrate(stmts);
|
|
1000
|
+
},
|
|
1001
|
+
// Sync mode — delegate to fallback
|
|
1002
|
+
sync() {
|
|
1003
|
+
return fallbackClient.sync();
|
|
1004
|
+
},
|
|
1005
|
+
close() {
|
|
1006
|
+
_useDaemon = false;
|
|
1007
|
+
},
|
|
1008
|
+
get closed() {
|
|
1009
|
+
return fallbackClient.closed;
|
|
1010
|
+
},
|
|
1011
|
+
get protocol() {
|
|
1012
|
+
return fallbackClient.protocol;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
return {
|
|
1016
|
+
...client,
|
|
1017
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1018
|
+
_enableDaemon() {
|
|
1019
|
+
_useDaemon = true;
|
|
1020
|
+
},
|
|
1021
|
+
/** Check if daemon routing is active */
|
|
1022
|
+
_isDaemonActive() {
|
|
1023
|
+
return _useDaemon && isClientConnected();
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1028
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1029
|
+
const connected = await connectEmbedDaemon();
|
|
1030
|
+
if (!connected) {
|
|
1031
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1035
|
+
client._enableDaemon();
|
|
1036
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1037
|
+
return client;
|
|
1038
|
+
}
|
|
1039
|
+
var init_db_daemon_client = __esm({
|
|
1040
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1041
|
+
"use strict";
|
|
1042
|
+
init_exe_daemon_client();
|
|
1043
|
+
init_daemon_protocol();
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
|
|
610
1047
|
// src/lib/database.ts
|
|
611
1048
|
var database_exports = {};
|
|
612
1049
|
__export(database_exports, {
|
|
@@ -615,6 +1052,7 @@ __export(database_exports, {
|
|
|
615
1052
|
ensureSchema: () => ensureSchema,
|
|
616
1053
|
getClient: () => getClient,
|
|
617
1054
|
getRawClient: () => getRawClient,
|
|
1055
|
+
initDaemonClient: () => initDaemonClient,
|
|
618
1056
|
initDatabase: () => initDatabase,
|
|
619
1057
|
initTurso: () => initTurso,
|
|
620
1058
|
isInitialized: () => isInitialized
|
|
@@ -642,8 +1080,27 @@ function getClient() {
|
|
|
642
1080
|
if (!_resilientClient) {
|
|
643
1081
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
644
1082
|
}
|
|
1083
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1084
|
+
return _resilientClient;
|
|
1085
|
+
}
|
|
1086
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1087
|
+
return _daemonClient;
|
|
1088
|
+
}
|
|
645
1089
|
return _resilientClient;
|
|
646
1090
|
}
|
|
1091
|
+
async function initDaemonClient() {
|
|
1092
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1093
|
+
if (!_resilientClient) return;
|
|
1094
|
+
try {
|
|
1095
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1096
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
process.stderr.write(
|
|
1099
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1100
|
+
`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
647
1104
|
function getRawClient() {
|
|
648
1105
|
if (!_client) {
|
|
649
1106
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1130,6 +1587,12 @@ async function ensureSchema() {
|
|
|
1130
1587
|
} catch {
|
|
1131
1588
|
}
|
|
1132
1589
|
}
|
|
1590
|
+
try {
|
|
1591
|
+
await client.execute(
|
|
1592
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1593
|
+
);
|
|
1594
|
+
} catch {
|
|
1595
|
+
}
|
|
1133
1596
|
await client.executeMultiple(`
|
|
1134
1597
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1135
1598
|
id TEXT PRIMARY KEY,
|
|
@@ -1182,7 +1645,30 @@ async function ensureSchema() {
|
|
|
1182
1645
|
entity_id TEXT NOT NULL,
|
|
1183
1646
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1184
1647
|
);
|
|
1648
|
+
|
|
1649
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1650
|
+
name,
|
|
1651
|
+
content=entities,
|
|
1652
|
+
content_rowid=rowid
|
|
1653
|
+
);
|
|
1654
|
+
|
|
1655
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1656
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1657
|
+
END;
|
|
1658
|
+
|
|
1659
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1660
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1661
|
+
END;
|
|
1662
|
+
|
|
1663
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1664
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1665
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1666
|
+
END;
|
|
1185
1667
|
`);
|
|
1668
|
+
try {
|
|
1669
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1670
|
+
} catch {
|
|
1671
|
+
}
|
|
1186
1672
|
await client.executeMultiple(`
|
|
1187
1673
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1188
1674
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1363,6 +1849,33 @@ async function ensureSchema() {
|
|
|
1363
1849
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1364
1850
|
ON conversations(channel_id);
|
|
1365
1851
|
`);
|
|
1852
|
+
await client.executeMultiple(`
|
|
1853
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1854
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1855
|
+
agent_id TEXT NOT NULL,
|
|
1856
|
+
session_name TEXT,
|
|
1857
|
+
task_id TEXT,
|
|
1858
|
+
project_name TEXT,
|
|
1859
|
+
started_at TEXT NOT NULL
|
|
1860
|
+
);
|
|
1861
|
+
|
|
1862
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1863
|
+
ON session_agent_map(agent_id);
|
|
1864
|
+
`);
|
|
1865
|
+
try {
|
|
1866
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1867
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1868
|
+
await client.execute({
|
|
1869
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1870
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1871
|
+
FROM memories
|
|
1872
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1873
|
+
GROUP BY session_id, agent_id`,
|
|
1874
|
+
args: []
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
} catch {
|
|
1878
|
+
}
|
|
1366
1879
|
try {
|
|
1367
1880
|
await client.execute({
|
|
1368
1881
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1496,15 +2009,41 @@ async function ensureSchema() {
|
|
|
1496
2009
|
});
|
|
1497
2010
|
} catch {
|
|
1498
2011
|
}
|
|
2012
|
+
for (const col of [
|
|
2013
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2014
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2015
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2016
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2017
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2018
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2019
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2020
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2021
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2022
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2023
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2024
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2025
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2026
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2027
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2028
|
+
]) {
|
|
2029
|
+
try {
|
|
2030
|
+
await client.execute(col);
|
|
2031
|
+
} catch {
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
1499
2034
|
}
|
|
1500
2035
|
async function disposeDatabase() {
|
|
2036
|
+
if (_daemonClient) {
|
|
2037
|
+
_daemonClient.close();
|
|
2038
|
+
_daemonClient = null;
|
|
2039
|
+
}
|
|
1501
2040
|
if (_client) {
|
|
1502
2041
|
_client.close();
|
|
1503
2042
|
_client = null;
|
|
1504
2043
|
_resilientClient = null;
|
|
1505
2044
|
}
|
|
1506
2045
|
}
|
|
1507
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2046
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1508
2047
|
var init_database = __esm({
|
|
1509
2048
|
"src/lib/database.ts"() {
|
|
1510
2049
|
"use strict";
|
|
@@ -1512,24 +2051,25 @@ var init_database = __esm({
|
|
|
1512
2051
|
init_employees();
|
|
1513
2052
|
_client = null;
|
|
1514
2053
|
_resilientClient = null;
|
|
2054
|
+
_daemonClient = null;
|
|
1515
2055
|
initTurso = initDatabase;
|
|
1516
2056
|
disposeTurso = disposeDatabase;
|
|
1517
2057
|
}
|
|
1518
2058
|
});
|
|
1519
2059
|
|
|
1520
2060
|
// src/lib/license.ts
|
|
1521
|
-
import { readFileSync as
|
|
1522
|
-
import { randomUUID } from "crypto";
|
|
1523
|
-
import
|
|
2061
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2062
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2063
|
+
import path6 from "path";
|
|
1524
2064
|
import { jwtVerify, importSPKI } from "jose";
|
|
1525
2065
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1526
2066
|
var init_license = __esm({
|
|
1527
2067
|
"src/lib/license.ts"() {
|
|
1528
2068
|
"use strict";
|
|
1529
2069
|
init_config();
|
|
1530
|
-
LICENSE_PATH =
|
|
1531
|
-
CACHE_PATH =
|
|
1532
|
-
DEVICE_ID_PATH =
|
|
2070
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
2071
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
2072
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1533
2073
|
PLAN_LIMITS = {
|
|
1534
2074
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1535
2075
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1541,12 +2081,12 @@ var init_license = __esm({
|
|
|
1541
2081
|
});
|
|
1542
2082
|
|
|
1543
2083
|
// src/lib/plan-limits.ts
|
|
1544
|
-
import { readFileSync as
|
|
1545
|
-
import
|
|
2084
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
2085
|
+
import path7 from "path";
|
|
1546
2086
|
function getLicenseSync() {
|
|
1547
2087
|
try {
|
|
1548
|
-
if (!
|
|
1549
|
-
const raw = JSON.parse(
|
|
2088
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
2089
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1550
2090
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1551
2091
|
const parts = raw.token.split(".");
|
|
1552
2092
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1584,8 +2124,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1584
2124
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1585
2125
|
let count = 0;
|
|
1586
2126
|
try {
|
|
1587
|
-
if (
|
|
1588
|
-
const raw =
|
|
2127
|
+
if (existsSync7(filePath)) {
|
|
2128
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1589
2129
|
const employees = JSON.parse(raw);
|
|
1590
2130
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1591
2131
|
}
|
|
@@ -1614,19 +2154,19 @@ var init_plan_limits = __esm({
|
|
|
1614
2154
|
this.name = "PlanLimitError";
|
|
1615
2155
|
}
|
|
1616
2156
|
};
|
|
1617
|
-
CACHE_PATH2 =
|
|
2157
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1618
2158
|
}
|
|
1619
2159
|
});
|
|
1620
2160
|
|
|
1621
2161
|
// src/lib/notifications.ts
|
|
1622
2162
|
import crypto from "crypto";
|
|
1623
|
-
import
|
|
2163
|
+
import path8 from "path";
|
|
1624
2164
|
import os5 from "os";
|
|
1625
2165
|
import {
|
|
1626
|
-
readFileSync as
|
|
2166
|
+
readFileSync as readFileSync8,
|
|
1627
2167
|
readdirSync,
|
|
1628
|
-
unlinkSync as
|
|
1629
|
-
existsSync as
|
|
2168
|
+
unlinkSync as unlinkSync3,
|
|
2169
|
+
existsSync as existsSync8,
|
|
1630
2170
|
rmdirSync
|
|
1631
2171
|
} from "fs";
|
|
1632
2172
|
async function writeNotification(notification) {
|
|
@@ -1761,10 +2301,11 @@ var init_state_bus = __esm({
|
|
|
1761
2301
|
|
|
1762
2302
|
// src/lib/tasks-crud.ts
|
|
1763
2303
|
import crypto3 from "crypto";
|
|
1764
|
-
import
|
|
2304
|
+
import path9 from "path";
|
|
2305
|
+
import os6 from "os";
|
|
1765
2306
|
import { execSync as execSync4 } from "child_process";
|
|
1766
2307
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1767
|
-
import { existsSync as
|
|
2308
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1768
2309
|
async function writeCheckpoint(input) {
|
|
1769
2310
|
const client = getClient();
|
|
1770
2311
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1805,6 +2346,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1805
2346
|
function slugify(title) {
|
|
1806
2347
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1807
2348
|
}
|
|
2349
|
+
function buildKeywordIndex() {
|
|
2350
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2351
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2352
|
+
for (const kw of keywords) {
|
|
2353
|
+
const existing = idx.get(kw) ?? [];
|
|
2354
|
+
existing.push(role);
|
|
2355
|
+
idx.set(kw, existing);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
return idx;
|
|
2359
|
+
}
|
|
2360
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2361
|
+
const employees = loadEmployeesSync();
|
|
2362
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2363
|
+
if (!employee) return void 0;
|
|
2364
|
+
const assigneeRole = employee.role;
|
|
2365
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2366
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2367
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2368
|
+
if (text.includes(keyword)) {
|
|
2369
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2373
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2374
|
+
if (assigneeRole === "COO") return void 0;
|
|
2375
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2376
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2377
|
+
}
|
|
1808
2378
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1809
2379
|
const scope = sessionScopeFilter(scopeSession);
|
|
1810
2380
|
let result = await client.execute({
|
|
@@ -1854,7 +2424,14 @@ async function createTaskCore(input) {
|
|
|
1854
2424
|
const id = crypto3.randomUUID();
|
|
1855
2425
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1856
2426
|
const slug = slugify(input.title);
|
|
1857
|
-
|
|
2427
|
+
let earlySessionScope = null;
|
|
2428
|
+
try {
|
|
2429
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2430
|
+
earlySessionScope = resolveExeSession2();
|
|
2431
|
+
} catch {
|
|
2432
|
+
}
|
|
2433
|
+
const scope = earlySessionScope ?? "default";
|
|
2434
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1858
2435
|
let blockedById = null;
|
|
1859
2436
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1860
2437
|
if (input.blockedBy) {
|
|
@@ -1894,22 +2471,24 @@ async function createTaskCore(input) {
|
|
|
1894
2471
|
if (dupCheck.rows.length > 0) {
|
|
1895
2472
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1896
2473
|
}
|
|
2474
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2475
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2476
|
+
if (laneWarning) {
|
|
2477
|
+
warning = warning ? `${warning}
|
|
2478
|
+
${laneWarning}` : laneWarning;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
1897
2481
|
if (input.baseDir) {
|
|
1898
2482
|
try {
|
|
1899
|
-
await mkdir3(
|
|
1900
|
-
await mkdir3(
|
|
2483
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2484
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1901
2485
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1902
2486
|
await ensureGitignoreExe(input.baseDir);
|
|
1903
2487
|
} catch {
|
|
1904
2488
|
}
|
|
1905
2489
|
}
|
|
1906
2490
|
const complexity = input.complexity ?? "standard";
|
|
1907
|
-
|
|
1908
|
-
try {
|
|
1909
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1910
|
-
sessionScope = resolveExeSession2();
|
|
1911
|
-
} catch {
|
|
1912
|
-
}
|
|
2491
|
+
const sessionScope = earlySessionScope;
|
|
1913
2492
|
await client.execute({
|
|
1914
2493
|
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)
|
|
1915
2494
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1936,6 +2515,39 @@ async function createTaskCore(input) {
|
|
|
1936
2515
|
now
|
|
1937
2516
|
]
|
|
1938
2517
|
});
|
|
2518
|
+
if (input.baseDir) {
|
|
2519
|
+
try {
|
|
2520
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
2521
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
2522
|
+
const mdDir = path9.dirname(mdPath);
|
|
2523
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2524
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2525
|
+
const mdContent = `# ${input.title}
|
|
2526
|
+
|
|
2527
|
+
**ID:** ${id}
|
|
2528
|
+
**Status:** ${initialStatus}
|
|
2529
|
+
**Priority:** ${input.priority}
|
|
2530
|
+
**Assigned by:** ${input.assignedBy}
|
|
2531
|
+
**Assigned to:** ${input.assignedTo}
|
|
2532
|
+
**Project:** ${input.projectName}
|
|
2533
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2534
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2535
|
+
**Reviewer:** ${reviewer}
|
|
2536
|
+
|
|
2537
|
+
## Context
|
|
2538
|
+
|
|
2539
|
+
${input.context}
|
|
2540
|
+
|
|
2541
|
+
## MANDATORY: When done
|
|
2542
|
+
|
|
2543
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2544
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2545
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2546
|
+
`;
|
|
2547
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2548
|
+
} catch {
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
1939
2551
|
return {
|
|
1940
2552
|
id,
|
|
1941
2553
|
title: input.title,
|
|
@@ -2128,7 +2740,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2128
2740
|
return { row, taskFile, now, taskId };
|
|
2129
2741
|
}
|
|
2130
2742
|
}
|
|
2131
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2743
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2132
2744
|
process.stderr.write(
|
|
2133
2745
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2134
2746
|
`
|
|
@@ -2193,9 +2805,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2193
2805
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2194
2806
|
}
|
|
2195
2807
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2196
|
-
const archPath =
|
|
2808
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2197
2809
|
try {
|
|
2198
|
-
if (
|
|
2810
|
+
if (existsSync9(archPath)) return;
|
|
2199
2811
|
const template = [
|
|
2200
2812
|
`# ${projectName} \u2014 System Architecture`,
|
|
2201
2813
|
"",
|
|
@@ -2228,10 +2840,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2228
2840
|
}
|
|
2229
2841
|
}
|
|
2230
2842
|
async function ensureGitignoreExe(baseDir) {
|
|
2231
|
-
const gitignorePath =
|
|
2843
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
2232
2844
|
try {
|
|
2233
|
-
if (
|
|
2234
|
-
const content =
|
|
2845
|
+
if (existsSync9(gitignorePath)) {
|
|
2846
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2235
2847
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2236
2848
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2237
2849
|
} else {
|
|
@@ -2240,20 +2852,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2240
2852
|
} catch {
|
|
2241
2853
|
}
|
|
2242
2854
|
}
|
|
2243
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2855
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2244
2856
|
var init_tasks_crud = __esm({
|
|
2245
2857
|
"src/lib/tasks-crud.ts"() {
|
|
2246
2858
|
"use strict";
|
|
2247
2859
|
init_database();
|
|
2248
2860
|
init_task_scope();
|
|
2861
|
+
init_employees();
|
|
2862
|
+
LANE_KEYWORDS = {
|
|
2863
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2864
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2865
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2866
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2867
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2868
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2869
|
+
};
|
|
2870
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2249
2871
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2250
2872
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2251
2873
|
}
|
|
2252
2874
|
});
|
|
2253
2875
|
|
|
2254
2876
|
// src/lib/tasks-review.ts
|
|
2255
|
-
import
|
|
2256
|
-
import { existsSync as
|
|
2877
|
+
import path10 from "path";
|
|
2878
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2257
2879
|
async function countPendingReviews(sessionScope) {
|
|
2258
2880
|
const client = getClient();
|
|
2259
2881
|
if (sessionScope) {
|
|
@@ -2275,7 +2897,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2275
2897
|
const result2 = await client.execute({
|
|
2276
2898
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2277
2899
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2278
|
-
AND
|
|
2900
|
+
AND session_scope = ?`,
|
|
2279
2901
|
args: [sinceIso, sessionScope]
|
|
2280
2902
|
});
|
|
2281
2903
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2293,7 +2915,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2293
2915
|
const result2 = await client.execute({
|
|
2294
2916
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2295
2917
|
WHERE status = 'needs_review'
|
|
2296
|
-
AND
|
|
2918
|
+
AND session_scope = ?
|
|
2297
2919
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2298
2920
|
args: [sessionScope, limit]
|
|
2299
2921
|
});
|
|
@@ -2414,14 +3036,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2414
3036
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2415
3037
|
const agent = parts[1];
|
|
2416
3038
|
const slug = parts.slice(2).join("-");
|
|
2417
|
-
const
|
|
3039
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2418
3040
|
const result = await client.execute({
|
|
2419
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2420
|
-
args: [now,
|
|
3041
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3042
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2421
3043
|
});
|
|
2422
3044
|
if (result.rowsAffected > 0) {
|
|
2423
3045
|
process.stderr.write(
|
|
2424
|
-
`[review-cleanup] Cascaded original task to done
|
|
3046
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2425
3047
|
`
|
|
2426
3048
|
);
|
|
2427
3049
|
}
|
|
@@ -2434,11 +3056,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2434
3056
|
);
|
|
2435
3057
|
}
|
|
2436
3058
|
try {
|
|
2437
|
-
const cacheDir =
|
|
2438
|
-
if (
|
|
3059
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
3060
|
+
if (existsSync10(cacheDir)) {
|
|
2439
3061
|
for (const f of readdirSync2(cacheDir)) {
|
|
2440
3062
|
if (f.startsWith("review-notified-")) {
|
|
2441
|
-
|
|
3063
|
+
unlinkSync4(path10.join(cacheDir, f));
|
|
2442
3064
|
}
|
|
2443
3065
|
}
|
|
2444
3066
|
}
|
|
@@ -2459,7 +3081,7 @@ var init_tasks_review = __esm({
|
|
|
2459
3081
|
});
|
|
2460
3082
|
|
|
2461
3083
|
// src/lib/tasks-chain.ts
|
|
2462
|
-
import
|
|
3084
|
+
import path11 from "path";
|
|
2463
3085
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2464
3086
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2465
3087
|
const client = getClient();
|
|
@@ -2476,7 +3098,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2476
3098
|
});
|
|
2477
3099
|
for (const ur of unblockedRows.rows) {
|
|
2478
3100
|
try {
|
|
2479
|
-
const ubFile =
|
|
3101
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
2480
3102
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2481
3103
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2482
3104
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2550,7 +3172,7 @@ __export(project_name_exports, {
|
|
|
2550
3172
|
getProjectName: () => getProjectName
|
|
2551
3173
|
});
|
|
2552
3174
|
import { execSync as execSync5 } from "child_process";
|
|
2553
|
-
import
|
|
3175
|
+
import path12 from "path";
|
|
2554
3176
|
function getProjectName(cwd) {
|
|
2555
3177
|
const dir = cwd ?? process.cwd();
|
|
2556
3178
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2563,7 +3185,7 @@ function getProjectName(cwd) {
|
|
|
2563
3185
|
timeout: 2e3,
|
|
2564
3186
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2565
3187
|
}).trim();
|
|
2566
|
-
repoRoot =
|
|
3188
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
2567
3189
|
} catch {
|
|
2568
3190
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2569
3191
|
cwd: dir,
|
|
@@ -2572,11 +3194,11 @@ function getProjectName(cwd) {
|
|
|
2572
3194
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2573
3195
|
}).trim();
|
|
2574
3196
|
}
|
|
2575
|
-
_cached2 =
|
|
3197
|
+
_cached2 = path12.basename(repoRoot);
|
|
2576
3198
|
_cachedCwd = dir;
|
|
2577
3199
|
return _cached2;
|
|
2578
3200
|
} catch {
|
|
2579
|
-
_cached2 =
|
|
3201
|
+
_cached2 = path12.basename(dir);
|
|
2580
3202
|
_cachedCwd = dir;
|
|
2581
3203
|
return _cached2;
|
|
2582
3204
|
}
|
|
@@ -2612,7 +3234,7 @@ function findSessionForProject(projectName) {
|
|
|
2612
3234
|
const sessions = listSessions();
|
|
2613
3235
|
for (const s of sessions) {
|
|
2614
3236
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2615
|
-
if (proj === projectName &&
|
|
3237
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2616
3238
|
}
|
|
2617
3239
|
return null;
|
|
2618
3240
|
}
|
|
@@ -2658,7 +3280,7 @@ var init_session_scope = __esm({
|
|
|
2658
3280
|
|
|
2659
3281
|
// src/lib/tasks-notify.ts
|
|
2660
3282
|
async function dispatchTaskToEmployee(input) {
|
|
2661
|
-
if (
|
|
3283
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2662
3284
|
let crossProject = false;
|
|
2663
3285
|
if (input.projectName) {
|
|
2664
3286
|
try {
|
|
@@ -3053,8 +3675,8 @@ __export(tasks_exports, {
|
|
|
3053
3675
|
updateTaskStatus: () => updateTaskStatus,
|
|
3054
3676
|
writeCheckpoint: () => writeCheckpoint
|
|
3055
3677
|
});
|
|
3056
|
-
import
|
|
3057
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
3678
|
+
import path13 from "path";
|
|
3679
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3058
3680
|
async function createTask(input) {
|
|
3059
3681
|
const result = await createTaskCore(input);
|
|
3060
3682
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3073,14 +3695,14 @@ async function updateTask(input) {
|
|
|
3073
3695
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3074
3696
|
try {
|
|
3075
3697
|
const agent = String(row.assigned_to);
|
|
3076
|
-
const cacheDir =
|
|
3077
|
-
const cachePath =
|
|
3698
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3699
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
3078
3700
|
if (input.status === "in_progress") {
|
|
3079
3701
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3080
3702
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3081
3703
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3082
3704
|
try {
|
|
3083
|
-
|
|
3705
|
+
unlinkSync5(cachePath);
|
|
3084
3706
|
} catch {
|
|
3085
3707
|
}
|
|
3086
3708
|
}
|
|
@@ -3137,7 +3759,7 @@ async function updateTask(input) {
|
|
|
3137
3759
|
}
|
|
3138
3760
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3139
3761
|
if (isTerminal) {
|
|
3140
|
-
const isCoordinator =
|
|
3762
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3141
3763
|
if (!isCoordinator) {
|
|
3142
3764
|
notifyTaskDone();
|
|
3143
3765
|
}
|
|
@@ -3162,7 +3784,7 @@ async function updateTask(input) {
|
|
|
3162
3784
|
}
|
|
3163
3785
|
}
|
|
3164
3786
|
}
|
|
3165
|
-
if (input.status === "done" &&
|
|
3787
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3166
3788
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3167
3789
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3168
3790
|
taskId,
|
|
@@ -3178,7 +3800,7 @@ async function updateTask(input) {
|
|
|
3178
3800
|
});
|
|
3179
3801
|
}
|
|
3180
3802
|
let nextTask;
|
|
3181
|
-
if (isTerminal &&
|
|
3803
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3182
3804
|
try {
|
|
3183
3805
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3184
3806
|
} catch {
|
|
@@ -3522,7 +4144,7 @@ var init_capacity_monitor = __esm({
|
|
|
3522
4144
|
// src/lib/tmux-routing.ts
|
|
3523
4145
|
var tmux_routing_exports = {};
|
|
3524
4146
|
__export(tmux_routing_exports, {
|
|
3525
|
-
acquireSpawnLock: () =>
|
|
4147
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3526
4148
|
employeeSessionName: () => employeeSessionName,
|
|
3527
4149
|
ensureEmployee: () => ensureEmployee,
|
|
3528
4150
|
extractRootExe: () => extractRootExe,
|
|
@@ -3537,20 +4159,20 @@ __export(tmux_routing_exports, {
|
|
|
3537
4159
|
notifyParentExe: () => notifyParentExe,
|
|
3538
4160
|
parseParentExe: () => parseParentExe,
|
|
3539
4161
|
registerParentExe: () => registerParentExe,
|
|
3540
|
-
releaseSpawnLock: () =>
|
|
4162
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3541
4163
|
resolveExeSession: () => resolveExeSession,
|
|
3542
4164
|
sendIntercom: () => sendIntercom,
|
|
3543
4165
|
spawnEmployee: () => spawnEmployee,
|
|
3544
4166
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3545
4167
|
});
|
|
3546
4168
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3547
|
-
import { readFileSync as
|
|
3548
|
-
import
|
|
3549
|
-
import
|
|
3550
|
-
import { fileURLToPath as
|
|
3551
|
-
import { unlinkSync as
|
|
4169
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4170
|
+
import path14 from "path";
|
|
4171
|
+
import os7 from "os";
|
|
4172
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4173
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3552
4174
|
function spawnLockPath(sessionName) {
|
|
3553
|
-
return
|
|
4175
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3554
4176
|
}
|
|
3555
4177
|
function isProcessAlive(pid) {
|
|
3556
4178
|
try {
|
|
@@ -3560,14 +4182,14 @@ function isProcessAlive(pid) {
|
|
|
3560
4182
|
return false;
|
|
3561
4183
|
}
|
|
3562
4184
|
}
|
|
3563
|
-
function
|
|
3564
|
-
if (!
|
|
4185
|
+
function acquireSpawnLock2(sessionName) {
|
|
4186
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3565
4187
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3566
4188
|
}
|
|
3567
4189
|
const lockFile = spawnLockPath(sessionName);
|
|
3568
|
-
if (
|
|
4190
|
+
if (existsSync11(lockFile)) {
|
|
3569
4191
|
try {
|
|
3570
|
-
const lock = JSON.parse(
|
|
4192
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3571
4193
|
const age = Date.now() - lock.timestamp;
|
|
3572
4194
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3573
4195
|
return false;
|
|
@@ -3578,22 +4200,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3578
4200
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3579
4201
|
return true;
|
|
3580
4202
|
}
|
|
3581
|
-
function
|
|
4203
|
+
function releaseSpawnLock2(sessionName) {
|
|
3582
4204
|
try {
|
|
3583
|
-
|
|
4205
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3584
4206
|
} catch {
|
|
3585
4207
|
}
|
|
3586
4208
|
}
|
|
3587
4209
|
function resolveBehaviorsExporterScript() {
|
|
3588
4210
|
try {
|
|
3589
|
-
const thisFile =
|
|
3590
|
-
const scriptPath =
|
|
3591
|
-
|
|
4211
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
4212
|
+
const scriptPath = path14.join(
|
|
4213
|
+
path14.dirname(thisFile),
|
|
3592
4214
|
"..",
|
|
3593
4215
|
"bin",
|
|
3594
4216
|
"exe-export-behaviors.js"
|
|
3595
4217
|
);
|
|
3596
|
-
return
|
|
4218
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3597
4219
|
} catch {
|
|
3598
4220
|
return null;
|
|
3599
4221
|
}
|
|
@@ -3659,11 +4281,11 @@ function extractRootExe(name) {
|
|
|
3659
4281
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3660
4282
|
}
|
|
3661
4283
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3662
|
-
if (!
|
|
4284
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3663
4285
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3664
4286
|
}
|
|
3665
4287
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3666
|
-
const filePath =
|
|
4288
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3667
4289
|
writeFileSync6(filePath, JSON.stringify({
|
|
3668
4290
|
parentExe: rootExe,
|
|
3669
4291
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3672,7 +4294,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3672
4294
|
}
|
|
3673
4295
|
function getParentExe(sessionKey) {
|
|
3674
4296
|
try {
|
|
3675
|
-
const data = JSON.parse(
|
|
4297
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3676
4298
|
return data.parentExe || null;
|
|
3677
4299
|
} catch {
|
|
3678
4300
|
return null;
|
|
@@ -3680,8 +4302,8 @@ function getParentExe(sessionKey) {
|
|
|
3680
4302
|
}
|
|
3681
4303
|
function getDispatchedBy(sessionKey) {
|
|
3682
4304
|
try {
|
|
3683
|
-
const data = JSON.parse(
|
|
3684
|
-
|
|
4305
|
+
const data = JSON.parse(readFileSync10(
|
|
4306
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3685
4307
|
"utf8"
|
|
3686
4308
|
));
|
|
3687
4309
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3707,10 +4329,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3707
4329
|
}
|
|
3708
4330
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3709
4331
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3710
|
-
if (!isAlive(base) &&
|
|
4332
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3711
4333
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3712
4334
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3713
|
-
if (!isAlive(candidate) &&
|
|
4335
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3714
4336
|
}
|
|
3715
4337
|
return null;
|
|
3716
4338
|
}
|
|
@@ -3742,15 +4364,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3742
4364
|
}
|
|
3743
4365
|
function readDebounceState() {
|
|
3744
4366
|
try {
|
|
3745
|
-
if (!
|
|
3746
|
-
return JSON.parse(
|
|
4367
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4368
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3747
4369
|
} catch {
|
|
3748
4370
|
return {};
|
|
3749
4371
|
}
|
|
3750
4372
|
}
|
|
3751
4373
|
function writeDebounceState(state) {
|
|
3752
4374
|
try {
|
|
3753
|
-
if (!
|
|
4375
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3754
4376
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3755
4377
|
} catch {
|
|
3756
4378
|
}
|
|
@@ -3870,7 +4492,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3870
4492
|
return true;
|
|
3871
4493
|
}
|
|
3872
4494
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3873
|
-
if (
|
|
4495
|
+
if (isCoordinatorName(employeeName)) {
|
|
3874
4496
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3875
4497
|
}
|
|
3876
4498
|
try {
|
|
@@ -3942,26 +4564,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3942
4564
|
const transport = getTransport();
|
|
3943
4565
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3944
4566
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3945
|
-
const logDir =
|
|
3946
|
-
const logFile =
|
|
3947
|
-
if (!
|
|
4567
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4568
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4569
|
+
if (!existsSync11(logDir)) {
|
|
3948
4570
|
mkdirSync5(logDir, { recursive: true });
|
|
3949
4571
|
}
|
|
3950
4572
|
transport.kill(sessionName);
|
|
3951
4573
|
let cleanupSuffix = "";
|
|
3952
4574
|
try {
|
|
3953
|
-
const thisFile =
|
|
3954
|
-
const cleanupScript =
|
|
3955
|
-
if (
|
|
4575
|
+
const thisFile = fileURLToPath3(import.meta.url);
|
|
4576
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4577
|
+
if (existsSync11(cleanupScript)) {
|
|
3956
4578
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3957
4579
|
}
|
|
3958
4580
|
} catch {
|
|
3959
4581
|
}
|
|
3960
4582
|
try {
|
|
3961
|
-
const claudeJsonPath =
|
|
4583
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3962
4584
|
let claudeJson = {};
|
|
3963
4585
|
try {
|
|
3964
|
-
claudeJson = JSON.parse(
|
|
4586
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3965
4587
|
} catch {
|
|
3966
4588
|
}
|
|
3967
4589
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3973,13 +4595,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3973
4595
|
} catch {
|
|
3974
4596
|
}
|
|
3975
4597
|
try {
|
|
3976
|
-
const settingsDir =
|
|
4598
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3977
4599
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3978
|
-
const projSettingsDir =
|
|
3979
|
-
const settingsPath =
|
|
4600
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
4601
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3980
4602
|
let settings = {};
|
|
3981
4603
|
try {
|
|
3982
|
-
settings = JSON.parse(
|
|
4604
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3983
4605
|
} catch {
|
|
3984
4606
|
}
|
|
3985
4607
|
const perms = settings.permissions ?? {};
|
|
@@ -4020,8 +4642,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4020
4642
|
let behaviorsFlag = "";
|
|
4021
4643
|
let legacyFallbackWarned = false;
|
|
4022
4644
|
if (!useExeAgent && !useBinSymlink) {
|
|
4023
|
-
const identityPath =
|
|
4024
|
-
|
|
4645
|
+
const identityPath = path14.join(
|
|
4646
|
+
os7.homedir(),
|
|
4025
4647
|
".exe-os",
|
|
4026
4648
|
"identity",
|
|
4027
4649
|
`${employeeName}.md`
|
|
@@ -4030,13 +4652,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4030
4652
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4031
4653
|
if (hasAgentFlag) {
|
|
4032
4654
|
identityFlag = ` --agent ${employeeName}`;
|
|
4033
|
-
} else if (
|
|
4655
|
+
} else if (existsSync11(identityPath)) {
|
|
4034
4656
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4035
4657
|
legacyFallbackWarned = true;
|
|
4036
4658
|
}
|
|
4037
4659
|
const behaviorsFile = exportBehaviorsSync(
|
|
4038
4660
|
employeeName,
|
|
4039
|
-
|
|
4661
|
+
path14.basename(spawnCwd),
|
|
4040
4662
|
sessionName
|
|
4041
4663
|
);
|
|
4042
4664
|
if (behaviorsFile) {
|
|
@@ -4051,9 +4673,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4051
4673
|
}
|
|
4052
4674
|
let sessionContextFlag = "";
|
|
4053
4675
|
try {
|
|
4054
|
-
const ctxDir =
|
|
4676
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4055
4677
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4056
|
-
const ctxFile =
|
|
4678
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4057
4679
|
const ctxContent = [
|
|
4058
4680
|
`## Session Context`,
|
|
4059
4681
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4092,13 +4714,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4092
4714
|
command: spawnCommand
|
|
4093
4715
|
});
|
|
4094
4716
|
if (spawnResult.error) {
|
|
4095
|
-
|
|
4717
|
+
releaseSpawnLock2(sessionName);
|
|
4096
4718
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4097
4719
|
}
|
|
4098
4720
|
transport.pipeLog(sessionName, logFile);
|
|
4099
4721
|
try {
|
|
4100
4722
|
const mySession = getMySession();
|
|
4101
|
-
const dispatchInfo =
|
|
4723
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4102
4724
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4103
4725
|
dispatchedBy: mySession,
|
|
4104
4726
|
rootExe: exeSession,
|
|
@@ -4130,7 +4752,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4130
4752
|
}
|
|
4131
4753
|
}
|
|
4132
4754
|
if (!booted) {
|
|
4133
|
-
|
|
4755
|
+
releaseSpawnLock2(sessionName);
|
|
4134
4756
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4135
4757
|
}
|
|
4136
4758
|
if (!useExeAgent) {
|
|
@@ -4147,7 +4769,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4147
4769
|
pid: 0,
|
|
4148
4770
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4149
4771
|
});
|
|
4150
|
-
|
|
4772
|
+
releaseSpawnLock2(sessionName);
|
|
4151
4773
|
return { sessionName };
|
|
4152
4774
|
}
|
|
4153
4775
|
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;
|
|
@@ -4163,14 +4785,14 @@ var init_tmux_routing = __esm({
|
|
|
4163
4785
|
init_intercom_queue();
|
|
4164
4786
|
init_plan_limits();
|
|
4165
4787
|
init_employees();
|
|
4166
|
-
SPAWN_LOCK_DIR =
|
|
4167
|
-
SESSION_CACHE =
|
|
4788
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4789
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4168
4790
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4169
4791
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4170
4792
|
VERIFY_PANE_LINES = 200;
|
|
4171
4793
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4172
|
-
INTERCOM_LOG2 =
|
|
4173
|
-
DEBOUNCE_FILE =
|
|
4794
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4795
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4174
4796
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4175
4797
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4176
4798
|
}
|
|
@@ -4211,14 +4833,14 @@ var init_memory = __esm({
|
|
|
4211
4833
|
|
|
4212
4834
|
// src/lib/keychain.ts
|
|
4213
4835
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4214
|
-
import { existsSync as
|
|
4215
|
-
import
|
|
4216
|
-
import
|
|
4836
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4837
|
+
import path15 from "path";
|
|
4838
|
+
import os8 from "os";
|
|
4217
4839
|
function getKeyDir() {
|
|
4218
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4840
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
|
|
4219
4841
|
}
|
|
4220
4842
|
function getKeyPath() {
|
|
4221
|
-
return
|
|
4843
|
+
return path15.join(getKeyDir(), "master.key");
|
|
4222
4844
|
}
|
|
4223
4845
|
async function tryKeytar() {
|
|
4224
4846
|
try {
|
|
@@ -4239,13 +4861,21 @@ async function getMasterKey() {
|
|
|
4239
4861
|
}
|
|
4240
4862
|
}
|
|
4241
4863
|
const keyPath = getKeyPath();
|
|
4242
|
-
if (!
|
|
4864
|
+
if (!existsSync12(keyPath)) {
|
|
4865
|
+
process.stderr.write(
|
|
4866
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4867
|
+
`
|
|
4868
|
+
);
|
|
4243
4869
|
return null;
|
|
4244
4870
|
}
|
|
4245
4871
|
try {
|
|
4246
4872
|
const content = await readFile4(keyPath, "utf-8");
|
|
4247
4873
|
return Buffer.from(content.trim(), "base64");
|
|
4248
|
-
} catch {
|
|
4874
|
+
} catch (err) {
|
|
4875
|
+
process.stderr.write(
|
|
4876
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4877
|
+
`
|
|
4878
|
+
);
|
|
4249
4879
|
return null;
|
|
4250
4880
|
}
|
|
4251
4881
|
}
|
|
@@ -4271,12 +4901,12 @@ __export(shard_manager_exports, {
|
|
|
4271
4901
|
listShards: () => listShards,
|
|
4272
4902
|
shardExists: () => shardExists
|
|
4273
4903
|
});
|
|
4274
|
-
import
|
|
4275
|
-
import { existsSync as
|
|
4904
|
+
import path16 from "path";
|
|
4905
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4276
4906
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4277
4907
|
function initShardManager(encryptionKey) {
|
|
4278
4908
|
_encryptionKey = encryptionKey;
|
|
4279
|
-
if (!
|
|
4909
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4280
4910
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
4281
4911
|
}
|
|
4282
4912
|
_shardingEnabled = true;
|
|
@@ -4297,7 +4927,7 @@ function getShardClient(projectName) {
|
|
|
4297
4927
|
}
|
|
4298
4928
|
const cached = _shards.get(safeName);
|
|
4299
4929
|
if (cached) return cached;
|
|
4300
|
-
const dbPath =
|
|
4930
|
+
const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
|
|
4301
4931
|
const client = createClient2({
|
|
4302
4932
|
url: `file:${dbPath}`,
|
|
4303
4933
|
encryptionKey: _encryptionKey
|
|
@@ -4307,10 +4937,10 @@ function getShardClient(projectName) {
|
|
|
4307
4937
|
}
|
|
4308
4938
|
function shardExists(projectName) {
|
|
4309
4939
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4310
|
-
return
|
|
4940
|
+
return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
|
|
4311
4941
|
}
|
|
4312
4942
|
function listShards() {
|
|
4313
|
-
if (!
|
|
4943
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4314
4944
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4315
4945
|
}
|
|
4316
4946
|
async function ensureShardSchema(client) {
|
|
@@ -4496,7 +5126,7 @@ var init_shard_manager = __esm({
|
|
|
4496
5126
|
"src/lib/shard-manager.ts"() {
|
|
4497
5127
|
"use strict";
|
|
4498
5128
|
init_config();
|
|
4499
|
-
SHARDS_DIR =
|
|
5129
|
+
SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
|
|
4500
5130
|
_shards = /* @__PURE__ */ new Map();
|
|
4501
5131
|
_encryptionKey = null;
|
|
4502
5132
|
_shardingEnabled = false;
|
|
@@ -4621,7 +5251,7 @@ __export(global_procedures_exports, {
|
|
|
4621
5251
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4622
5252
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4623
5253
|
});
|
|
4624
|
-
import { randomUUID as
|
|
5254
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4625
5255
|
async function loadGlobalProcedures() {
|
|
4626
5256
|
const client = getClient();
|
|
4627
5257
|
const result = await client.execute({
|
|
@@ -4650,7 +5280,7 @@ ${sections.join("\n\n")}
|
|
|
4650
5280
|
`;
|
|
4651
5281
|
}
|
|
4652
5282
|
async function storeGlobalProcedure(input) {
|
|
4653
|
-
const id =
|
|
5283
|
+
const id = randomUUID3();
|
|
4654
5284
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4655
5285
|
const client = getClient();
|
|
4656
5286
|
await client.execute({
|
|
@@ -4701,6 +5331,7 @@ __export(store_exports, {
|
|
|
4701
5331
|
vectorToBlob: () => vectorToBlob,
|
|
4702
5332
|
writeMemory: () => writeMemory
|
|
4703
5333
|
});
|
|
5334
|
+
import { createHash } from "crypto";
|
|
4704
5335
|
function isBusyError2(err) {
|
|
4705
5336
|
if (err instanceof Error) {
|
|
4706
5337
|
const msg = err.message.toLowerCase();
|
|
@@ -4774,12 +5405,52 @@ function classifyTier(record) {
|
|
|
4774
5405
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4775
5406
|
return 3;
|
|
4776
5407
|
}
|
|
5408
|
+
function inferFilePaths(record) {
|
|
5409
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5410
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5411
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5412
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5413
|
+
}
|
|
5414
|
+
function inferCommitHash(record) {
|
|
5415
|
+
if (record.tool_name !== "Bash") return null;
|
|
5416
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5417
|
+
return match ? match[1] : null;
|
|
5418
|
+
}
|
|
5419
|
+
function inferLanguageType(record) {
|
|
5420
|
+
const text = record.raw_text;
|
|
5421
|
+
if (!text || text.length < 10) return null;
|
|
5422
|
+
const trimmed = text.trimStart();
|
|
5423
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5424
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5425
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5426
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5427
|
+
return "mixed";
|
|
5428
|
+
}
|
|
5429
|
+
function inferDomain(record) {
|
|
5430
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5431
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5432
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5433
|
+
return null;
|
|
5434
|
+
}
|
|
4777
5435
|
async function writeMemory(record) {
|
|
4778
5436
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4779
5437
|
throw new Error(
|
|
4780
5438
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4781
5439
|
);
|
|
4782
5440
|
}
|
|
5441
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5442
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5443
|
+
return;
|
|
5444
|
+
}
|
|
5445
|
+
try {
|
|
5446
|
+
const client = getClient();
|
|
5447
|
+
const existing = await client.execute({
|
|
5448
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5449
|
+
args: [contentHash, record.agent_id]
|
|
5450
|
+
});
|
|
5451
|
+
if (existing.rows.length > 0) return;
|
|
5452
|
+
} catch {
|
|
5453
|
+
}
|
|
4783
5454
|
const dbRow = {
|
|
4784
5455
|
id: record.id,
|
|
4785
5456
|
agent_id: record.agent_id,
|
|
@@ -4809,7 +5480,23 @@ async function writeMemory(record) {
|
|
|
4809
5480
|
supersedes_id: record.supersedes_id ?? null,
|
|
4810
5481
|
draft: record.draft ? 1 : 0,
|
|
4811
5482
|
memory_type: record.memory_type ?? "raw",
|
|
4812
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5483
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5484
|
+
content_hash: contentHash,
|
|
5485
|
+
intent: record.intent ?? null,
|
|
5486
|
+
outcome: record.outcome ?? null,
|
|
5487
|
+
domain: record.domain ?? inferDomain(record),
|
|
5488
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5489
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5490
|
+
chain_position: record.chain_position ?? null,
|
|
5491
|
+
review_status: record.review_status ?? null,
|
|
5492
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5493
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5494
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5495
|
+
duration_ms: record.duration_ms ?? null,
|
|
5496
|
+
token_cost: record.token_cost ?? null,
|
|
5497
|
+
audience: record.audience ?? null,
|
|
5498
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5499
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4813
5500
|
};
|
|
4814
5501
|
_pendingRecords.push(dbRow);
|
|
4815
5502
|
orgBus.emit({
|
|
@@ -4867,80 +5554,85 @@ async function flushBatch() {
|
|
|
4867
5554
|
const draft = row.draft ? 1 : 0;
|
|
4868
5555
|
const memoryType = row.memory_type ?? "raw";
|
|
4869
5556
|
const trajectory = row.trajectory ?? null;
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
5557
|
+
const contentHash = row.content_hash ?? null;
|
|
5558
|
+
const intent = row.intent ?? null;
|
|
5559
|
+
const outcome = row.outcome ?? null;
|
|
5560
|
+
const domain = row.domain ?? null;
|
|
5561
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5562
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5563
|
+
const chainPosition = row.chain_position ?? null;
|
|
5564
|
+
const reviewStatus = row.review_status ?? null;
|
|
5565
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5566
|
+
const filePaths = row.file_paths ?? null;
|
|
5567
|
+
const commitHash = row.commit_hash ?? null;
|
|
5568
|
+
const durationMs = row.duration_ms ?? null;
|
|
5569
|
+
const tokenCost = row.token_cost ?? null;
|
|
5570
|
+
const audience = row.audience ?? null;
|
|
5571
|
+
const languageType = row.language_type ?? null;
|
|
5572
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5573
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4873
5574
|
tool_name, project_name,
|
|
4874
5575
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4875
5576
|
confidence, last_accessed,
|
|
4876
5577
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4877
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
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
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
pageNumber,
|
|
4936
|
-
sourcePath,
|
|
4937
|
-
sourceType,
|
|
4938
|
-
tier,
|
|
4939
|
-
supersedesId,
|
|
4940
|
-
draft,
|
|
4941
|
-
memoryType,
|
|
4942
|
-
trajectory
|
|
4943
|
-
]
|
|
5578
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5579
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5580
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5581
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5582
|
+
const metaArgs = [
|
|
5583
|
+
intent,
|
|
5584
|
+
outcome,
|
|
5585
|
+
domain,
|
|
5586
|
+
referencedEntities,
|
|
5587
|
+
retrievalCount,
|
|
5588
|
+
chainPosition,
|
|
5589
|
+
reviewStatus,
|
|
5590
|
+
contextWindowPct,
|
|
5591
|
+
filePaths,
|
|
5592
|
+
commitHash,
|
|
5593
|
+
durationMs,
|
|
5594
|
+
tokenCost,
|
|
5595
|
+
audience,
|
|
5596
|
+
languageType,
|
|
5597
|
+
parentMemoryId
|
|
5598
|
+
];
|
|
5599
|
+
const baseArgs = [
|
|
5600
|
+
row.id,
|
|
5601
|
+
row.agent_id,
|
|
5602
|
+
row.agent_role,
|
|
5603
|
+
row.session_id,
|
|
5604
|
+
row.timestamp,
|
|
5605
|
+
row.tool_name,
|
|
5606
|
+
row.project_name,
|
|
5607
|
+
row.has_error,
|
|
5608
|
+
row.raw_text
|
|
5609
|
+
];
|
|
5610
|
+
const sharedArgs = [
|
|
5611
|
+
row.version,
|
|
5612
|
+
taskId,
|
|
5613
|
+
importance,
|
|
5614
|
+
status,
|
|
5615
|
+
confidence,
|
|
5616
|
+
lastAccessed,
|
|
5617
|
+
workspaceId,
|
|
5618
|
+
documentId,
|
|
5619
|
+
userId,
|
|
5620
|
+
charOffset,
|
|
5621
|
+
pageNumber,
|
|
5622
|
+
sourcePath,
|
|
5623
|
+
sourceType,
|
|
5624
|
+
tier,
|
|
5625
|
+
supersedesId,
|
|
5626
|
+
draft,
|
|
5627
|
+
memoryType,
|
|
5628
|
+
trajectory,
|
|
5629
|
+
contentHash
|
|
5630
|
+
];
|
|
5631
|
+
return {
|
|
5632
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5633
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5634
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5635
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4944
5636
|
};
|
|
4945
5637
|
};
|
|
4946
5638
|
const globalClient = getClient();
|
|
@@ -5191,15 +5883,16 @@ var init_store = __esm({
|
|
|
5191
5883
|
});
|
|
5192
5884
|
|
|
5193
5885
|
// src/bin/scan-tasks.ts
|
|
5194
|
-
import { existsSync as
|
|
5195
|
-
import
|
|
5196
|
-
import
|
|
5886
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
5887
|
+
import path17 from "path";
|
|
5888
|
+
import os9 from "os";
|
|
5197
5889
|
|
|
5198
5890
|
// src/lib/is-main.ts
|
|
5199
5891
|
import { realpathSync } from "fs";
|
|
5200
5892
|
import { fileURLToPath } from "url";
|
|
5201
5893
|
function isMainModule(importMetaUrl) {
|
|
5202
5894
|
if (process.argv[1] == null) return false;
|
|
5895
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
5203
5896
|
try {
|
|
5204
5897
|
const scriptPath = realpathSync(process.argv[1]);
|
|
5205
5898
|
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
|
@@ -5213,14 +5906,14 @@ function isMainModule(importMetaUrl) {
|
|
|
5213
5906
|
init_task_scope();
|
|
5214
5907
|
function checkMcpHealth() {
|
|
5215
5908
|
try {
|
|
5216
|
-
const claudeJson =
|
|
5217
|
-
if (!
|
|
5909
|
+
const claudeJson = path17.join(os9.homedir(), ".claude.json");
|
|
5910
|
+
if (!existsSync14(claudeJson)) {
|
|
5218
5911
|
process.stderr.write(
|
|
5219
5912
|
"\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n"
|
|
5220
5913
|
);
|
|
5221
5914
|
return;
|
|
5222
5915
|
}
|
|
5223
|
-
const config = JSON.parse(
|
|
5916
|
+
const config = JSON.parse(readFileSync11(claudeJson, "utf8"));
|
|
5224
5917
|
const servers = config.mcpServers;
|
|
5225
5918
|
if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
|
|
5226
5919
|
process.stderr.write(
|