@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/runtime/index.js
CHANGED
|
@@ -648,6 +648,443 @@ var init_db_retry = __esm({
|
|
|
648
648
|
}
|
|
649
649
|
});
|
|
650
650
|
|
|
651
|
+
// src/lib/exe-daemon-client.ts
|
|
652
|
+
import net from "net";
|
|
653
|
+
import { spawn } from "child_process";
|
|
654
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
655
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
656
|
+
import path6 from "path";
|
|
657
|
+
import { fileURLToPath } from "url";
|
|
658
|
+
function handleData(chunk) {
|
|
659
|
+
_buffer += chunk.toString();
|
|
660
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
661
|
+
_buffer = "";
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
let newlineIdx;
|
|
665
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
666
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
667
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
668
|
+
if (!line) continue;
|
|
669
|
+
try {
|
|
670
|
+
const response = JSON.parse(line);
|
|
671
|
+
const id = response.id;
|
|
672
|
+
if (!id) continue;
|
|
673
|
+
const entry = _pending.get(id);
|
|
674
|
+
if (entry) {
|
|
675
|
+
clearTimeout(entry.timer);
|
|
676
|
+
_pending.delete(id);
|
|
677
|
+
entry.resolve(response);
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function cleanupStaleFiles() {
|
|
684
|
+
if (existsSync5(PID_PATH)) {
|
|
685
|
+
try {
|
|
686
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
687
|
+
if (pid > 0) {
|
|
688
|
+
try {
|
|
689
|
+
process.kill(pid, 0);
|
|
690
|
+
return;
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
unlinkSync2(PID_PATH);
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
unlinkSync2(SOCKET_PATH);
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function findPackageRoot() {
|
|
707
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
708
|
+
const { root } = path6.parse(dir);
|
|
709
|
+
while (dir !== root) {
|
|
710
|
+
if (existsSync5(path6.join(dir, "package.json"))) return dir;
|
|
711
|
+
dir = path6.dirname(dir);
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
function spawnDaemon() {
|
|
716
|
+
const pkgRoot = findPackageRoot();
|
|
717
|
+
if (!pkgRoot) {
|
|
718
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
722
|
+
if (!existsSync5(daemonPath)) {
|
|
723
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
724
|
+
`);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const resolvedPath = daemonPath;
|
|
728
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
729
|
+
`);
|
|
730
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
731
|
+
let stderrFd = "ignore";
|
|
732
|
+
try {
|
|
733
|
+
stderrFd = openSync(logPath, "a");
|
|
734
|
+
} catch {
|
|
735
|
+
}
|
|
736
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
737
|
+
detached: true,
|
|
738
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
739
|
+
env: {
|
|
740
|
+
...process.env,
|
|
741
|
+
TMUX: void 0,
|
|
742
|
+
// Daemon is global — must not inherit session scope
|
|
743
|
+
TMUX_PANE: void 0,
|
|
744
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
745
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
746
|
+
EXE_DAEMON_PID: PID_PATH
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
child.unref();
|
|
750
|
+
if (typeof stderrFd === "number") {
|
|
751
|
+
try {
|
|
752
|
+
closeSync(stderrFd);
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function acquireSpawnLock() {
|
|
758
|
+
try {
|
|
759
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
760
|
+
closeSync(fd);
|
|
761
|
+
return true;
|
|
762
|
+
} catch {
|
|
763
|
+
try {
|
|
764
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
765
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
766
|
+
try {
|
|
767
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
772
|
+
closeSync(fd);
|
|
773
|
+
return true;
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function releaseSpawnLock() {
|
|
783
|
+
try {
|
|
784
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function connectToSocket() {
|
|
789
|
+
return new Promise((resolve) => {
|
|
790
|
+
if (_socket && _connected) {
|
|
791
|
+
resolve(true);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
795
|
+
const connectTimeout = setTimeout(() => {
|
|
796
|
+
socket.destroy();
|
|
797
|
+
resolve(false);
|
|
798
|
+
}, 2e3);
|
|
799
|
+
socket.on("connect", () => {
|
|
800
|
+
clearTimeout(connectTimeout);
|
|
801
|
+
_socket = socket;
|
|
802
|
+
_connected = true;
|
|
803
|
+
_buffer = "";
|
|
804
|
+
socket.on("data", handleData);
|
|
805
|
+
socket.on("close", () => {
|
|
806
|
+
_connected = false;
|
|
807
|
+
_socket = null;
|
|
808
|
+
for (const [id, entry] of _pending) {
|
|
809
|
+
clearTimeout(entry.timer);
|
|
810
|
+
_pending.delete(id);
|
|
811
|
+
entry.resolve({ error: "Connection closed" });
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
socket.on("error", () => {
|
|
815
|
+
_connected = false;
|
|
816
|
+
_socket = null;
|
|
817
|
+
});
|
|
818
|
+
resolve(true);
|
|
819
|
+
});
|
|
820
|
+
socket.on("error", () => {
|
|
821
|
+
clearTimeout(connectTimeout);
|
|
822
|
+
resolve(false);
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
async function connectEmbedDaemon() {
|
|
827
|
+
if (_socket && _connected) return true;
|
|
828
|
+
if (await connectToSocket()) return true;
|
|
829
|
+
if (acquireSpawnLock()) {
|
|
830
|
+
try {
|
|
831
|
+
cleanupStaleFiles();
|
|
832
|
+
spawnDaemon();
|
|
833
|
+
} finally {
|
|
834
|
+
releaseSpawnLock();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const start = Date.now();
|
|
838
|
+
let delay2 = 100;
|
|
839
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
840
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
841
|
+
if (await connectToSocket()) return true;
|
|
842
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
843
|
+
}
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
847
|
+
return new Promise((resolve) => {
|
|
848
|
+
if (!_socket || !_connected) {
|
|
849
|
+
resolve({ error: "Not connected" });
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const id = randomUUID2();
|
|
853
|
+
const timer = setTimeout(() => {
|
|
854
|
+
_pending.delete(id);
|
|
855
|
+
resolve({ error: "Request timeout" });
|
|
856
|
+
}, timeoutMs);
|
|
857
|
+
_pending.set(id, { resolve, timer });
|
|
858
|
+
try {
|
|
859
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
860
|
+
} catch {
|
|
861
|
+
clearTimeout(timer);
|
|
862
|
+
_pending.delete(id);
|
|
863
|
+
resolve({ error: "Write failed" });
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
function isClientConnected() {
|
|
868
|
+
return _connected;
|
|
869
|
+
}
|
|
870
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
871
|
+
var init_exe_daemon_client = __esm({
|
|
872
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
873
|
+
"use strict";
|
|
874
|
+
init_config();
|
|
875
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
876
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
877
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
878
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
879
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
880
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
881
|
+
_socket = null;
|
|
882
|
+
_connected = false;
|
|
883
|
+
_buffer = "";
|
|
884
|
+
_pending = /* @__PURE__ */ new Map();
|
|
885
|
+
MAX_BUFFER = 1e7;
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
// src/lib/daemon-protocol.ts
|
|
890
|
+
function serializeValue(v) {
|
|
891
|
+
if (v === null || v === void 0) return null;
|
|
892
|
+
if (typeof v === "bigint") return Number(v);
|
|
893
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
894
|
+
if (v instanceof Uint8Array) {
|
|
895
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
896
|
+
}
|
|
897
|
+
if (ArrayBuffer.isView(v)) {
|
|
898
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
899
|
+
}
|
|
900
|
+
if (v instanceof ArrayBuffer) {
|
|
901
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
902
|
+
}
|
|
903
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
904
|
+
return String(v);
|
|
905
|
+
}
|
|
906
|
+
function deserializeValue(v) {
|
|
907
|
+
if (v === null) return null;
|
|
908
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
909
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
910
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
911
|
+
}
|
|
912
|
+
return v;
|
|
913
|
+
}
|
|
914
|
+
function deserializeResultSet(srs) {
|
|
915
|
+
const rows = srs.rows.map((obj) => {
|
|
916
|
+
const values = srs.columns.map(
|
|
917
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
918
|
+
);
|
|
919
|
+
const row = values;
|
|
920
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
921
|
+
const col = srs.columns[i];
|
|
922
|
+
if (col !== void 0) {
|
|
923
|
+
row[col] = values[i] ?? null;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
Object.defineProperty(row, "length", {
|
|
927
|
+
value: values.length,
|
|
928
|
+
enumerable: false
|
|
929
|
+
});
|
|
930
|
+
return row;
|
|
931
|
+
});
|
|
932
|
+
return {
|
|
933
|
+
columns: srs.columns,
|
|
934
|
+
columnTypes: srs.columnTypes ?? [],
|
|
935
|
+
rows,
|
|
936
|
+
rowsAffected: srs.rowsAffected,
|
|
937
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
938
|
+
toJSON: () => ({
|
|
939
|
+
columns: srs.columns,
|
|
940
|
+
columnTypes: srs.columnTypes ?? [],
|
|
941
|
+
rows: srs.rows,
|
|
942
|
+
rowsAffected: srs.rowsAffected,
|
|
943
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
944
|
+
})
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
var init_daemon_protocol = __esm({
|
|
948
|
+
"src/lib/daemon-protocol.ts"() {
|
|
949
|
+
"use strict";
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// src/lib/db-daemon-client.ts
|
|
954
|
+
var db_daemon_client_exports = {};
|
|
955
|
+
__export(db_daemon_client_exports, {
|
|
956
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
957
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
958
|
+
});
|
|
959
|
+
function normalizeStatement(stmt) {
|
|
960
|
+
if (typeof stmt === "string") {
|
|
961
|
+
return { sql: stmt, args: [] };
|
|
962
|
+
}
|
|
963
|
+
const sql = stmt.sql;
|
|
964
|
+
let args = [];
|
|
965
|
+
if (Array.isArray(stmt.args)) {
|
|
966
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
967
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
968
|
+
const named = {};
|
|
969
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
970
|
+
named[key] = serializeValue(val);
|
|
971
|
+
}
|
|
972
|
+
return { sql, args: named };
|
|
973
|
+
}
|
|
974
|
+
return { sql, args };
|
|
975
|
+
}
|
|
976
|
+
function createDaemonDbClient(fallbackClient) {
|
|
977
|
+
let _useDaemon = false;
|
|
978
|
+
const client = {
|
|
979
|
+
async execute(stmt) {
|
|
980
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
981
|
+
return fallbackClient.execute(stmt);
|
|
982
|
+
}
|
|
983
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
984
|
+
const response = await sendDaemonRequest({
|
|
985
|
+
type: "db-execute",
|
|
986
|
+
sql,
|
|
987
|
+
args
|
|
988
|
+
});
|
|
989
|
+
if (response.error) {
|
|
990
|
+
const errMsg = String(response.error);
|
|
991
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
992
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
993
|
+
`);
|
|
994
|
+
return fallbackClient.execute(stmt);
|
|
995
|
+
}
|
|
996
|
+
throw new Error(errMsg);
|
|
997
|
+
}
|
|
998
|
+
if (response.db) {
|
|
999
|
+
return deserializeResultSet(response.db);
|
|
1000
|
+
}
|
|
1001
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1002
|
+
return fallbackClient.execute(stmt);
|
|
1003
|
+
},
|
|
1004
|
+
async batch(stmts, mode) {
|
|
1005
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1006
|
+
return fallbackClient.batch(stmts, mode);
|
|
1007
|
+
}
|
|
1008
|
+
const statements = stmts.map(normalizeStatement);
|
|
1009
|
+
const response = await sendDaemonRequest({
|
|
1010
|
+
type: "db-batch",
|
|
1011
|
+
statements,
|
|
1012
|
+
mode: mode ?? "deferred"
|
|
1013
|
+
});
|
|
1014
|
+
if (response.error) {
|
|
1015
|
+
const errMsg = String(response.error);
|
|
1016
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1017
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1018
|
+
`);
|
|
1019
|
+
return fallbackClient.batch(stmts, mode);
|
|
1020
|
+
}
|
|
1021
|
+
throw new Error(errMsg);
|
|
1022
|
+
}
|
|
1023
|
+
const batchResults = response["db-batch"];
|
|
1024
|
+
if (batchResults) {
|
|
1025
|
+
return batchResults.map(deserializeResultSet);
|
|
1026
|
+
}
|
|
1027
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1028
|
+
return fallbackClient.batch(stmts, mode);
|
|
1029
|
+
},
|
|
1030
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1031
|
+
async transaction(mode) {
|
|
1032
|
+
return fallbackClient.transaction(mode);
|
|
1033
|
+
},
|
|
1034
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1035
|
+
async executeMultiple(sql) {
|
|
1036
|
+
return fallbackClient.executeMultiple(sql);
|
|
1037
|
+
},
|
|
1038
|
+
// migrate — delegate to fallback
|
|
1039
|
+
async migrate(stmts) {
|
|
1040
|
+
return fallbackClient.migrate(stmts);
|
|
1041
|
+
},
|
|
1042
|
+
// Sync mode — delegate to fallback
|
|
1043
|
+
sync() {
|
|
1044
|
+
return fallbackClient.sync();
|
|
1045
|
+
},
|
|
1046
|
+
close() {
|
|
1047
|
+
_useDaemon = false;
|
|
1048
|
+
},
|
|
1049
|
+
get closed() {
|
|
1050
|
+
return fallbackClient.closed;
|
|
1051
|
+
},
|
|
1052
|
+
get protocol() {
|
|
1053
|
+
return fallbackClient.protocol;
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
return {
|
|
1057
|
+
...client,
|
|
1058
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1059
|
+
_enableDaemon() {
|
|
1060
|
+
_useDaemon = true;
|
|
1061
|
+
},
|
|
1062
|
+
/** Check if daemon routing is active */
|
|
1063
|
+
_isDaemonActive() {
|
|
1064
|
+
return _useDaemon && isClientConnected();
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1069
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1070
|
+
const connected = await connectEmbedDaemon();
|
|
1071
|
+
if (!connected) {
|
|
1072
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1076
|
+
client._enableDaemon();
|
|
1077
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1078
|
+
return client;
|
|
1079
|
+
}
|
|
1080
|
+
var init_db_daemon_client = __esm({
|
|
1081
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1082
|
+
"use strict";
|
|
1083
|
+
init_exe_daemon_client();
|
|
1084
|
+
init_daemon_protocol();
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
|
|
651
1088
|
// src/lib/database.ts
|
|
652
1089
|
var database_exports = {};
|
|
653
1090
|
__export(database_exports, {
|
|
@@ -656,6 +1093,7 @@ __export(database_exports, {
|
|
|
656
1093
|
ensureSchema: () => ensureSchema,
|
|
657
1094
|
getClient: () => getClient,
|
|
658
1095
|
getRawClient: () => getRawClient,
|
|
1096
|
+
initDaemonClient: () => initDaemonClient,
|
|
659
1097
|
initDatabase: () => initDatabase,
|
|
660
1098
|
initTurso: () => initTurso,
|
|
661
1099
|
isInitialized: () => isInitialized
|
|
@@ -683,8 +1121,27 @@ function getClient() {
|
|
|
683
1121
|
if (!_resilientClient) {
|
|
684
1122
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
685
1123
|
}
|
|
1124
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1125
|
+
return _resilientClient;
|
|
1126
|
+
}
|
|
1127
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1128
|
+
return _daemonClient;
|
|
1129
|
+
}
|
|
686
1130
|
return _resilientClient;
|
|
687
1131
|
}
|
|
1132
|
+
async function initDaemonClient() {
|
|
1133
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1134
|
+
if (!_resilientClient) return;
|
|
1135
|
+
try {
|
|
1136
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1137
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
process.stderr.write(
|
|
1140
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1141
|
+
`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
688
1145
|
function getRawClient() {
|
|
689
1146
|
if (!_client) {
|
|
690
1147
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1171,6 +1628,12 @@ async function ensureSchema() {
|
|
|
1171
1628
|
} catch {
|
|
1172
1629
|
}
|
|
1173
1630
|
}
|
|
1631
|
+
try {
|
|
1632
|
+
await client.execute(
|
|
1633
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1634
|
+
);
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1174
1637
|
await client.executeMultiple(`
|
|
1175
1638
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1176
1639
|
id TEXT PRIMARY KEY,
|
|
@@ -1223,7 +1686,30 @@ async function ensureSchema() {
|
|
|
1223
1686
|
entity_id TEXT NOT NULL,
|
|
1224
1687
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1225
1688
|
);
|
|
1689
|
+
|
|
1690
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1691
|
+
name,
|
|
1692
|
+
content=entities,
|
|
1693
|
+
content_rowid=rowid
|
|
1694
|
+
);
|
|
1695
|
+
|
|
1696
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1697
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1698
|
+
END;
|
|
1699
|
+
|
|
1700
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1701
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1702
|
+
END;
|
|
1703
|
+
|
|
1704
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1705
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1706
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1707
|
+
END;
|
|
1226
1708
|
`);
|
|
1709
|
+
try {
|
|
1710
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1711
|
+
} catch {
|
|
1712
|
+
}
|
|
1227
1713
|
await client.executeMultiple(`
|
|
1228
1714
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1229
1715
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1404,6 +1890,33 @@ async function ensureSchema() {
|
|
|
1404
1890
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1405
1891
|
ON conversations(channel_id);
|
|
1406
1892
|
`);
|
|
1893
|
+
await client.executeMultiple(`
|
|
1894
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1895
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1896
|
+
agent_id TEXT NOT NULL,
|
|
1897
|
+
session_name TEXT,
|
|
1898
|
+
task_id TEXT,
|
|
1899
|
+
project_name TEXT,
|
|
1900
|
+
started_at TEXT NOT NULL
|
|
1901
|
+
);
|
|
1902
|
+
|
|
1903
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1904
|
+
ON session_agent_map(agent_id);
|
|
1905
|
+
`);
|
|
1906
|
+
try {
|
|
1907
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1908
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1909
|
+
await client.execute({
|
|
1910
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1911
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1912
|
+
FROM memories
|
|
1913
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1914
|
+
GROUP BY session_id, agent_id`,
|
|
1915
|
+
args: []
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1407
1920
|
try {
|
|
1408
1921
|
await client.execute({
|
|
1409
1922
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1537,15 +2050,41 @@ async function ensureSchema() {
|
|
|
1537
2050
|
});
|
|
1538
2051
|
} catch {
|
|
1539
2052
|
}
|
|
2053
|
+
for (const col of [
|
|
2054
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2055
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2056
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2057
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2058
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2059
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2060
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2061
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2062
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2063
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2064
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2065
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2066
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2067
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2068
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2069
|
+
]) {
|
|
2070
|
+
try {
|
|
2071
|
+
await client.execute(col);
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
1540
2075
|
}
|
|
1541
2076
|
async function disposeDatabase() {
|
|
2077
|
+
if (_daemonClient) {
|
|
2078
|
+
_daemonClient.close();
|
|
2079
|
+
_daemonClient = null;
|
|
2080
|
+
}
|
|
1542
2081
|
if (_client) {
|
|
1543
2082
|
_client.close();
|
|
1544
2083
|
_client = null;
|
|
1545
2084
|
_resilientClient = null;
|
|
1546
2085
|
}
|
|
1547
2086
|
}
|
|
1548
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2087
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1549
2088
|
var init_database = __esm({
|
|
1550
2089
|
"src/lib/database.ts"() {
|
|
1551
2090
|
"use strict";
|
|
@@ -1553,24 +2092,25 @@ var init_database = __esm({
|
|
|
1553
2092
|
init_employees();
|
|
1554
2093
|
_client = null;
|
|
1555
2094
|
_resilientClient = null;
|
|
2095
|
+
_daemonClient = null;
|
|
1556
2096
|
initTurso = initDatabase;
|
|
1557
2097
|
disposeTurso = disposeDatabase;
|
|
1558
2098
|
}
|
|
1559
2099
|
});
|
|
1560
2100
|
|
|
1561
2101
|
// src/lib/license.ts
|
|
1562
|
-
import { readFileSync as
|
|
1563
|
-
import { randomUUID as
|
|
1564
|
-
import
|
|
2102
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2103
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2104
|
+
import path7 from "path";
|
|
1565
2105
|
import { jwtVerify, importSPKI } from "jose";
|
|
1566
2106
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1567
2107
|
var init_license = __esm({
|
|
1568
2108
|
"src/lib/license.ts"() {
|
|
1569
2109
|
"use strict";
|
|
1570
2110
|
init_config();
|
|
1571
|
-
LICENSE_PATH =
|
|
1572
|
-
CACHE_PATH =
|
|
1573
|
-
DEVICE_ID_PATH =
|
|
2111
|
+
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
2112
|
+
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
2113
|
+
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1574
2114
|
PLAN_LIMITS = {
|
|
1575
2115
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1576
2116
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1582,12 +2122,12 @@ var init_license = __esm({
|
|
|
1582
2122
|
});
|
|
1583
2123
|
|
|
1584
2124
|
// src/lib/plan-limits.ts
|
|
1585
|
-
import { readFileSync as
|
|
1586
|
-
import
|
|
2125
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
2126
|
+
import path8 from "path";
|
|
1587
2127
|
function getLicenseSync() {
|
|
1588
2128
|
try {
|
|
1589
|
-
if (!
|
|
1590
|
-
const raw = JSON.parse(
|
|
2129
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
2130
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
1591
2131
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1592
2132
|
const parts = raw.token.split(".");
|
|
1593
2133
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1625,8 +2165,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1625
2165
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1626
2166
|
let count = 0;
|
|
1627
2167
|
try {
|
|
1628
|
-
if (
|
|
1629
|
-
const raw =
|
|
2168
|
+
if (existsSync7(filePath)) {
|
|
2169
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
1630
2170
|
const employees = JSON.parse(raw);
|
|
1631
2171
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1632
2172
|
}
|
|
@@ -1655,19 +2195,19 @@ var init_plan_limits = __esm({
|
|
|
1655
2195
|
this.name = "PlanLimitError";
|
|
1656
2196
|
}
|
|
1657
2197
|
};
|
|
1658
|
-
CACHE_PATH2 =
|
|
2198
|
+
CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
1659
2199
|
}
|
|
1660
2200
|
});
|
|
1661
2201
|
|
|
1662
2202
|
// src/lib/notifications.ts
|
|
1663
2203
|
import crypto from "crypto";
|
|
1664
|
-
import
|
|
2204
|
+
import path9 from "path";
|
|
1665
2205
|
import os6 from "os";
|
|
1666
2206
|
import {
|
|
1667
|
-
readFileSync as
|
|
2207
|
+
readFileSync as readFileSync8,
|
|
1668
2208
|
readdirSync,
|
|
1669
|
-
unlinkSync as
|
|
1670
|
-
existsSync as
|
|
2209
|
+
unlinkSync as unlinkSync3,
|
|
2210
|
+
existsSync as existsSync8,
|
|
1671
2211
|
rmdirSync
|
|
1672
2212
|
} from "fs";
|
|
1673
2213
|
async function writeNotification(notification) {
|
|
@@ -1831,10 +2371,11 @@ var init_state_bus = __esm({
|
|
|
1831
2371
|
|
|
1832
2372
|
// src/lib/tasks-crud.ts
|
|
1833
2373
|
import crypto3 from "crypto";
|
|
1834
|
-
import
|
|
2374
|
+
import path10 from "path";
|
|
2375
|
+
import os7 from "os";
|
|
1835
2376
|
import { execSync as execSync5 } from "child_process";
|
|
1836
2377
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1837
|
-
import { existsSync as
|
|
2378
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1838
2379
|
async function writeCheckpoint(input) {
|
|
1839
2380
|
const client = getClient();
|
|
1840
2381
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1875,6 +2416,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1875
2416
|
function slugify(title) {
|
|
1876
2417
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1877
2418
|
}
|
|
2419
|
+
function buildKeywordIndex() {
|
|
2420
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2421
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2422
|
+
for (const kw of keywords) {
|
|
2423
|
+
const existing = idx.get(kw) ?? [];
|
|
2424
|
+
existing.push(role);
|
|
2425
|
+
idx.set(kw, existing);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
return idx;
|
|
2429
|
+
}
|
|
2430
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2431
|
+
const employees = loadEmployeesSync();
|
|
2432
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2433
|
+
if (!employee) return void 0;
|
|
2434
|
+
const assigneeRole = employee.role;
|
|
2435
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2436
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2437
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2438
|
+
if (text.includes(keyword)) {
|
|
2439
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2443
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2444
|
+
if (assigneeRole === "COO") return void 0;
|
|
2445
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2446
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2447
|
+
}
|
|
1878
2448
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1879
2449
|
const scope = sessionScopeFilter(scopeSession);
|
|
1880
2450
|
let result = await client.execute({
|
|
@@ -1924,7 +2494,14 @@ async function createTaskCore(input) {
|
|
|
1924
2494
|
const id = crypto3.randomUUID();
|
|
1925
2495
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1926
2496
|
const slug = slugify(input.title);
|
|
1927
|
-
|
|
2497
|
+
let earlySessionScope = null;
|
|
2498
|
+
try {
|
|
2499
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2500
|
+
earlySessionScope = resolveExeSession2();
|
|
2501
|
+
} catch {
|
|
2502
|
+
}
|
|
2503
|
+
const scope = earlySessionScope ?? "default";
|
|
2504
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1928
2505
|
let blockedById = null;
|
|
1929
2506
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1930
2507
|
if (input.blockedBy) {
|
|
@@ -1964,22 +2541,24 @@ async function createTaskCore(input) {
|
|
|
1964
2541
|
if (dupCheck.rows.length > 0) {
|
|
1965
2542
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1966
2543
|
}
|
|
2544
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2545
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2546
|
+
if (laneWarning) {
|
|
2547
|
+
warning = warning ? `${warning}
|
|
2548
|
+
${laneWarning}` : laneWarning;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
1967
2551
|
if (input.baseDir) {
|
|
1968
2552
|
try {
|
|
1969
|
-
await mkdir3(
|
|
1970
|
-
await mkdir3(
|
|
2553
|
+
await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2554
|
+
await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1971
2555
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1972
2556
|
await ensureGitignoreExe(input.baseDir);
|
|
1973
2557
|
} catch {
|
|
1974
2558
|
}
|
|
1975
2559
|
}
|
|
1976
2560
|
const complexity = input.complexity ?? "standard";
|
|
1977
|
-
|
|
1978
|
-
try {
|
|
1979
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1980
|
-
sessionScope = resolveExeSession2();
|
|
1981
|
-
} catch {
|
|
1982
|
-
}
|
|
2561
|
+
const sessionScope = earlySessionScope;
|
|
1983
2562
|
await client.execute({
|
|
1984
2563
|
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)
|
|
1985
2564
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2006,6 +2585,39 @@ async function createTaskCore(input) {
|
|
|
2006
2585
|
now
|
|
2007
2586
|
]
|
|
2008
2587
|
});
|
|
2588
|
+
if (input.baseDir) {
|
|
2589
|
+
try {
|
|
2590
|
+
const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
|
|
2591
|
+
const mdPath = path10.join(EXE_OS_DIR, taskFile);
|
|
2592
|
+
const mdDir = path10.dirname(mdPath);
|
|
2593
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2594
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2595
|
+
const mdContent = `# ${input.title}
|
|
2596
|
+
|
|
2597
|
+
**ID:** ${id}
|
|
2598
|
+
**Status:** ${initialStatus}
|
|
2599
|
+
**Priority:** ${input.priority}
|
|
2600
|
+
**Assigned by:** ${input.assignedBy}
|
|
2601
|
+
**Assigned to:** ${input.assignedTo}
|
|
2602
|
+
**Project:** ${input.projectName}
|
|
2603
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2604
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2605
|
+
**Reviewer:** ${reviewer}
|
|
2606
|
+
|
|
2607
|
+
## Context
|
|
2608
|
+
|
|
2609
|
+
${input.context}
|
|
2610
|
+
|
|
2611
|
+
## MANDATORY: When done
|
|
2612
|
+
|
|
2613
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2614
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2615
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2616
|
+
`;
|
|
2617
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2618
|
+
} catch {
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2009
2621
|
return {
|
|
2010
2622
|
id,
|
|
2011
2623
|
title: input.title,
|
|
@@ -2198,7 +2810,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2198
2810
|
return { row, taskFile, now, taskId };
|
|
2199
2811
|
}
|
|
2200
2812
|
}
|
|
2201
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2813
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2202
2814
|
process.stderr.write(
|
|
2203
2815
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2204
2816
|
`
|
|
@@ -2263,9 +2875,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2263
2875
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2264
2876
|
}
|
|
2265
2877
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2266
|
-
const archPath =
|
|
2878
|
+
const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2267
2879
|
try {
|
|
2268
|
-
if (
|
|
2880
|
+
if (existsSync9(archPath)) return;
|
|
2269
2881
|
const template = [
|
|
2270
2882
|
`# ${projectName} \u2014 System Architecture`,
|
|
2271
2883
|
"",
|
|
@@ -2298,10 +2910,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2298
2910
|
}
|
|
2299
2911
|
}
|
|
2300
2912
|
async function ensureGitignoreExe(baseDir) {
|
|
2301
|
-
const gitignorePath =
|
|
2913
|
+
const gitignorePath = path10.join(baseDir, ".gitignore");
|
|
2302
2914
|
try {
|
|
2303
|
-
if (
|
|
2304
|
-
const content =
|
|
2915
|
+
if (existsSync9(gitignorePath)) {
|
|
2916
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
2305
2917
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2306
2918
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2307
2919
|
} else {
|
|
@@ -2310,20 +2922,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2310
2922
|
} catch {
|
|
2311
2923
|
}
|
|
2312
2924
|
}
|
|
2313
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2925
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2314
2926
|
var init_tasks_crud = __esm({
|
|
2315
2927
|
"src/lib/tasks-crud.ts"() {
|
|
2316
2928
|
"use strict";
|
|
2317
2929
|
init_database();
|
|
2318
2930
|
init_task_scope();
|
|
2931
|
+
init_employees();
|
|
2932
|
+
LANE_KEYWORDS = {
|
|
2933
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2934
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2935
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2936
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2937
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2938
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2939
|
+
};
|
|
2940
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2319
2941
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2320
2942
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2321
2943
|
}
|
|
2322
2944
|
});
|
|
2323
2945
|
|
|
2324
2946
|
// src/lib/tasks-review.ts
|
|
2325
|
-
import
|
|
2326
|
-
import { existsSync as
|
|
2947
|
+
import path11 from "path";
|
|
2948
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
2327
2949
|
async function countPendingReviews(sessionScope) {
|
|
2328
2950
|
const client = getClient();
|
|
2329
2951
|
if (sessionScope) {
|
|
@@ -2345,7 +2967,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2345
2967
|
const result2 = await client.execute({
|
|
2346
2968
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2347
2969
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2348
|
-
AND
|
|
2970
|
+
AND session_scope = ?`,
|
|
2349
2971
|
args: [sinceIso, sessionScope]
|
|
2350
2972
|
});
|
|
2351
2973
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2363,7 +2985,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2363
2985
|
const result2 = await client.execute({
|
|
2364
2986
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2365
2987
|
WHERE status = 'needs_review'
|
|
2366
|
-
AND
|
|
2988
|
+
AND session_scope = ?
|
|
2367
2989
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2368
2990
|
args: [sessionScope, limit]
|
|
2369
2991
|
});
|
|
@@ -2484,14 +3106,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2484
3106
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2485
3107
|
const agent = parts[1];
|
|
2486
3108
|
const slug = parts.slice(2).join("-");
|
|
2487
|
-
const
|
|
3109
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2488
3110
|
const result = await client.execute({
|
|
2489
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2490
|
-
args: [now,
|
|
3111
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3112
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2491
3113
|
});
|
|
2492
3114
|
if (result.rowsAffected > 0) {
|
|
2493
3115
|
process.stderr.write(
|
|
2494
|
-
`[review-cleanup] Cascaded original task to done
|
|
3116
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2495
3117
|
`
|
|
2496
3118
|
);
|
|
2497
3119
|
}
|
|
@@ -2504,11 +3126,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2504
3126
|
);
|
|
2505
3127
|
}
|
|
2506
3128
|
try {
|
|
2507
|
-
const cacheDir =
|
|
2508
|
-
if (
|
|
3129
|
+
const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
|
|
3130
|
+
if (existsSync10(cacheDir)) {
|
|
2509
3131
|
for (const f of readdirSync2(cacheDir)) {
|
|
2510
3132
|
if (f.startsWith("review-notified-")) {
|
|
2511
|
-
|
|
3133
|
+
unlinkSync4(path11.join(cacheDir, f));
|
|
2512
3134
|
}
|
|
2513
3135
|
}
|
|
2514
3136
|
}
|
|
@@ -2529,7 +3151,7 @@ var init_tasks_review = __esm({
|
|
|
2529
3151
|
});
|
|
2530
3152
|
|
|
2531
3153
|
// src/lib/tasks-chain.ts
|
|
2532
|
-
import
|
|
3154
|
+
import path12 from "path";
|
|
2533
3155
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2534
3156
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2535
3157
|
const client = getClient();
|
|
@@ -2546,7 +3168,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2546
3168
|
});
|
|
2547
3169
|
for (const ur of unblockedRows.rows) {
|
|
2548
3170
|
try {
|
|
2549
|
-
const ubFile =
|
|
3171
|
+
const ubFile = path12.join(baseDir, String(ur.task_file));
|
|
2550
3172
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2551
3173
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2552
3174
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2615,7 +3237,7 @@ var init_tasks_chain = __esm({
|
|
|
2615
3237
|
|
|
2616
3238
|
// src/lib/project-name.ts
|
|
2617
3239
|
import { execSync as execSync6 } from "child_process";
|
|
2618
|
-
import
|
|
3240
|
+
import path13 from "path";
|
|
2619
3241
|
function getProjectName(cwd) {
|
|
2620
3242
|
const dir = cwd ?? process.cwd();
|
|
2621
3243
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2628,7 +3250,7 @@ function getProjectName(cwd) {
|
|
|
2628
3250
|
timeout: 2e3,
|
|
2629
3251
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2630
3252
|
}).trim();
|
|
2631
|
-
repoRoot =
|
|
3253
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2632
3254
|
} catch {
|
|
2633
3255
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2634
3256
|
cwd: dir,
|
|
@@ -2637,11 +3259,11 @@ function getProjectName(cwd) {
|
|
|
2637
3259
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2638
3260
|
}).trim();
|
|
2639
3261
|
}
|
|
2640
|
-
_cached2 =
|
|
3262
|
+
_cached2 = path13.basename(repoRoot);
|
|
2641
3263
|
_cachedCwd = dir;
|
|
2642
3264
|
return _cached2;
|
|
2643
3265
|
} catch {
|
|
2644
|
-
_cached2 =
|
|
3266
|
+
_cached2 = path13.basename(dir);
|
|
2645
3267
|
_cachedCwd = dir;
|
|
2646
3268
|
return _cached2;
|
|
2647
3269
|
}
|
|
@@ -2673,7 +3295,7 @@ function findSessionForProject(projectName) {
|
|
|
2673
3295
|
const sessions = listSessions();
|
|
2674
3296
|
for (const s of sessions) {
|
|
2675
3297
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2676
|
-
if (proj === projectName &&
|
|
3298
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2677
3299
|
}
|
|
2678
3300
|
return null;
|
|
2679
3301
|
}
|
|
@@ -2719,7 +3341,7 @@ var init_session_scope = __esm({
|
|
|
2719
3341
|
|
|
2720
3342
|
// src/lib/tasks-notify.ts
|
|
2721
3343
|
async function dispatchTaskToEmployee(input) {
|
|
2722
|
-
if (
|
|
3344
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2723
3345
|
let crossProject = false;
|
|
2724
3346
|
if (input.projectName) {
|
|
2725
3347
|
try {
|
|
@@ -3176,8 +3798,8 @@ __export(tasks_exports, {
|
|
|
3176
3798
|
updateTaskStatus: () => updateTaskStatus,
|
|
3177
3799
|
writeCheckpoint: () => writeCheckpoint
|
|
3178
3800
|
});
|
|
3179
|
-
import
|
|
3180
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as
|
|
3801
|
+
import path14 from "path";
|
|
3802
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
3181
3803
|
async function createTask(input) {
|
|
3182
3804
|
const result = await createTaskCore(input);
|
|
3183
3805
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3196,14 +3818,14 @@ async function updateTask(input) {
|
|
|
3196
3818
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3197
3819
|
try {
|
|
3198
3820
|
const agent = String(row.assigned_to);
|
|
3199
|
-
const cacheDir =
|
|
3200
|
-
const cachePath =
|
|
3821
|
+
const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
|
|
3822
|
+
const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
|
|
3201
3823
|
if (input.status === "in_progress") {
|
|
3202
3824
|
mkdirSync4(cacheDir, { recursive: true });
|
|
3203
3825
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3204
3826
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3205
3827
|
try {
|
|
3206
|
-
|
|
3828
|
+
unlinkSync5(cachePath);
|
|
3207
3829
|
} catch {
|
|
3208
3830
|
}
|
|
3209
3831
|
}
|
|
@@ -3260,7 +3882,7 @@ async function updateTask(input) {
|
|
|
3260
3882
|
}
|
|
3261
3883
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3262
3884
|
if (isTerminal) {
|
|
3263
|
-
const isCoordinator =
|
|
3885
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3264
3886
|
if (!isCoordinator) {
|
|
3265
3887
|
notifyTaskDone();
|
|
3266
3888
|
}
|
|
@@ -3285,7 +3907,7 @@ async function updateTask(input) {
|
|
|
3285
3907
|
}
|
|
3286
3908
|
}
|
|
3287
3909
|
}
|
|
3288
|
-
if (input.status === "done" &&
|
|
3910
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3289
3911
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3290
3912
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3291
3913
|
taskId,
|
|
@@ -3301,7 +3923,7 @@ async function updateTask(input) {
|
|
|
3301
3923
|
});
|
|
3302
3924
|
}
|
|
3303
3925
|
let nextTask;
|
|
3304
|
-
if (isTerminal &&
|
|
3926
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3305
3927
|
try {
|
|
3306
3928
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3307
3929
|
} catch {
|
|
@@ -3645,7 +4267,7 @@ var init_capacity_monitor = __esm({
|
|
|
3645
4267
|
// src/lib/tmux-routing.ts
|
|
3646
4268
|
var tmux_routing_exports = {};
|
|
3647
4269
|
__export(tmux_routing_exports, {
|
|
3648
|
-
acquireSpawnLock: () =>
|
|
4270
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3649
4271
|
employeeSessionName: () => employeeSessionName,
|
|
3650
4272
|
ensureEmployee: () => ensureEmployee,
|
|
3651
4273
|
extractRootExe: () => extractRootExe,
|
|
@@ -3660,20 +4282,20 @@ __export(tmux_routing_exports, {
|
|
|
3660
4282
|
notifyParentExe: () => notifyParentExe,
|
|
3661
4283
|
parseParentExe: () => parseParentExe,
|
|
3662
4284
|
registerParentExe: () => registerParentExe,
|
|
3663
|
-
releaseSpawnLock: () =>
|
|
4285
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3664
4286
|
resolveExeSession: () => resolveExeSession,
|
|
3665
4287
|
sendIntercom: () => sendIntercom,
|
|
3666
4288
|
spawnEmployee: () => spawnEmployee,
|
|
3667
4289
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3668
4290
|
});
|
|
3669
4291
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3670
|
-
import { readFileSync as
|
|
3671
|
-
import
|
|
3672
|
-
import
|
|
3673
|
-
import { fileURLToPath } from "url";
|
|
3674
|
-
import { unlinkSync as
|
|
4292
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
|
|
4293
|
+
import path15 from "path";
|
|
4294
|
+
import os8 from "os";
|
|
4295
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4296
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
3675
4297
|
function spawnLockPath(sessionName) {
|
|
3676
|
-
return
|
|
4298
|
+
return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3677
4299
|
}
|
|
3678
4300
|
function isProcessAlive(pid) {
|
|
3679
4301
|
try {
|
|
@@ -3683,14 +4305,14 @@ function isProcessAlive(pid) {
|
|
|
3683
4305
|
return false;
|
|
3684
4306
|
}
|
|
3685
4307
|
}
|
|
3686
|
-
function
|
|
3687
|
-
if (!
|
|
4308
|
+
function acquireSpawnLock2(sessionName) {
|
|
4309
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
3688
4310
|
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
3689
4311
|
}
|
|
3690
4312
|
const lockFile = spawnLockPath(sessionName);
|
|
3691
|
-
if (
|
|
4313
|
+
if (existsSync11(lockFile)) {
|
|
3692
4314
|
try {
|
|
3693
|
-
const lock = JSON.parse(
|
|
4315
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
3694
4316
|
const age = Date.now() - lock.timestamp;
|
|
3695
4317
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3696
4318
|
return false;
|
|
@@ -3701,22 +4323,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
3701
4323
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3702
4324
|
return true;
|
|
3703
4325
|
}
|
|
3704
|
-
function
|
|
4326
|
+
function releaseSpawnLock2(sessionName) {
|
|
3705
4327
|
try {
|
|
3706
|
-
|
|
4328
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
3707
4329
|
} catch {
|
|
3708
4330
|
}
|
|
3709
4331
|
}
|
|
3710
4332
|
function resolveBehaviorsExporterScript() {
|
|
3711
4333
|
try {
|
|
3712
|
-
const thisFile =
|
|
3713
|
-
const scriptPath =
|
|
3714
|
-
|
|
4334
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4335
|
+
const scriptPath = path15.join(
|
|
4336
|
+
path15.dirname(thisFile),
|
|
3715
4337
|
"..",
|
|
3716
4338
|
"bin",
|
|
3717
4339
|
"exe-export-behaviors.js"
|
|
3718
4340
|
);
|
|
3719
|
-
return
|
|
4341
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
3720
4342
|
} catch {
|
|
3721
4343
|
return null;
|
|
3722
4344
|
}
|
|
@@ -3782,11 +4404,11 @@ function extractRootExe(name) {
|
|
|
3782
4404
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3783
4405
|
}
|
|
3784
4406
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3785
|
-
if (!
|
|
4407
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
3786
4408
|
mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3787
4409
|
}
|
|
3788
4410
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3789
|
-
const filePath =
|
|
4411
|
+
const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3790
4412
|
writeFileSync6(filePath, JSON.stringify({
|
|
3791
4413
|
parentExe: rootExe,
|
|
3792
4414
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -3795,7 +4417,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3795
4417
|
}
|
|
3796
4418
|
function getParentExe(sessionKey) {
|
|
3797
4419
|
try {
|
|
3798
|
-
const data = JSON.parse(
|
|
4420
|
+
const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3799
4421
|
return data.parentExe || null;
|
|
3800
4422
|
} catch {
|
|
3801
4423
|
return null;
|
|
@@ -3803,8 +4425,8 @@ function getParentExe(sessionKey) {
|
|
|
3803
4425
|
}
|
|
3804
4426
|
function getDispatchedBy(sessionKey) {
|
|
3805
4427
|
try {
|
|
3806
|
-
const data = JSON.parse(
|
|
3807
|
-
|
|
4428
|
+
const data = JSON.parse(readFileSync10(
|
|
4429
|
+
path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3808
4430
|
"utf8"
|
|
3809
4431
|
));
|
|
3810
4432
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3830,10 +4452,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3830
4452
|
}
|
|
3831
4453
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3832
4454
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3833
|
-
if (!isAlive(base) &&
|
|
4455
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3834
4456
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3835
4457
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3836
|
-
if (!isAlive(candidate) &&
|
|
4458
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3837
4459
|
}
|
|
3838
4460
|
return null;
|
|
3839
4461
|
}
|
|
@@ -3865,15 +4487,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3865
4487
|
}
|
|
3866
4488
|
function readDebounceState() {
|
|
3867
4489
|
try {
|
|
3868
|
-
if (!
|
|
3869
|
-
return JSON.parse(
|
|
4490
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
4491
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
3870
4492
|
} catch {
|
|
3871
4493
|
return {};
|
|
3872
4494
|
}
|
|
3873
4495
|
}
|
|
3874
4496
|
function writeDebounceState(state) {
|
|
3875
4497
|
try {
|
|
3876
|
-
if (!
|
|
4498
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
|
|
3877
4499
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3878
4500
|
} catch {
|
|
3879
4501
|
}
|
|
@@ -3993,7 +4615,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3993
4615
|
return true;
|
|
3994
4616
|
}
|
|
3995
4617
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3996
|
-
if (
|
|
4618
|
+
if (isCoordinatorName(employeeName)) {
|
|
3997
4619
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3998
4620
|
}
|
|
3999
4621
|
try {
|
|
@@ -4065,26 +4687,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4065
4687
|
const transport = getTransport();
|
|
4066
4688
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4067
4689
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4068
|
-
const logDir =
|
|
4069
|
-
const logFile =
|
|
4070
|
-
if (!
|
|
4690
|
+
const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
|
|
4691
|
+
const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4692
|
+
if (!existsSync11(logDir)) {
|
|
4071
4693
|
mkdirSync5(logDir, { recursive: true });
|
|
4072
4694
|
}
|
|
4073
4695
|
transport.kill(sessionName);
|
|
4074
4696
|
let cleanupSuffix = "";
|
|
4075
4697
|
try {
|
|
4076
|
-
const thisFile =
|
|
4077
|
-
const cleanupScript =
|
|
4078
|
-
if (
|
|
4698
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4699
|
+
const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4700
|
+
if (existsSync11(cleanupScript)) {
|
|
4079
4701
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4080
4702
|
}
|
|
4081
4703
|
} catch {
|
|
4082
4704
|
}
|
|
4083
4705
|
try {
|
|
4084
|
-
const claudeJsonPath =
|
|
4706
|
+
const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
|
|
4085
4707
|
let claudeJson = {};
|
|
4086
4708
|
try {
|
|
4087
|
-
claudeJson = JSON.parse(
|
|
4709
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
4088
4710
|
} catch {
|
|
4089
4711
|
}
|
|
4090
4712
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -4096,13 +4718,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4096
4718
|
} catch {
|
|
4097
4719
|
}
|
|
4098
4720
|
try {
|
|
4099
|
-
const settingsDir =
|
|
4721
|
+
const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
|
|
4100
4722
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4101
|
-
const projSettingsDir =
|
|
4102
|
-
const settingsPath =
|
|
4723
|
+
const projSettingsDir = path15.join(settingsDir, normalizedKey);
|
|
4724
|
+
const settingsPath = path15.join(projSettingsDir, "settings.json");
|
|
4103
4725
|
let settings = {};
|
|
4104
4726
|
try {
|
|
4105
|
-
settings = JSON.parse(
|
|
4727
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
4106
4728
|
} catch {
|
|
4107
4729
|
}
|
|
4108
4730
|
const perms = settings.permissions ?? {};
|
|
@@ -4143,8 +4765,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4143
4765
|
let behaviorsFlag = "";
|
|
4144
4766
|
let legacyFallbackWarned = false;
|
|
4145
4767
|
if (!useExeAgent && !useBinSymlink) {
|
|
4146
|
-
const identityPath =
|
|
4147
|
-
|
|
4768
|
+
const identityPath = path15.join(
|
|
4769
|
+
os8.homedir(),
|
|
4148
4770
|
".exe-os",
|
|
4149
4771
|
"identity",
|
|
4150
4772
|
`${employeeName}.md`
|
|
@@ -4153,13 +4775,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4153
4775
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4154
4776
|
if (hasAgentFlag) {
|
|
4155
4777
|
identityFlag = ` --agent ${employeeName}`;
|
|
4156
|
-
} else if (
|
|
4778
|
+
} else if (existsSync11(identityPath)) {
|
|
4157
4779
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4158
4780
|
legacyFallbackWarned = true;
|
|
4159
4781
|
}
|
|
4160
4782
|
const behaviorsFile = exportBehaviorsSync(
|
|
4161
4783
|
employeeName,
|
|
4162
|
-
|
|
4784
|
+
path15.basename(spawnCwd),
|
|
4163
4785
|
sessionName
|
|
4164
4786
|
);
|
|
4165
4787
|
if (behaviorsFile) {
|
|
@@ -4174,9 +4796,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4174
4796
|
}
|
|
4175
4797
|
let sessionContextFlag = "";
|
|
4176
4798
|
try {
|
|
4177
|
-
const ctxDir =
|
|
4799
|
+
const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4178
4800
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4179
|
-
const ctxFile =
|
|
4801
|
+
const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4180
4802
|
const ctxContent = [
|
|
4181
4803
|
`## Session Context`,
|
|
4182
4804
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -4215,13 +4837,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4215
4837
|
command: spawnCommand
|
|
4216
4838
|
});
|
|
4217
4839
|
if (spawnResult.error) {
|
|
4218
|
-
|
|
4840
|
+
releaseSpawnLock2(sessionName);
|
|
4219
4841
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4220
4842
|
}
|
|
4221
4843
|
transport.pipeLog(sessionName, logFile);
|
|
4222
4844
|
try {
|
|
4223
4845
|
const mySession = getMySession();
|
|
4224
|
-
const dispatchInfo =
|
|
4846
|
+
const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4225
4847
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
4226
4848
|
dispatchedBy: mySession,
|
|
4227
4849
|
rootExe: exeSession,
|
|
@@ -4253,7 +4875,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4253
4875
|
}
|
|
4254
4876
|
}
|
|
4255
4877
|
if (!booted) {
|
|
4256
|
-
|
|
4878
|
+
releaseSpawnLock2(sessionName);
|
|
4257
4879
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4258
4880
|
}
|
|
4259
4881
|
if (!useExeAgent) {
|
|
@@ -4270,7 +4892,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4270
4892
|
pid: 0,
|
|
4271
4893
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4272
4894
|
});
|
|
4273
|
-
|
|
4895
|
+
releaseSpawnLock2(sessionName);
|
|
4274
4896
|
return { sessionName };
|
|
4275
4897
|
}
|
|
4276
4898
|
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;
|
|
@@ -4286,14 +4908,14 @@ var init_tmux_routing = __esm({
|
|
|
4286
4908
|
init_intercom_queue();
|
|
4287
4909
|
init_plan_limits();
|
|
4288
4910
|
init_employees();
|
|
4289
|
-
SPAWN_LOCK_DIR =
|
|
4290
|
-
SESSION_CACHE =
|
|
4911
|
+
SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
4912
|
+
SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
|
|
4291
4913
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4292
4914
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4293
4915
|
VERIFY_PANE_LINES = 200;
|
|
4294
4916
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4295
|
-
INTERCOM_LOG2 =
|
|
4296
|
-
DEBOUNCE_FILE =
|
|
4917
|
+
INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
4918
|
+
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4297
4919
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4298
4920
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4299
4921
|
}
|
|
@@ -4310,14 +4932,14 @@ var init_memory = __esm({
|
|
|
4310
4932
|
|
|
4311
4933
|
// src/lib/keychain.ts
|
|
4312
4934
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4313
|
-
import { existsSync as
|
|
4314
|
-
import
|
|
4315
|
-
import
|
|
4935
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4936
|
+
import path16 from "path";
|
|
4937
|
+
import os9 from "os";
|
|
4316
4938
|
function getKeyDir() {
|
|
4317
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4939
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
|
|
4318
4940
|
}
|
|
4319
4941
|
function getKeyPath() {
|
|
4320
|
-
return
|
|
4942
|
+
return path16.join(getKeyDir(), "master.key");
|
|
4321
4943
|
}
|
|
4322
4944
|
async function tryKeytar() {
|
|
4323
4945
|
try {
|
|
@@ -4338,13 +4960,21 @@ async function getMasterKey() {
|
|
|
4338
4960
|
}
|
|
4339
4961
|
}
|
|
4340
4962
|
const keyPath = getKeyPath();
|
|
4341
|
-
if (!
|
|
4963
|
+
if (!existsSync12(keyPath)) {
|
|
4964
|
+
process.stderr.write(
|
|
4965
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4966
|
+
`
|
|
4967
|
+
);
|
|
4342
4968
|
return null;
|
|
4343
4969
|
}
|
|
4344
4970
|
try {
|
|
4345
4971
|
const content = await readFile4(keyPath, "utf-8");
|
|
4346
4972
|
return Buffer.from(content.trim(), "base64");
|
|
4347
|
-
} catch {
|
|
4973
|
+
} catch (err) {
|
|
4974
|
+
process.stderr.write(
|
|
4975
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4976
|
+
`
|
|
4977
|
+
);
|
|
4348
4978
|
return null;
|
|
4349
4979
|
}
|
|
4350
4980
|
}
|
|
@@ -4370,12 +5000,12 @@ __export(shard_manager_exports, {
|
|
|
4370
5000
|
listShards: () => listShards,
|
|
4371
5001
|
shardExists: () => shardExists
|
|
4372
5002
|
});
|
|
4373
|
-
import
|
|
4374
|
-
import { existsSync as
|
|
5003
|
+
import path17 from "path";
|
|
5004
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
4375
5005
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4376
5006
|
function initShardManager(encryptionKey) {
|
|
4377
5007
|
_encryptionKey = encryptionKey;
|
|
4378
|
-
if (!
|
|
5008
|
+
if (!existsSync13(SHARDS_DIR)) {
|
|
4379
5009
|
mkdirSync6(SHARDS_DIR, { recursive: true });
|
|
4380
5010
|
}
|
|
4381
5011
|
_shardingEnabled = true;
|
|
@@ -4396,7 +5026,7 @@ function getShardClient(projectName) {
|
|
|
4396
5026
|
}
|
|
4397
5027
|
const cached = _shards.get(safeName);
|
|
4398
5028
|
if (cached) return cached;
|
|
4399
|
-
const dbPath =
|
|
5029
|
+
const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
|
|
4400
5030
|
const client = createClient2({
|
|
4401
5031
|
url: `file:${dbPath}`,
|
|
4402
5032
|
encryptionKey: _encryptionKey
|
|
@@ -4406,10 +5036,10 @@ function getShardClient(projectName) {
|
|
|
4406
5036
|
}
|
|
4407
5037
|
function shardExists(projectName) {
|
|
4408
5038
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4409
|
-
return
|
|
5039
|
+
return existsSync13(path17.join(SHARDS_DIR, `${safeName}.db`));
|
|
4410
5040
|
}
|
|
4411
5041
|
function listShards() {
|
|
4412
|
-
if (!
|
|
5042
|
+
if (!existsSync13(SHARDS_DIR)) return [];
|
|
4413
5043
|
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4414
5044
|
}
|
|
4415
5045
|
async function ensureShardSchema(client) {
|
|
@@ -4595,7 +5225,7 @@ var init_shard_manager = __esm({
|
|
|
4595
5225
|
"src/lib/shard-manager.ts"() {
|
|
4596
5226
|
"use strict";
|
|
4597
5227
|
init_config();
|
|
4598
|
-
SHARDS_DIR =
|
|
5228
|
+
SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
|
|
4599
5229
|
_shards = /* @__PURE__ */ new Map();
|
|
4600
5230
|
_encryptionKey = null;
|
|
4601
5231
|
_shardingEnabled = false;
|
|
@@ -4720,7 +5350,7 @@ __export(global_procedures_exports, {
|
|
|
4720
5350
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4721
5351
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4722
5352
|
});
|
|
4723
|
-
import { randomUUID as
|
|
5353
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4724
5354
|
async function loadGlobalProcedures() {
|
|
4725
5355
|
const client = getClient();
|
|
4726
5356
|
const result = await client.execute({
|
|
@@ -4749,7 +5379,7 @@ ${sections.join("\n\n")}
|
|
|
4749
5379
|
`;
|
|
4750
5380
|
}
|
|
4751
5381
|
async function storeGlobalProcedure(input) {
|
|
4752
|
-
const id =
|
|
5382
|
+
const id = randomUUID4();
|
|
4753
5383
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4754
5384
|
const client = getClient();
|
|
4755
5385
|
await client.execute({
|
|
@@ -4800,6 +5430,7 @@ __export(store_exports, {
|
|
|
4800
5430
|
vectorToBlob: () => vectorToBlob,
|
|
4801
5431
|
writeMemory: () => writeMemory
|
|
4802
5432
|
});
|
|
5433
|
+
import { createHash } from "crypto";
|
|
4803
5434
|
function isBusyError2(err) {
|
|
4804
5435
|
if (err instanceof Error) {
|
|
4805
5436
|
const msg = err.message.toLowerCase();
|
|
@@ -4873,12 +5504,52 @@ function classifyTier(record) {
|
|
|
4873
5504
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4874
5505
|
return 3;
|
|
4875
5506
|
}
|
|
5507
|
+
function inferFilePaths(record) {
|
|
5508
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5509
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5510
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5511
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5512
|
+
}
|
|
5513
|
+
function inferCommitHash(record) {
|
|
5514
|
+
if (record.tool_name !== "Bash") return null;
|
|
5515
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5516
|
+
return match ? match[1] : null;
|
|
5517
|
+
}
|
|
5518
|
+
function inferLanguageType(record) {
|
|
5519
|
+
const text = record.raw_text;
|
|
5520
|
+
if (!text || text.length < 10) return null;
|
|
5521
|
+
const trimmed = text.trimStart();
|
|
5522
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5523
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5524
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5525
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5526
|
+
return "mixed";
|
|
5527
|
+
}
|
|
5528
|
+
function inferDomain(record) {
|
|
5529
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5530
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5531
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5532
|
+
return null;
|
|
5533
|
+
}
|
|
4876
5534
|
async function writeMemory(record) {
|
|
4877
5535
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4878
5536
|
throw new Error(
|
|
4879
5537
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4880
5538
|
);
|
|
4881
5539
|
}
|
|
5540
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5541
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
try {
|
|
5545
|
+
const client = getClient();
|
|
5546
|
+
const existing = await client.execute({
|
|
5547
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5548
|
+
args: [contentHash, record.agent_id]
|
|
5549
|
+
});
|
|
5550
|
+
if (existing.rows.length > 0) return;
|
|
5551
|
+
} catch {
|
|
5552
|
+
}
|
|
4882
5553
|
const dbRow = {
|
|
4883
5554
|
id: record.id,
|
|
4884
5555
|
agent_id: record.agent_id,
|
|
@@ -4908,7 +5579,23 @@ async function writeMemory(record) {
|
|
|
4908
5579
|
supersedes_id: record.supersedes_id ?? null,
|
|
4909
5580
|
draft: record.draft ? 1 : 0,
|
|
4910
5581
|
memory_type: record.memory_type ?? "raw",
|
|
4911
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5582
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5583
|
+
content_hash: contentHash,
|
|
5584
|
+
intent: record.intent ?? null,
|
|
5585
|
+
outcome: record.outcome ?? null,
|
|
5586
|
+
domain: record.domain ?? inferDomain(record),
|
|
5587
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5588
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5589
|
+
chain_position: record.chain_position ?? null,
|
|
5590
|
+
review_status: record.review_status ?? null,
|
|
5591
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5592
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5593
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5594
|
+
duration_ms: record.duration_ms ?? null,
|
|
5595
|
+
token_cost: record.token_cost ?? null,
|
|
5596
|
+
audience: record.audience ?? null,
|
|
5597
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5598
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4912
5599
|
};
|
|
4913
5600
|
_pendingRecords.push(dbRow);
|
|
4914
5601
|
orgBus.emit({
|
|
@@ -4966,80 +5653,85 @@ async function flushBatch() {
|
|
|
4966
5653
|
const draft = row.draft ? 1 : 0;
|
|
4967
5654
|
const memoryType = row.memory_type ?? "raw";
|
|
4968
5655
|
const trajectory = row.trajectory ?? null;
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
5656
|
+
const contentHash = row.content_hash ?? null;
|
|
5657
|
+
const intent = row.intent ?? null;
|
|
5658
|
+
const outcome = row.outcome ?? null;
|
|
5659
|
+
const domain = row.domain ?? null;
|
|
5660
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5661
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5662
|
+
const chainPosition = row.chain_position ?? null;
|
|
5663
|
+
const reviewStatus = row.review_status ?? null;
|
|
5664
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5665
|
+
const filePaths = row.file_paths ?? null;
|
|
5666
|
+
const commitHash = row.commit_hash ?? null;
|
|
5667
|
+
const durationMs = row.duration_ms ?? null;
|
|
5668
|
+
const tokenCost = row.token_cost ?? null;
|
|
5669
|
+
const audience = row.audience ?? null;
|
|
5670
|
+
const languageType = row.language_type ?? null;
|
|
5671
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5672
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4979
5673
|
tool_name, project_name,
|
|
4980
5674
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4981
5675
|
confidence, last_accessed,
|
|
4982
5676
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4983
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
trajectory
|
|
5042
|
-
]
|
|
5677
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5678
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5679
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5680
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5681
|
+
const metaArgs = [
|
|
5682
|
+
intent,
|
|
5683
|
+
outcome,
|
|
5684
|
+
domain,
|
|
5685
|
+
referencedEntities,
|
|
5686
|
+
retrievalCount,
|
|
5687
|
+
chainPosition,
|
|
5688
|
+
reviewStatus,
|
|
5689
|
+
contextWindowPct,
|
|
5690
|
+
filePaths,
|
|
5691
|
+
commitHash,
|
|
5692
|
+
durationMs,
|
|
5693
|
+
tokenCost,
|
|
5694
|
+
audience,
|
|
5695
|
+
languageType,
|
|
5696
|
+
parentMemoryId
|
|
5697
|
+
];
|
|
5698
|
+
const baseArgs = [
|
|
5699
|
+
row.id,
|
|
5700
|
+
row.agent_id,
|
|
5701
|
+
row.agent_role,
|
|
5702
|
+
row.session_id,
|
|
5703
|
+
row.timestamp,
|
|
5704
|
+
row.tool_name,
|
|
5705
|
+
row.project_name,
|
|
5706
|
+
row.has_error,
|
|
5707
|
+
row.raw_text
|
|
5708
|
+
];
|
|
5709
|
+
const sharedArgs = [
|
|
5710
|
+
row.version,
|
|
5711
|
+
taskId,
|
|
5712
|
+
importance,
|
|
5713
|
+
status,
|
|
5714
|
+
confidence,
|
|
5715
|
+
lastAccessed,
|
|
5716
|
+
workspaceId,
|
|
5717
|
+
documentId,
|
|
5718
|
+
userId,
|
|
5719
|
+
charOffset,
|
|
5720
|
+
pageNumber,
|
|
5721
|
+
sourcePath,
|
|
5722
|
+
sourceType,
|
|
5723
|
+
tier,
|
|
5724
|
+
supersedesId,
|
|
5725
|
+
draft,
|
|
5726
|
+
memoryType,
|
|
5727
|
+
trajectory,
|
|
5728
|
+
contentHash
|
|
5729
|
+
];
|
|
5730
|
+
return {
|
|
5731
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5732
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5733
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5734
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
5043
5735
|
};
|
|
5044
5736
|
};
|
|
5045
5737
|
const globalClient = getClient();
|
|
@@ -5322,8 +6014,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
5322
6014
|
try {
|
|
5323
6015
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
5324
6016
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
5325
|
-
const { readFileSync:
|
|
5326
|
-
const source =
|
|
6017
|
+
const { readFileSync: readFileSync11 } = __require("fs");
|
|
6018
|
+
const source = readFileSync11(filePath, "utf8");
|
|
5327
6019
|
const lines = source.split("\n");
|
|
5328
6020
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
5329
6021
|
let matchLine = -1;
|
|
@@ -5389,9 +6081,9 @@ function extractBash(input, response) {
|
|
|
5389
6081
|
}
|
|
5390
6082
|
function extractGrep(input, response) {
|
|
5391
6083
|
const pattern = String(input.pattern ?? "");
|
|
5392
|
-
const
|
|
6084
|
+
const path18 = input.path ? String(input.path) : "";
|
|
5393
6085
|
const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
|
|
5394
|
-
return `Searched for "${pattern}"${
|
|
6086
|
+
return `Searched for "${pattern}"${path18 ? ` in ${path18}` : ""}
|
|
5395
6087
|
${output.slice(0, MAX_OUTPUT)}`;
|
|
5396
6088
|
}
|
|
5397
6089
|
function extractGlob(input, response) {
|
|
@@ -7276,11 +7968,11 @@ function createQuietRenderer() {
|
|
|
7276
7968
|
init_state_bus();
|
|
7277
7969
|
|
|
7278
7970
|
// src/runtime/session-manager.ts
|
|
7279
|
-
import { randomUUID as
|
|
7971
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
7280
7972
|
init_state_bus();
|
|
7281
7973
|
|
|
7282
7974
|
// src/runtime/exe-hooks.ts
|
|
7283
|
-
import { randomUUID as
|
|
7975
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
7284
7976
|
function createExeOSHooks(config) {
|
|
7285
7977
|
let sessionRegistered = false;
|
|
7286
7978
|
return {
|
|
@@ -7339,7 +8031,7 @@ function createExeOSHooks(config) {
|
|
|
7339
8031
|
const toolResponse = result.isError ? { error: result.content } : { output: result.content };
|
|
7340
8032
|
const rawText = extractSemanticText2(toolName, toolInput, toolResponse);
|
|
7341
8033
|
await writeMemory2({
|
|
7342
|
-
id:
|
|
8034
|
+
id: randomUUID5(),
|
|
7343
8035
|
agent_id: config.agentId,
|
|
7344
8036
|
agent_role: "employee",
|
|
7345
8037
|
session_id: `api-${config.agentId}`,
|
|
@@ -7362,7 +8054,7 @@ function createExeOSHooks(config) {
|
|
|
7362
8054
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7363
8055
|
if (summary) {
|
|
7364
8056
|
await writeMemory2({
|
|
7365
|
-
id:
|
|
8057
|
+
id: randomUUID5(),
|
|
7366
8058
|
agent_id: config.agentId,
|
|
7367
8059
|
agent_role: "employee",
|
|
7368
8060
|
session_id: `api-${config.agentId}`,
|
|
@@ -7435,7 +8127,7 @@ function createExeOSHooks(config) {
|
|
|
7435
8127
|
await client.execute({
|
|
7436
8128
|
sql: `INSERT OR IGNORE INTO notifications (id, type, source_agent, message, task_file, created_at, read)
|
|
7437
8129
|
VALUES (?, 'system', ?, ?, NULL, ?, 0)`,
|
|
7438
|
-
args: [
|
|
8130
|
+
args: [randomUUID5(), config.agentId, message.slice(0, 500), (/* @__PURE__ */ new Date()).toISOString()]
|
|
7439
8131
|
});
|
|
7440
8132
|
} catch {
|
|
7441
8133
|
}
|
|
@@ -7451,7 +8143,7 @@ function createExeOSHooks(config) {
|
|
|
7451
8143
|
try {
|
|
7452
8144
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7453
8145
|
await writeMemory2({
|
|
7454
|
-
id:
|
|
8146
|
+
id: randomUUID5(),
|
|
7455
8147
|
agent_id: config.agentId,
|
|
7456
8148
|
agent_role: "employee",
|
|
7457
8149
|
session_id: `api-${config.agentId}`,
|
|
@@ -7473,7 +8165,7 @@ function createExeOSHooks(config) {
|
|
|
7473
8165
|
try {
|
|
7474
8166
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7475
8167
|
await writeMemory2({
|
|
7476
|
-
id:
|
|
8168
|
+
id: randomUUID5(),
|
|
7477
8169
|
agent_id: config.agentId,
|
|
7478
8170
|
agent_role: "employee",
|
|
7479
8171
|
session_id: `api-${config.agentId}`,
|
|
@@ -7512,7 +8204,7 @@ function createExeOSHooks(config) {
|
|
|
7512
8204
|
if (tasks.rows.length > 0) {
|
|
7513
8205
|
const taskList = tasks.rows.map((r) => `- [${String(r.status)}] ${String(r.title)} (${String(r.task_file)})`).join("\n");
|
|
7514
8206
|
await writeMemory2({
|
|
7515
|
-
id:
|
|
8207
|
+
id: randomUUID5(),
|
|
7516
8208
|
agent_id: config.agentId,
|
|
7517
8209
|
agent_role: "employee",
|
|
7518
8210
|
session_id: `api-${config.agentId}`,
|
|
@@ -7538,7 +8230,7 @@ ${taskList}`,
|
|
|
7538
8230
|
try {
|
|
7539
8231
|
const { writeMemory: writeMemory2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
7540
8232
|
await writeMemory2({
|
|
7541
|
-
id:
|
|
8233
|
+
id: randomUUID5(),
|
|
7542
8234
|
agent_id: config.agentId,
|
|
7543
8235
|
agent_role: "employee",
|
|
7544
8236
|
session_id: `api-${config.agentId}`,
|
|
@@ -7565,7 +8257,7 @@ var SessionManager = class {
|
|
|
7565
8257
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
7566
8258
|
/** Start a new agent session for an employee */
|
|
7567
8259
|
startSession(employeeId, config) {
|
|
7568
|
-
const sessionId =
|
|
8260
|
+
const sessionId = randomUUID6();
|
|
7569
8261
|
const abortController = new AbortController();
|
|
7570
8262
|
const session = {
|
|
7571
8263
|
info: {
|