@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/git-sweep.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 } 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(fileURLToPath(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 args2 = [];
|
|
924
|
+
if (Array.isArray(stmt.args)) {
|
|
925
|
+
args2 = 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: args2 };
|
|
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: args2 } = normalizeStatement(stmt);
|
|
943
|
+
const response = await sendDaemonRequest({
|
|
944
|
+
type: "db-execute",
|
|
945
|
+
sql,
|
|
946
|
+
args: args2
|
|
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) {
|
|
@@ -1777,10 +2317,11 @@ __export(tasks_crud_exports, {
|
|
|
1777
2317
|
writeCheckpoint: () => writeCheckpoint
|
|
1778
2318
|
});
|
|
1779
2319
|
import crypto3 from "crypto";
|
|
1780
|
-
import
|
|
2320
|
+
import path9 from "path";
|
|
2321
|
+
import os6 from "os";
|
|
1781
2322
|
import { execSync as execSync4 } from "child_process";
|
|
1782
2323
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1783
|
-
import { existsSync as
|
|
2324
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1784
2325
|
async function writeCheckpoint(input) {
|
|
1785
2326
|
const client = getClient();
|
|
1786
2327
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1821,6 +2362,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1821
2362
|
function slugify(title) {
|
|
1822
2363
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1823
2364
|
}
|
|
2365
|
+
function buildKeywordIndex() {
|
|
2366
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2367
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2368
|
+
for (const kw of keywords) {
|
|
2369
|
+
const existing = idx.get(kw) ?? [];
|
|
2370
|
+
existing.push(role);
|
|
2371
|
+
idx.set(kw, existing);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
return idx;
|
|
2375
|
+
}
|
|
2376
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2377
|
+
const employees = loadEmployeesSync();
|
|
2378
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2379
|
+
if (!employee) return void 0;
|
|
2380
|
+
const assigneeRole = employee.role;
|
|
2381
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2382
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2383
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2384
|
+
if (text.includes(keyword)) {
|
|
2385
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2389
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2390
|
+
if (assigneeRole === "COO") return void 0;
|
|
2391
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2392
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2393
|
+
}
|
|
1824
2394
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1825
2395
|
const scope = sessionScopeFilter(scopeSession);
|
|
1826
2396
|
let result = await client.execute({
|
|
@@ -1870,7 +2440,14 @@ async function createTaskCore(input) {
|
|
|
1870
2440
|
const id = crypto3.randomUUID();
|
|
1871
2441
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1872
2442
|
const slug = slugify(input.title);
|
|
1873
|
-
|
|
2443
|
+
let earlySessionScope = null;
|
|
2444
|
+
try {
|
|
2445
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2446
|
+
earlySessionScope = resolveExeSession2();
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
const scope = earlySessionScope ?? "default";
|
|
2450
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1874
2451
|
let blockedById = null;
|
|
1875
2452
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1876
2453
|
if (input.blockedBy) {
|
|
@@ -1910,22 +2487,24 @@ async function createTaskCore(input) {
|
|
|
1910
2487
|
if (dupCheck.rows.length > 0) {
|
|
1911
2488
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1912
2489
|
}
|
|
2490
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2491
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2492
|
+
if (laneWarning) {
|
|
2493
|
+
warning = warning ? `${warning}
|
|
2494
|
+
${laneWarning}` : laneWarning;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
1913
2497
|
if (input.baseDir) {
|
|
1914
2498
|
try {
|
|
1915
|
-
await mkdir3(
|
|
1916
|
-
await mkdir3(
|
|
2499
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2500
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1917
2501
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1918
2502
|
await ensureGitignoreExe(input.baseDir);
|
|
1919
2503
|
} catch {
|
|
1920
2504
|
}
|
|
1921
2505
|
}
|
|
1922
2506
|
const complexity = input.complexity ?? "standard";
|
|
1923
|
-
|
|
1924
|
-
try {
|
|
1925
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1926
|
-
sessionScope = resolveExeSession2();
|
|
1927
|
-
} catch {
|
|
1928
|
-
}
|
|
2507
|
+
const sessionScope = earlySessionScope;
|
|
1929
2508
|
await client.execute({
|
|
1930
2509
|
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)
|
|
1931
2510
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1952,6 +2531,39 @@ async function createTaskCore(input) {
|
|
|
1952
2531
|
now
|
|
1953
2532
|
]
|
|
1954
2533
|
});
|
|
2534
|
+
if (input.baseDir) {
|
|
2535
|
+
try {
|
|
2536
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
2537
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
2538
|
+
const mdDir = path9.dirname(mdPath);
|
|
2539
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2540
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2541
|
+
const mdContent = `# ${input.title}
|
|
2542
|
+
|
|
2543
|
+
**ID:** ${id}
|
|
2544
|
+
**Status:** ${initialStatus}
|
|
2545
|
+
**Priority:** ${input.priority}
|
|
2546
|
+
**Assigned by:** ${input.assignedBy}
|
|
2547
|
+
**Assigned to:** ${input.assignedTo}
|
|
2548
|
+
**Project:** ${input.projectName}
|
|
2549
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2550
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2551
|
+
**Reviewer:** ${reviewer}
|
|
2552
|
+
|
|
2553
|
+
## Context
|
|
2554
|
+
|
|
2555
|
+
${input.context}
|
|
2556
|
+
|
|
2557
|
+
## MANDATORY: When done
|
|
2558
|
+
|
|
2559
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2560
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2561
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2562
|
+
`;
|
|
2563
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2564
|
+
} catch {
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
1955
2567
|
return {
|
|
1956
2568
|
id,
|
|
1957
2569
|
title: input.title,
|
|
@@ -2144,7 +2756,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2144
2756
|
return { row, taskFile, now, taskId };
|
|
2145
2757
|
}
|
|
2146
2758
|
}
|
|
2147
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2759
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2148
2760
|
process.stderr.write(
|
|
2149
2761
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2150
2762
|
`
|
|
@@ -2209,9 +2821,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2209
2821
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2210
2822
|
}
|
|
2211
2823
|
async function ensureArchitectureDoc(baseDir, projectName2) {
|
|
2212
|
-
const archPath =
|
|
2824
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2213
2825
|
try {
|
|
2214
|
-
if (
|
|
2826
|
+
if (existsSync9(archPath)) return;
|
|
2215
2827
|
const template = [
|
|
2216
2828
|
`# ${projectName2} \u2014 System Architecture`,
|
|
2217
2829
|
"",
|
|
@@ -2244,10 +2856,10 @@ async function ensureArchitectureDoc(baseDir, projectName2) {
|
|
|
2244
2856
|
}
|
|
2245
2857
|
}
|
|
2246
2858
|
async function ensureGitignoreExe(baseDir) {
|
|
2247
|
-
const gitignorePath =
|
|
2859
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
2248
2860
|
try {
|
|
2249
|
-
if (
|
|
2250
|
-
const content =
|
|
2861
|
+
if (existsSync9(gitignorePath)) {
|
|
2862
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2251
2863
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2252
2864
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2253
2865
|
} else {
|
|
@@ -2256,20 +2868,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2256
2868
|
} catch {
|
|
2257
2869
|
}
|
|
2258
2870
|
}
|
|
2259
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2871
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2260
2872
|
var init_tasks_crud = __esm({
|
|
2261
2873
|
"src/lib/tasks-crud.ts"() {
|
|
2262
2874
|
"use strict";
|
|
2263
2875
|
init_database();
|
|
2264
2876
|
init_task_scope();
|
|
2877
|
+
init_employees();
|
|
2878
|
+
LANE_KEYWORDS = {
|
|
2879
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2880
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2881
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2882
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2883
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2884
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2885
|
+
};
|
|
2886
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2265
2887
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2266
2888
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2267
2889
|
}
|
|
2268
2890
|
});
|
|
2269
2891
|
|
|
2270
2892
|
// src/lib/tasks-review.ts
|
|
2271
|
-
import
|
|
2272
|
-
import { existsSync as
|
|
2893
|
+
import path10 from "path";
|
|
2894
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2273
2895
|
async function countPendingReviews(sessionScope) {
|
|
2274
2896
|
const client = getClient();
|
|
2275
2897
|
if (sessionScope) {
|
|
@@ -2291,7 +2913,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2291
2913
|
const result2 = await client.execute({
|
|
2292
2914
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2293
2915
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2294
|
-
AND
|
|
2916
|
+
AND session_scope = ?`,
|
|
2295
2917
|
args: [sinceIso, sessionScope]
|
|
2296
2918
|
});
|
|
2297
2919
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2309,7 +2931,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2309
2931
|
const result2 = await client.execute({
|
|
2310
2932
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2311
2933
|
WHERE status = 'needs_review'
|
|
2312
|
-
AND
|
|
2934
|
+
AND session_scope = ?
|
|
2313
2935
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2314
2936
|
args: [sessionScope, limit]
|
|
2315
2937
|
});
|
|
@@ -2430,14 +3052,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2430
3052
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2431
3053
|
const agent = parts[1];
|
|
2432
3054
|
const slug = parts.slice(2).join("-");
|
|
2433
|
-
const
|
|
3055
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2434
3056
|
const result = await client.execute({
|
|
2435
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2436
|
-
args: [now,
|
|
3057
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3058
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2437
3059
|
});
|
|
2438
3060
|
if (result.rowsAffected > 0) {
|
|
2439
3061
|
process.stderr.write(
|
|
2440
|
-
`[review-cleanup] Cascaded original task to done
|
|
3062
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2441
3063
|
`
|
|
2442
3064
|
);
|
|
2443
3065
|
}
|
|
@@ -2450,11 +3072,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2450
3072
|
);
|
|
2451
3073
|
}
|
|
2452
3074
|
try {
|
|
2453
|
-
const cacheDir =
|
|
2454
|
-
if (
|
|
3075
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
3076
|
+
if (existsSync10(cacheDir)) {
|
|
2455
3077
|
for (const f of readdirSync2(cacheDir)) {
|
|
2456
3078
|
if (f.startsWith("review-notified-")) {
|
|
2457
|
-
|
|
3079
|
+
unlinkSync4(path10.join(cacheDir, f));
|
|
2458
3080
|
}
|
|
2459
3081
|
}
|
|
2460
3082
|
}
|
|
@@ -2475,7 +3097,7 @@ var init_tasks_review = __esm({
|
|
|
2475
3097
|
});
|
|
2476
3098
|
|
|
2477
3099
|
// src/lib/tasks-chain.ts
|
|
2478
|
-
import
|
|
3100
|
+
import path11 from "path";
|
|
2479
3101
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2480
3102
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2481
3103
|
const client = getClient();
|
|
@@ -2492,7 +3114,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2492
3114
|
});
|
|
2493
3115
|
for (const ur of unblockedRows.rows) {
|
|
2494
3116
|
try {
|
|
2495
|
-
const ubFile =
|
|
3117
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
2496
3118
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2497
3119
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2498
3120
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2561,7 +3183,7 @@ var init_tasks_chain = __esm({
|
|
|
2561
3183
|
|
|
2562
3184
|
// src/lib/project-name.ts
|
|
2563
3185
|
import { execSync as execSync5 } from "child_process";
|
|
2564
|
-
import
|
|
3186
|
+
import path12 from "path";
|
|
2565
3187
|
function getProjectName(cwd) {
|
|
2566
3188
|
const dir = cwd ?? process.cwd();
|
|
2567
3189
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2574,7 +3196,7 @@ function getProjectName(cwd) {
|
|
|
2574
3196
|
timeout: 2e3,
|
|
2575
3197
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2576
3198
|
}).trim();
|
|
2577
|
-
repoRoot =
|
|
3199
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
2578
3200
|
} catch {
|
|
2579
3201
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
2580
3202
|
cwd: dir,
|
|
@@ -2583,11 +3205,11 @@ function getProjectName(cwd) {
|
|
|
2583
3205
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2584
3206
|
}).trim();
|
|
2585
3207
|
}
|
|
2586
|
-
_cached2 =
|
|
3208
|
+
_cached2 = path12.basename(repoRoot);
|
|
2587
3209
|
_cachedCwd = dir;
|
|
2588
3210
|
return _cached2;
|
|
2589
3211
|
} catch {
|
|
2590
|
-
_cached2 =
|
|
3212
|
+
_cached2 = path12.basename(dir);
|
|
2591
3213
|
_cachedCwd = dir;
|
|
2592
3214
|
return _cached2;
|
|
2593
3215
|
}
|
|
@@ -2619,7 +3241,7 @@ function findSessionForProject(projectName2) {
|
|
|
2619
3241
|
const sessions = listSessions();
|
|
2620
3242
|
for (const s of sessions) {
|
|
2621
3243
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2622
|
-
if (proj === projectName2 &&
|
|
3244
|
+
if (proj === projectName2 && isCoordinatorName(s.agentId)) return s;
|
|
2623
3245
|
}
|
|
2624
3246
|
return null;
|
|
2625
3247
|
}
|
|
@@ -2665,7 +3287,7 @@ var init_session_scope = __esm({
|
|
|
2665
3287
|
|
|
2666
3288
|
// src/lib/tasks-notify.ts
|
|
2667
3289
|
async function dispatchTaskToEmployee(input) {
|
|
2668
|
-
if (
|
|
3290
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2669
3291
|
let crossProject = false;
|
|
2670
3292
|
if (input.projectName) {
|
|
2671
3293
|
try {
|
|
@@ -3060,8 +3682,8 @@ __export(tasks_exports, {
|
|
|
3060
3682
|
updateTaskStatus: () => updateTaskStatus,
|
|
3061
3683
|
writeCheckpoint: () => writeCheckpoint
|
|
3062
3684
|
});
|
|
3063
|
-
import
|
|
3064
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
3685
|
+
import path13 from "path";
|
|
3686
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3065
3687
|
async function createTask(input) {
|
|
3066
3688
|
const result = await createTaskCore(input);
|
|
3067
3689
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3080,14 +3702,14 @@ async function updateTask(input) {
|
|
|
3080
3702
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3081
3703
|
try {
|
|
3082
3704
|
const agent = String(row.assigned_to);
|
|
3083
|
-
const cacheDir =
|
|
3084
|
-
const cachePath =
|
|
3705
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
3706
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
3085
3707
|
if (input.status === "in_progress") {
|
|
3086
3708
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3087
3709
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3088
3710
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3089
3711
|
try {
|
|
3090
|
-
|
|
3712
|
+
unlinkSync5(cachePath);
|
|
3091
3713
|
} catch {
|
|
3092
3714
|
}
|
|
3093
3715
|
}
|
|
@@ -3144,7 +3766,7 @@ async function updateTask(input) {
|
|
|
3144
3766
|
}
|
|
3145
3767
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3146
3768
|
if (isTerminal) {
|
|
3147
|
-
const isCoordinator =
|
|
3769
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3148
3770
|
if (!isCoordinator) {
|
|
3149
3771
|
notifyTaskDone();
|
|
3150
3772
|
}
|
|
@@ -3169,7 +3791,7 @@ async function updateTask(input) {
|
|
|
3169
3791
|
}
|
|
3170
3792
|
}
|
|
3171
3793
|
}
|
|
3172
|
-
if (input.status === "done" &&
|
|
3794
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3173
3795
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3174
3796
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3175
3797
|
taskId,
|
|
@@ -3185,7 +3807,7 @@ async function updateTask(input) {
|
|
|
3185
3807
|
});
|
|
3186
3808
|
}
|
|
3187
3809
|
let nextTask;
|
|
3188
|
-
if (isTerminal &&
|
|
3810
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3189
3811
|
try {
|
|
3190
3812
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3191
3813
|
} catch {
|
|
@@ -3529,7 +4151,7 @@ var init_capacity_monitor = __esm({
|
|
|
3529
4151
|
// src/lib/tmux-routing.ts
|
|
3530
4152
|
var tmux_routing_exports = {};
|
|
3531
4153
|
__export(tmux_routing_exports, {
|
|
3532
|
-
acquireSpawnLock: () =>
|
|
4154
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3533
4155
|
employeeSessionName: () => employeeSessionName,
|
|
3534
4156
|
ensureEmployee: () => ensureEmployee,
|
|
3535
4157
|
extractRootExe: () => extractRootExe,
|
|
@@ -3544,20 +4166,20 @@ __export(tmux_routing_exports, {
|
|
|
3544
4166
|
notifyParentExe: () => notifyParentExe,
|
|
3545
4167
|
parseParentExe: () => parseParentExe,
|
|
3546
4168
|
registerParentExe: () => registerParentExe,
|
|
3547
|
-
releaseSpawnLock: () =>
|
|
4169
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3548
4170
|
resolveExeSession: () => resolveExeSession,
|
|
3549
4171
|
sendIntercom: () => sendIntercom,
|
|
3550
4172
|
spawnEmployee: () => spawnEmployee,
|
|
3551
4173
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3552
4174
|
});
|
|
3553
4175
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3554
|
-
import { readFileSync as
|
|
3555
|
-
import
|
|
3556
|
-
import
|
|
3557
|
-
import { fileURLToPath } from "url";
|
|
3558
|
-
import { unlinkSync as
|
|
4176
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4177
|
+
import path14 from "path";
|
|
4178
|
+
import os7 from "os";
|
|
4179
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4180
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3559
4181
|
function spawnLockPath(sessionName) {
|
|
3560
|
-
return
|
|
4182
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3561
4183
|
}
|
|
3562
4184
|
function isProcessAlive(pid) {
|
|
3563
4185
|
try {
|
|
@@ -3567,14 +4189,14 @@ function isProcessAlive(pid) {
|
|
|
3567
4189
|
return false;
|
|
3568
4190
|
}
|
|
3569
4191
|
}
|
|
3570
|
-
function
|
|
3571
|
-
if (!
|
|
4192
|
+
function acquireSpawnLock2(sessionName) {
|
|
4193
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3572
4194
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3573
4195
|
}
|
|
3574
4196
|
const lockFile = spawnLockPath(sessionName);
|
|
3575
|
-
if (
|
|
4197
|
+
if (existsSync11(lockFile)) {
|
|
3576
4198
|
try {
|
|
3577
|
-
const lock = JSON.parse(
|
|
4199
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3578
4200
|
const age = Date.now() - lock.timestamp;
|
|
3579
4201
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3580
4202
|
return false;
|
|
@@ -3585,22 +4207,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3585
4207
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3586
4208
|
return true;
|
|
3587
4209
|
}
|
|
3588
|
-
function
|
|
4210
|
+
function releaseSpawnLock2(sessionName) {
|
|
3589
4211
|
try {
|
|
3590
|
-
|
|
4212
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3591
4213
|
} catch {
|
|
3592
4214
|
}
|
|
3593
4215
|
}
|
|
3594
4216
|
function resolveBehaviorsExporterScript() {
|
|
3595
4217
|
try {
|
|
3596
|
-
const thisFile =
|
|
3597
|
-
const scriptPath =
|
|
3598
|
-
|
|
4218
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4219
|
+
const scriptPath = path14.join(
|
|
4220
|
+
path14.dirname(thisFile),
|
|
3599
4221
|
"..",
|
|
3600
4222
|
"bin",
|
|
3601
4223
|
"exe-export-behaviors.js"
|
|
3602
4224
|
);
|
|
3603
|
-
return
|
|
4225
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3604
4226
|
} catch {
|
|
3605
4227
|
return null;
|
|
3606
4228
|
}
|
|
@@ -3666,11 +4288,11 @@ function extractRootExe(name) {
|
|
|
3666
4288
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3667
4289
|
}
|
|
3668
4290
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3669
|
-
if (!
|
|
4291
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3670
4292
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3671
4293
|
}
|
|
3672
4294
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3673
|
-
const filePath =
|
|
4295
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3674
4296
|
writeFileSync6(filePath, JSON.stringify({
|
|
3675
4297
|
parentExe: rootExe,
|
|
3676
4298
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3679,7 +4301,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3679
4301
|
}
|
|
3680
4302
|
function getParentExe(sessionKey) {
|
|
3681
4303
|
try {
|
|
3682
|
-
const data = JSON.parse(
|
|
4304
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3683
4305
|
return data.parentExe || null;
|
|
3684
4306
|
} catch {
|
|
3685
4307
|
return null;
|
|
@@ -3687,8 +4309,8 @@ function getParentExe(sessionKey) {
|
|
|
3687
4309
|
}
|
|
3688
4310
|
function getDispatchedBy(sessionKey) {
|
|
3689
4311
|
try {
|
|
3690
|
-
const data = JSON.parse(
|
|
3691
|
-
|
|
4312
|
+
const data = JSON.parse(readFileSync10(
|
|
4313
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3692
4314
|
"utf8"
|
|
3693
4315
|
));
|
|
3694
4316
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3714,10 +4336,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3714
4336
|
}
|
|
3715
4337
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3716
4338
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3717
|
-
if (!isAlive(base) &&
|
|
4339
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3718
4340
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3719
4341
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3720
|
-
if (!isAlive(candidate) &&
|
|
4342
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3721
4343
|
}
|
|
3722
4344
|
return null;
|
|
3723
4345
|
}
|
|
@@ -3749,15 +4371,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3749
4371
|
}
|
|
3750
4372
|
function readDebounceState() {
|
|
3751
4373
|
try {
|
|
3752
|
-
if (!
|
|
3753
|
-
return JSON.parse(
|
|
4374
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4375
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3754
4376
|
} catch {
|
|
3755
4377
|
return {};
|
|
3756
4378
|
}
|
|
3757
4379
|
}
|
|
3758
4380
|
function writeDebounceState(state) {
|
|
3759
4381
|
try {
|
|
3760
|
-
if (!
|
|
4382
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3761
4383
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3762
4384
|
} catch {
|
|
3763
4385
|
}
|
|
@@ -3877,7 +4499,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3877
4499
|
return true;
|
|
3878
4500
|
}
|
|
3879
4501
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3880
|
-
if (
|
|
4502
|
+
if (isCoordinatorName(employeeName)) {
|
|
3881
4503
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3882
4504
|
}
|
|
3883
4505
|
try {
|
|
@@ -3949,26 +4571,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3949
4571
|
const transport = getTransport();
|
|
3950
4572
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3951
4573
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3952
|
-
const logDir =
|
|
3953
|
-
const logFile =
|
|
3954
|
-
if (!
|
|
4574
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4575
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4576
|
+
if (!existsSync11(logDir)) {
|
|
3955
4577
|
mkdirSync5(logDir, { recursive: true });
|
|
3956
4578
|
}
|
|
3957
4579
|
transport.kill(sessionName);
|
|
3958
4580
|
let cleanupSuffix = "";
|
|
3959
4581
|
try {
|
|
3960
|
-
const thisFile =
|
|
3961
|
-
const cleanupScript =
|
|
3962
|
-
if (
|
|
4582
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4583
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4584
|
+
if (existsSync11(cleanupScript)) {
|
|
3963
4585
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3964
4586
|
}
|
|
3965
4587
|
} catch {
|
|
3966
4588
|
}
|
|
3967
4589
|
try {
|
|
3968
|
-
const claudeJsonPath =
|
|
4590
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3969
4591
|
let claudeJson = {};
|
|
3970
4592
|
try {
|
|
3971
|
-
claudeJson = JSON.parse(
|
|
4593
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3972
4594
|
} catch {
|
|
3973
4595
|
}
|
|
3974
4596
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3980,13 +4602,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3980
4602
|
} catch {
|
|
3981
4603
|
}
|
|
3982
4604
|
try {
|
|
3983
|
-
const settingsDir =
|
|
4605
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3984
4606
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3985
|
-
const projSettingsDir =
|
|
3986
|
-
const settingsPath =
|
|
4607
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
4608
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3987
4609
|
let settings = {};
|
|
3988
4610
|
try {
|
|
3989
|
-
settings = JSON.parse(
|
|
4611
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3990
4612
|
} catch {
|
|
3991
4613
|
}
|
|
3992
4614
|
const perms = settings.permissions ?? {};
|
|
@@ -4027,8 +4649,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4027
4649
|
let behaviorsFlag = "";
|
|
4028
4650
|
let legacyFallbackWarned = false;
|
|
4029
4651
|
if (!useExeAgent && !useBinSymlink) {
|
|
4030
|
-
const identityPath =
|
|
4031
|
-
|
|
4652
|
+
const identityPath = path14.join(
|
|
4653
|
+
os7.homedir(),
|
|
4032
4654
|
".exe-os",
|
|
4033
4655
|
"identity",
|
|
4034
4656
|
`${employeeName}.md`
|
|
@@ -4037,13 +4659,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4037
4659
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4038
4660
|
if (hasAgentFlag) {
|
|
4039
4661
|
identityFlag = ` --agent ${employeeName}`;
|
|
4040
|
-
} else if (
|
|
4662
|
+
} else if (existsSync11(identityPath)) {
|
|
4041
4663
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4042
4664
|
legacyFallbackWarned = true;
|
|
4043
4665
|
}
|
|
4044
4666
|
const behaviorsFile = exportBehaviorsSync(
|
|
4045
4667
|
employeeName,
|
|
4046
|
-
|
|
4668
|
+
path14.basename(spawnCwd),
|
|
4047
4669
|
sessionName
|
|
4048
4670
|
);
|
|
4049
4671
|
if (behaviorsFile) {
|
|
@@ -4058,9 +4680,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4058
4680
|
}
|
|
4059
4681
|
let sessionContextFlag = "";
|
|
4060
4682
|
try {
|
|
4061
|
-
const ctxDir =
|
|
4683
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4062
4684
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4063
|
-
const ctxFile =
|
|
4685
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4064
4686
|
const ctxContent = [
|
|
4065
4687
|
`## Session Context`,
|
|
4066
4688
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4099,13 +4721,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4099
4721
|
command: spawnCommand
|
|
4100
4722
|
});
|
|
4101
4723
|
if (spawnResult.error) {
|
|
4102
|
-
|
|
4724
|
+
releaseSpawnLock2(sessionName);
|
|
4103
4725
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4104
4726
|
}
|
|
4105
4727
|
transport.pipeLog(sessionName, logFile);
|
|
4106
4728
|
try {
|
|
4107
4729
|
const mySession = getMySession();
|
|
4108
|
-
const dispatchInfo =
|
|
4730
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4109
4731
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4110
4732
|
dispatchedBy: mySession,
|
|
4111
4733
|
rootExe: exeSession,
|
|
@@ -4137,7 +4759,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4137
4759
|
}
|
|
4138
4760
|
}
|
|
4139
4761
|
if (!booted) {
|
|
4140
|
-
|
|
4762
|
+
releaseSpawnLock2(sessionName);
|
|
4141
4763
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4142
4764
|
}
|
|
4143
4765
|
if (!useExeAgent) {
|
|
@@ -4154,7 +4776,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4154
4776
|
pid: 0,
|
|
4155
4777
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4156
4778
|
});
|
|
4157
|
-
|
|
4779
|
+
releaseSpawnLock2(sessionName);
|
|
4158
4780
|
return { sessionName };
|
|
4159
4781
|
}
|
|
4160
4782
|
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;
|
|
@@ -4170,14 +4792,14 @@ var init_tmux_routing = __esm({
|
|
|
4170
4792
|
init_intercom_queue();
|
|
4171
4793
|
init_plan_limits();
|
|
4172
4794
|
init_employees();
|
|
4173
|
-
SPAWN_LOCK_DIR =
|
|
4174
|
-
SESSION_CACHE =
|
|
4795
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4796
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4175
4797
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4176
4798
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4177
4799
|
VERIFY_PANE_LINES = 200;
|
|
4178
4800
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4179
|
-
INTERCOM_LOG2 =
|
|
4180
|
-
DEBOUNCE_FILE =
|
|
4801
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4802
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4181
4803
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4182
4804
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4183
4805
|
}
|
|
@@ -4218,14 +4840,14 @@ var init_memory = __esm({
|
|
|
4218
4840
|
|
|
4219
4841
|
// src/lib/keychain.ts
|
|
4220
4842
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4221
|
-
import { existsSync as
|
|
4222
|
-
import
|
|
4223
|
-
import
|
|
4843
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4844
|
+
import path15 from "path";
|
|
4845
|
+
import os8 from "os";
|
|
4224
4846
|
function getKeyDir() {
|
|
4225
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4847
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
|
|
4226
4848
|
}
|
|
4227
4849
|
function getKeyPath() {
|
|
4228
|
-
return
|
|
4850
|
+
return path15.join(getKeyDir(), "master.key");
|
|
4229
4851
|
}
|
|
4230
4852
|
async function tryKeytar() {
|
|
4231
4853
|
try {
|
|
@@ -4246,13 +4868,21 @@ async function getMasterKey() {
|
|
|
4246
4868
|
}
|
|
4247
4869
|
}
|
|
4248
4870
|
const keyPath = getKeyPath();
|
|
4249
|
-
if (!
|
|
4871
|
+
if (!existsSync12(keyPath)) {
|
|
4872
|
+
process.stderr.write(
|
|
4873
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4874
|
+
`
|
|
4875
|
+
);
|
|
4250
4876
|
return null;
|
|
4251
4877
|
}
|
|
4252
4878
|
try {
|
|
4253
4879
|
const content = await readFile4(keyPath, "utf-8");
|
|
4254
4880
|
return Buffer.from(content.trim(), "base64");
|
|
4255
|
-
} catch {
|
|
4881
|
+
} catch (err) {
|
|
4882
|
+
process.stderr.write(
|
|
4883
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4884
|
+
`
|
|
4885
|
+
);
|
|
4256
4886
|
return null;
|
|
4257
4887
|
}
|
|
4258
4888
|
}
|
|
@@ -4278,12 +4908,12 @@ __export(shard_manager_exports, {
|
|
|
4278
4908
|
listShards: () => listShards,
|
|
4279
4909
|
shardExists: () => shardExists
|
|
4280
4910
|
});
|
|
4281
|
-
import
|
|
4282
|
-
import { existsSync as
|
|
4911
|
+
import path16 from "path";
|
|
4912
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4283
4913
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4284
4914
|
function initShardManager(encryptionKey) {
|
|
4285
4915
|
_encryptionKey = encryptionKey;
|
|
4286
|
-
if (!
|
|
4916
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4287
4917
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
4288
4918
|
}
|
|
4289
4919
|
_shardingEnabled = true;
|
|
@@ -4304,7 +4934,7 @@ function getShardClient(projectName2) {
|
|
|
4304
4934
|
}
|
|
4305
4935
|
const cached = _shards.get(safeName);
|
|
4306
4936
|
if (cached) return cached;
|
|
4307
|
-
const dbPath =
|
|
4937
|
+
const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
|
|
4308
4938
|
const client = createClient2({
|
|
4309
4939
|
url: `file:${dbPath}`,
|
|
4310
4940
|
encryptionKey: _encryptionKey
|
|
@@ -4314,10 +4944,10 @@ function getShardClient(projectName2) {
|
|
|
4314
4944
|
}
|
|
4315
4945
|
function shardExists(projectName2) {
|
|
4316
4946
|
const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4317
|
-
return
|
|
4947
|
+
return existsSync13(path16.join(SHARDS_DIR, `${safeName}.db`));
|
|
4318
4948
|
}
|
|
4319
4949
|
function listShards() {
|
|
4320
|
-
if (!
|
|
4950
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4321
4951
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4322
4952
|
}
|
|
4323
4953
|
async function ensureShardSchema(client) {
|
|
@@ -4503,7 +5133,7 @@ var init_shard_manager = __esm({
|
|
|
4503
5133
|
"src/lib/shard-manager.ts"() {
|
|
4504
5134
|
"use strict";
|
|
4505
5135
|
init_config();
|
|
4506
|
-
SHARDS_DIR =
|
|
5136
|
+
SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
|
|
4507
5137
|
_shards = /* @__PURE__ */ new Map();
|
|
4508
5138
|
_encryptionKey = null;
|
|
4509
5139
|
_shardingEnabled = false;
|
|
@@ -4628,7 +5258,7 @@ __export(global_procedures_exports, {
|
|
|
4628
5258
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4629
5259
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4630
5260
|
});
|
|
4631
|
-
import { randomUUID as
|
|
5261
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4632
5262
|
async function loadGlobalProcedures() {
|
|
4633
5263
|
const client = getClient();
|
|
4634
5264
|
const result = await client.execute({
|
|
@@ -4657,7 +5287,7 @@ ${sections.join("\n\n")}
|
|
|
4657
5287
|
`;
|
|
4658
5288
|
}
|
|
4659
5289
|
async function storeGlobalProcedure(input) {
|
|
4660
|
-
const id =
|
|
5290
|
+
const id = randomUUID3();
|
|
4661
5291
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4662
5292
|
const client = getClient();
|
|
4663
5293
|
await client.execute({
|
|
@@ -4708,6 +5338,7 @@ __export(store_exports, {
|
|
|
4708
5338
|
vectorToBlob: () => vectorToBlob,
|
|
4709
5339
|
writeMemory: () => writeMemory
|
|
4710
5340
|
});
|
|
5341
|
+
import { createHash } from "crypto";
|
|
4711
5342
|
function isBusyError2(err) {
|
|
4712
5343
|
if (err instanceof Error) {
|
|
4713
5344
|
const msg = err.message.toLowerCase();
|
|
@@ -4781,12 +5412,52 @@ function classifyTier(record) {
|
|
|
4781
5412
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4782
5413
|
return 3;
|
|
4783
5414
|
}
|
|
5415
|
+
function inferFilePaths(record) {
|
|
5416
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5417
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5418
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5419
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5420
|
+
}
|
|
5421
|
+
function inferCommitHash(record) {
|
|
5422
|
+
if (record.tool_name !== "Bash") return null;
|
|
5423
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5424
|
+
return match ? match[1] : null;
|
|
5425
|
+
}
|
|
5426
|
+
function inferLanguageType(record) {
|
|
5427
|
+
const text = record.raw_text;
|
|
5428
|
+
if (!text || text.length < 10) return null;
|
|
5429
|
+
const trimmed = text.trimStart();
|
|
5430
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5431
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5432
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5433
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5434
|
+
return "mixed";
|
|
5435
|
+
}
|
|
5436
|
+
function inferDomain(record) {
|
|
5437
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5438
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5439
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5440
|
+
return null;
|
|
5441
|
+
}
|
|
4784
5442
|
async function writeMemory(record) {
|
|
4785
5443
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4786
5444
|
throw new Error(
|
|
4787
5445
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4788
5446
|
);
|
|
4789
5447
|
}
|
|
5448
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5449
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
try {
|
|
5453
|
+
const client = getClient();
|
|
5454
|
+
const existing = await client.execute({
|
|
5455
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5456
|
+
args: [contentHash, record.agent_id]
|
|
5457
|
+
});
|
|
5458
|
+
if (existing.rows.length > 0) return;
|
|
5459
|
+
} catch {
|
|
5460
|
+
}
|
|
4790
5461
|
const dbRow = {
|
|
4791
5462
|
id: record.id,
|
|
4792
5463
|
agent_id: record.agent_id,
|
|
@@ -4816,7 +5487,23 @@ async function writeMemory(record) {
|
|
|
4816
5487
|
supersedes_id: record.supersedes_id ?? null,
|
|
4817
5488
|
draft: record.draft ? 1 : 0,
|
|
4818
5489
|
memory_type: record.memory_type ?? "raw",
|
|
4819
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5490
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5491
|
+
content_hash: contentHash,
|
|
5492
|
+
intent: record.intent ?? null,
|
|
5493
|
+
outcome: record.outcome ?? null,
|
|
5494
|
+
domain: record.domain ?? inferDomain(record),
|
|
5495
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5496
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5497
|
+
chain_position: record.chain_position ?? null,
|
|
5498
|
+
review_status: record.review_status ?? null,
|
|
5499
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5500
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5501
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5502
|
+
duration_ms: record.duration_ms ?? null,
|
|
5503
|
+
token_cost: record.token_cost ?? null,
|
|
5504
|
+
audience: record.audience ?? null,
|
|
5505
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5506
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4820
5507
|
};
|
|
4821
5508
|
_pendingRecords.push(dbRow);
|
|
4822
5509
|
orgBus.emit({
|
|
@@ -4874,80 +5561,85 @@ async function flushBatch() {
|
|
|
4874
5561
|
const draft = row.draft ? 1 : 0;
|
|
4875
5562
|
const memoryType = row.memory_type ?? "raw";
|
|
4876
5563
|
const trajectory = row.trajectory ?? null;
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
5564
|
+
const contentHash = row.content_hash ?? null;
|
|
5565
|
+
const intent = row.intent ?? null;
|
|
5566
|
+
const outcome = row.outcome ?? null;
|
|
5567
|
+
const domain = row.domain ?? null;
|
|
5568
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5569
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5570
|
+
const chainPosition = row.chain_position ?? null;
|
|
5571
|
+
const reviewStatus = row.review_status ?? null;
|
|
5572
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5573
|
+
const filePaths = row.file_paths ?? null;
|
|
5574
|
+
const commitHash = row.commit_hash ?? null;
|
|
5575
|
+
const durationMs = row.duration_ms ?? null;
|
|
5576
|
+
const tokenCost = row.token_cost ?? null;
|
|
5577
|
+
const audience = row.audience ?? null;
|
|
5578
|
+
const languageType = row.language_type ?? null;
|
|
5579
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5580
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4887
5581
|
tool_name, project_name,
|
|
4888
5582
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4889
5583
|
confidence, last_accessed,
|
|
4890
5584
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4891
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
trajectory
|
|
4950
|
-
]
|
|
5585
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5586
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5587
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5588
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5589
|
+
const metaArgs = [
|
|
5590
|
+
intent,
|
|
5591
|
+
outcome,
|
|
5592
|
+
domain,
|
|
5593
|
+
referencedEntities,
|
|
5594
|
+
retrievalCount,
|
|
5595
|
+
chainPosition,
|
|
5596
|
+
reviewStatus,
|
|
5597
|
+
contextWindowPct,
|
|
5598
|
+
filePaths,
|
|
5599
|
+
commitHash,
|
|
5600
|
+
durationMs,
|
|
5601
|
+
tokenCost,
|
|
5602
|
+
audience,
|
|
5603
|
+
languageType,
|
|
5604
|
+
parentMemoryId
|
|
5605
|
+
];
|
|
5606
|
+
const baseArgs = [
|
|
5607
|
+
row.id,
|
|
5608
|
+
row.agent_id,
|
|
5609
|
+
row.agent_role,
|
|
5610
|
+
row.session_id,
|
|
5611
|
+
row.timestamp,
|
|
5612
|
+
row.tool_name,
|
|
5613
|
+
row.project_name,
|
|
5614
|
+
row.has_error,
|
|
5615
|
+
row.raw_text
|
|
5616
|
+
];
|
|
5617
|
+
const sharedArgs = [
|
|
5618
|
+
row.version,
|
|
5619
|
+
taskId,
|
|
5620
|
+
importance,
|
|
5621
|
+
status,
|
|
5622
|
+
confidence,
|
|
5623
|
+
lastAccessed,
|
|
5624
|
+
workspaceId,
|
|
5625
|
+
documentId,
|
|
5626
|
+
userId,
|
|
5627
|
+
charOffset,
|
|
5628
|
+
pageNumber,
|
|
5629
|
+
sourcePath,
|
|
5630
|
+
sourceType,
|
|
5631
|
+
tier,
|
|
5632
|
+
supersedesId,
|
|
5633
|
+
draft,
|
|
5634
|
+
memoryType,
|
|
5635
|
+
trajectory,
|
|
5636
|
+
contentHash
|
|
5637
|
+
];
|
|
5638
|
+
return {
|
|
5639
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5640
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5641
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5642
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4951
5643
|
};
|
|
4952
5644
|
};
|
|
4953
5645
|
const globalClient = getClient();
|
|
@@ -5402,13 +6094,13 @@ async function sweepTasks(projectName2, options = {}) {
|
|
|
5402
6094
|
}
|
|
5403
6095
|
|
|
5404
6096
|
// src/bin/git-sweep.ts
|
|
5405
|
-
import
|
|
6097
|
+
import path17 from "path";
|
|
5406
6098
|
var args = process.argv.slice(2);
|
|
5407
6099
|
var dryRun = args.includes("--dry-run");
|
|
5408
6100
|
var projectIdx = args.indexOf("--project");
|
|
5409
6101
|
var limitIdx = args.indexOf("--limit");
|
|
5410
6102
|
var staleIdx = args.indexOf("--stale");
|
|
5411
|
-
var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(
|
|
6103
|
+
var projectName = projectIdx >= 0 ? args[projectIdx + 1] : process.cwd().split(path17.sep).pop();
|
|
5412
6104
|
var commitLimit = limitIdx >= 0 ? Number(args[limitIdx + 1]) : void 0;
|
|
5413
6105
|
var staleMinutes = staleIdx >= 0 ? Number(args[staleIdx + 1]) : void 0;
|
|
5414
6106
|
async function main() {
|