@askexenow/exe-os 0.8.83 → 0.8.86
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 +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- 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 +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- 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 +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- 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 +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- 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 +375 -152
- 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 +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -502,18 +502,69 @@ var init_provider_table = __esm({
|
|
|
502
502
|
}
|
|
503
503
|
});
|
|
504
504
|
|
|
505
|
-
// src/lib/
|
|
506
|
-
|
|
505
|
+
// src/lib/runtime-table.ts
|
|
506
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
507
|
+
var init_runtime_table = __esm({
|
|
508
|
+
"src/lib/runtime-table.ts"() {
|
|
509
|
+
"use strict";
|
|
510
|
+
RUNTIME_TABLE = {
|
|
511
|
+
codex: {
|
|
512
|
+
binary: "codex",
|
|
513
|
+
launchMode: "exec",
|
|
514
|
+
autoApproveFlag: "--full-auto",
|
|
515
|
+
inlineFlag: "--no-alt-screen",
|
|
516
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
517
|
+
defaultModel: "gpt-5.4"
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
DEFAULT_RUNTIME = "claude";
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// src/lib/agent-config.ts
|
|
525
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
507
526
|
import path5 from "path";
|
|
527
|
+
function loadAgentConfig() {
|
|
528
|
+
if (!existsSync4(AGENT_CONFIG_PATH)) return {};
|
|
529
|
+
try {
|
|
530
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
531
|
+
} catch {
|
|
532
|
+
return {};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function getAgentRuntime(agentId) {
|
|
536
|
+
const config = loadAgentConfig();
|
|
537
|
+
const entry = config[agentId];
|
|
538
|
+
if (entry) return entry;
|
|
539
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
540
|
+
}
|
|
541
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
542
|
+
var init_agent_config = __esm({
|
|
543
|
+
"src/lib/agent-config.ts"() {
|
|
544
|
+
"use strict";
|
|
545
|
+
init_config();
|
|
546
|
+
init_runtime_table();
|
|
547
|
+
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
548
|
+
DEFAULT_MODELS = {
|
|
549
|
+
claude: "claude-opus-4",
|
|
550
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
551
|
+
opencode: "minimax-m2.7"
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// src/lib/intercom-queue.ts
|
|
557
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
558
|
+
import path6 from "path";
|
|
508
559
|
import os4 from "os";
|
|
509
560
|
function ensureDir() {
|
|
510
|
-
const dir =
|
|
511
|
-
if (!
|
|
561
|
+
const dir = path6.dirname(QUEUE_PATH);
|
|
562
|
+
if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
|
|
512
563
|
}
|
|
513
564
|
function readQueue() {
|
|
514
565
|
try {
|
|
515
|
-
if (!
|
|
516
|
-
return JSON.parse(
|
|
566
|
+
if (!existsSync5(QUEUE_PATH)) return [];
|
|
567
|
+
return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
|
|
517
568
|
} catch {
|
|
518
569
|
return [];
|
|
519
570
|
}
|
|
@@ -521,7 +572,7 @@ function readQueue() {
|
|
|
521
572
|
function writeQueue(queue) {
|
|
522
573
|
ensureDir();
|
|
523
574
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
524
|
-
|
|
575
|
+
writeFileSync5(tmp, JSON.stringify(queue, null, 2));
|
|
525
576
|
renameSync3(tmp, QUEUE_PATH);
|
|
526
577
|
}
|
|
527
578
|
function queueIntercom(targetSession, reason) {
|
|
@@ -545,9 +596,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
545
596
|
var init_intercom_queue = __esm({
|
|
546
597
|
"src/lib/intercom-queue.ts"() {
|
|
547
598
|
"use strict";
|
|
548
|
-
QUEUE_PATH =
|
|
599
|
+
QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
549
600
|
TTL_MS = 60 * 60 * 1e3;
|
|
550
|
-
INTERCOM_LOG =
|
|
601
|
+
INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
551
602
|
}
|
|
552
603
|
});
|
|
553
604
|
|
|
@@ -606,6 +657,443 @@ var init_db_retry = __esm({
|
|
|
606
657
|
}
|
|
607
658
|
});
|
|
608
659
|
|
|
660
|
+
// src/lib/exe-daemon-client.ts
|
|
661
|
+
import net from "net";
|
|
662
|
+
import { spawn } from "child_process";
|
|
663
|
+
import { randomUUID } from "crypto";
|
|
664
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
|
|
665
|
+
import path7 from "path";
|
|
666
|
+
import { fileURLToPath } from "url";
|
|
667
|
+
function handleData(chunk) {
|
|
668
|
+
_buffer += chunk.toString();
|
|
669
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
670
|
+
_buffer = "";
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
let newlineIdx;
|
|
674
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
675
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
676
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
677
|
+
if (!line) continue;
|
|
678
|
+
try {
|
|
679
|
+
const response = JSON.parse(line);
|
|
680
|
+
const id = response.id;
|
|
681
|
+
if (!id) continue;
|
|
682
|
+
const entry = _pending.get(id);
|
|
683
|
+
if (entry) {
|
|
684
|
+
clearTimeout(entry.timer);
|
|
685
|
+
_pending.delete(id);
|
|
686
|
+
entry.resolve(response);
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function cleanupStaleFiles() {
|
|
693
|
+
if (existsSync6(PID_PATH)) {
|
|
694
|
+
try {
|
|
695
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
696
|
+
if (pid > 0) {
|
|
697
|
+
try {
|
|
698
|
+
process.kill(pid, 0);
|
|
699
|
+
return;
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
unlinkSync3(PID_PATH);
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
unlinkSync3(SOCKET_PATH);
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function findPackageRoot() {
|
|
716
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
717
|
+
const { root } = path7.parse(dir);
|
|
718
|
+
while (dir !== root) {
|
|
719
|
+
if (existsSync6(path7.join(dir, "package.json"))) return dir;
|
|
720
|
+
dir = path7.dirname(dir);
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function spawnDaemon() {
|
|
725
|
+
const pkgRoot = findPackageRoot();
|
|
726
|
+
if (!pkgRoot) {
|
|
727
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
731
|
+
if (!existsSync6(daemonPath)) {
|
|
732
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
733
|
+
`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const resolvedPath = daemonPath;
|
|
737
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
738
|
+
`);
|
|
739
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
740
|
+
let stderrFd = "ignore";
|
|
741
|
+
try {
|
|
742
|
+
stderrFd = openSync(logPath, "a");
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
746
|
+
detached: true,
|
|
747
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
748
|
+
env: {
|
|
749
|
+
...process.env,
|
|
750
|
+
TMUX: void 0,
|
|
751
|
+
// Daemon is global — must not inherit session scope
|
|
752
|
+
TMUX_PANE: void 0,
|
|
753
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
754
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
755
|
+
EXE_DAEMON_PID: PID_PATH
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
child.unref();
|
|
759
|
+
if (typeof stderrFd === "number") {
|
|
760
|
+
try {
|
|
761
|
+
closeSync(stderrFd);
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function acquireSpawnLock() {
|
|
767
|
+
try {
|
|
768
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
769
|
+
closeSync(fd);
|
|
770
|
+
return true;
|
|
771
|
+
} catch {
|
|
772
|
+
try {
|
|
773
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
774
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
775
|
+
try {
|
|
776
|
+
unlinkSync3(SPAWN_LOCK_PATH);
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
780
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
781
|
+
closeSync(fd);
|
|
782
|
+
return true;
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function releaseSpawnLock() {
|
|
792
|
+
try {
|
|
793
|
+
unlinkSync3(SPAWN_LOCK_PATH);
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function connectToSocket() {
|
|
798
|
+
return new Promise((resolve) => {
|
|
799
|
+
if (_socket && _connected) {
|
|
800
|
+
resolve(true);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
804
|
+
const connectTimeout = setTimeout(() => {
|
|
805
|
+
socket.destroy();
|
|
806
|
+
resolve(false);
|
|
807
|
+
}, 2e3);
|
|
808
|
+
socket.on("connect", () => {
|
|
809
|
+
clearTimeout(connectTimeout);
|
|
810
|
+
_socket = socket;
|
|
811
|
+
_connected = true;
|
|
812
|
+
_buffer = "";
|
|
813
|
+
socket.on("data", handleData);
|
|
814
|
+
socket.on("close", () => {
|
|
815
|
+
_connected = false;
|
|
816
|
+
_socket = null;
|
|
817
|
+
for (const [id, entry] of _pending) {
|
|
818
|
+
clearTimeout(entry.timer);
|
|
819
|
+
_pending.delete(id);
|
|
820
|
+
entry.resolve({ error: "Connection closed" });
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
socket.on("error", () => {
|
|
824
|
+
_connected = false;
|
|
825
|
+
_socket = null;
|
|
826
|
+
});
|
|
827
|
+
resolve(true);
|
|
828
|
+
});
|
|
829
|
+
socket.on("error", () => {
|
|
830
|
+
clearTimeout(connectTimeout);
|
|
831
|
+
resolve(false);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
async function connectEmbedDaemon() {
|
|
836
|
+
if (_socket && _connected) return true;
|
|
837
|
+
if (await connectToSocket()) return true;
|
|
838
|
+
if (acquireSpawnLock()) {
|
|
839
|
+
try {
|
|
840
|
+
cleanupStaleFiles();
|
|
841
|
+
spawnDaemon();
|
|
842
|
+
} finally {
|
|
843
|
+
releaseSpawnLock();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
const start = Date.now();
|
|
847
|
+
let delay2 = 100;
|
|
848
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
849
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
850
|
+
if (await connectToSocket()) return true;
|
|
851
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
852
|
+
}
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
856
|
+
return new Promise((resolve) => {
|
|
857
|
+
if (!_socket || !_connected) {
|
|
858
|
+
resolve({ error: "Not connected" });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const id = randomUUID();
|
|
862
|
+
const timer = setTimeout(() => {
|
|
863
|
+
_pending.delete(id);
|
|
864
|
+
resolve({ error: "Request timeout" });
|
|
865
|
+
}, timeoutMs);
|
|
866
|
+
_pending.set(id, { resolve, timer });
|
|
867
|
+
try {
|
|
868
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
869
|
+
} catch {
|
|
870
|
+
clearTimeout(timer);
|
|
871
|
+
_pending.delete(id);
|
|
872
|
+
resolve({ error: "Write failed" });
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
function isClientConnected() {
|
|
877
|
+
return _connected;
|
|
878
|
+
}
|
|
879
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
880
|
+
var init_exe_daemon_client = __esm({
|
|
881
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
init_config();
|
|
884
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
885
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
886
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
887
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
888
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
889
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
890
|
+
_socket = null;
|
|
891
|
+
_connected = false;
|
|
892
|
+
_buffer = "";
|
|
893
|
+
_pending = /* @__PURE__ */ new Map();
|
|
894
|
+
MAX_BUFFER = 1e7;
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// src/lib/daemon-protocol.ts
|
|
899
|
+
function serializeValue(v) {
|
|
900
|
+
if (v === null || v === void 0) return null;
|
|
901
|
+
if (typeof v === "bigint") return Number(v);
|
|
902
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
903
|
+
if (v instanceof Uint8Array) {
|
|
904
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
905
|
+
}
|
|
906
|
+
if (ArrayBuffer.isView(v)) {
|
|
907
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
908
|
+
}
|
|
909
|
+
if (v instanceof ArrayBuffer) {
|
|
910
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
911
|
+
}
|
|
912
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
913
|
+
return String(v);
|
|
914
|
+
}
|
|
915
|
+
function deserializeValue(v) {
|
|
916
|
+
if (v === null) return null;
|
|
917
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
918
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
919
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
920
|
+
}
|
|
921
|
+
return v;
|
|
922
|
+
}
|
|
923
|
+
function deserializeResultSet(srs) {
|
|
924
|
+
const rows = srs.rows.map((obj) => {
|
|
925
|
+
const values = srs.columns.map(
|
|
926
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
927
|
+
);
|
|
928
|
+
const row = values;
|
|
929
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
930
|
+
const col = srs.columns[i];
|
|
931
|
+
if (col !== void 0) {
|
|
932
|
+
row[col] = values[i] ?? null;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
Object.defineProperty(row, "length", {
|
|
936
|
+
value: values.length,
|
|
937
|
+
enumerable: false
|
|
938
|
+
});
|
|
939
|
+
return row;
|
|
940
|
+
});
|
|
941
|
+
return {
|
|
942
|
+
columns: srs.columns,
|
|
943
|
+
columnTypes: srs.columnTypes ?? [],
|
|
944
|
+
rows,
|
|
945
|
+
rowsAffected: srs.rowsAffected,
|
|
946
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
947
|
+
toJSON: () => ({
|
|
948
|
+
columns: srs.columns,
|
|
949
|
+
columnTypes: srs.columnTypes ?? [],
|
|
950
|
+
rows: srs.rows,
|
|
951
|
+
rowsAffected: srs.rowsAffected,
|
|
952
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
953
|
+
})
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
var init_daemon_protocol = __esm({
|
|
957
|
+
"src/lib/daemon-protocol.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// src/lib/db-daemon-client.ts
|
|
963
|
+
var db_daemon_client_exports = {};
|
|
964
|
+
__export(db_daemon_client_exports, {
|
|
965
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
966
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
967
|
+
});
|
|
968
|
+
function normalizeStatement(stmt) {
|
|
969
|
+
if (typeof stmt === "string") {
|
|
970
|
+
return { sql: stmt, args: [] };
|
|
971
|
+
}
|
|
972
|
+
const sql = stmt.sql;
|
|
973
|
+
let args = [];
|
|
974
|
+
if (Array.isArray(stmt.args)) {
|
|
975
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
976
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
977
|
+
const named = {};
|
|
978
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
979
|
+
named[key] = serializeValue(val);
|
|
980
|
+
}
|
|
981
|
+
return { sql, args: named };
|
|
982
|
+
}
|
|
983
|
+
return { sql, args };
|
|
984
|
+
}
|
|
985
|
+
function createDaemonDbClient(fallbackClient) {
|
|
986
|
+
let _useDaemon = false;
|
|
987
|
+
const client = {
|
|
988
|
+
async execute(stmt) {
|
|
989
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
990
|
+
return fallbackClient.execute(stmt);
|
|
991
|
+
}
|
|
992
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
993
|
+
const response = await sendDaemonRequest({
|
|
994
|
+
type: "db-execute",
|
|
995
|
+
sql,
|
|
996
|
+
args
|
|
997
|
+
});
|
|
998
|
+
if (response.error) {
|
|
999
|
+
const errMsg = String(response.error);
|
|
1000
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1001
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
1002
|
+
`);
|
|
1003
|
+
return fallbackClient.execute(stmt);
|
|
1004
|
+
}
|
|
1005
|
+
throw new Error(errMsg);
|
|
1006
|
+
}
|
|
1007
|
+
if (response.db) {
|
|
1008
|
+
return deserializeResultSet(response.db);
|
|
1009
|
+
}
|
|
1010
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
1011
|
+
return fallbackClient.execute(stmt);
|
|
1012
|
+
},
|
|
1013
|
+
async batch(stmts, mode) {
|
|
1014
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
1015
|
+
return fallbackClient.batch(stmts, mode);
|
|
1016
|
+
}
|
|
1017
|
+
const statements = stmts.map(normalizeStatement);
|
|
1018
|
+
const response = await sendDaemonRequest({
|
|
1019
|
+
type: "db-batch",
|
|
1020
|
+
statements,
|
|
1021
|
+
mode: mode ?? "deferred"
|
|
1022
|
+
});
|
|
1023
|
+
if (response.error) {
|
|
1024
|
+
const errMsg = String(response.error);
|
|
1025
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
1026
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
1027
|
+
`);
|
|
1028
|
+
return fallbackClient.batch(stmts, mode);
|
|
1029
|
+
}
|
|
1030
|
+
throw new Error(errMsg);
|
|
1031
|
+
}
|
|
1032
|
+
const batchResults = response["db-batch"];
|
|
1033
|
+
if (batchResults) {
|
|
1034
|
+
return batchResults.map(deserializeResultSet);
|
|
1035
|
+
}
|
|
1036
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
1037
|
+
return fallbackClient.batch(stmts, mode);
|
|
1038
|
+
},
|
|
1039
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
1040
|
+
async transaction(mode) {
|
|
1041
|
+
return fallbackClient.transaction(mode);
|
|
1042
|
+
},
|
|
1043
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
1044
|
+
async executeMultiple(sql) {
|
|
1045
|
+
return fallbackClient.executeMultiple(sql);
|
|
1046
|
+
},
|
|
1047
|
+
// migrate — delegate to fallback
|
|
1048
|
+
async migrate(stmts) {
|
|
1049
|
+
return fallbackClient.migrate(stmts);
|
|
1050
|
+
},
|
|
1051
|
+
// Sync mode — delegate to fallback
|
|
1052
|
+
sync() {
|
|
1053
|
+
return fallbackClient.sync();
|
|
1054
|
+
},
|
|
1055
|
+
close() {
|
|
1056
|
+
_useDaemon = false;
|
|
1057
|
+
},
|
|
1058
|
+
get closed() {
|
|
1059
|
+
return fallbackClient.closed;
|
|
1060
|
+
},
|
|
1061
|
+
get protocol() {
|
|
1062
|
+
return fallbackClient.protocol;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
return {
|
|
1066
|
+
...client,
|
|
1067
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
1068
|
+
_enableDaemon() {
|
|
1069
|
+
_useDaemon = true;
|
|
1070
|
+
},
|
|
1071
|
+
/** Check if daemon routing is active */
|
|
1072
|
+
_isDaemonActive() {
|
|
1073
|
+
return _useDaemon && isClientConnected();
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
1078
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
1079
|
+
const connected = await connectEmbedDaemon();
|
|
1080
|
+
if (!connected) {
|
|
1081
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
1085
|
+
client._enableDaemon();
|
|
1086
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
1087
|
+
return client;
|
|
1088
|
+
}
|
|
1089
|
+
var init_db_daemon_client = __esm({
|
|
1090
|
+
"src/lib/db-daemon-client.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_exe_daemon_client();
|
|
1093
|
+
init_daemon_protocol();
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
609
1097
|
// src/lib/database.ts
|
|
610
1098
|
var database_exports = {};
|
|
611
1099
|
__export(database_exports, {
|
|
@@ -614,6 +1102,7 @@ __export(database_exports, {
|
|
|
614
1102
|
ensureSchema: () => ensureSchema,
|
|
615
1103
|
getClient: () => getClient,
|
|
616
1104
|
getRawClient: () => getRawClient,
|
|
1105
|
+
initDaemonClient: () => initDaemonClient,
|
|
617
1106
|
initDatabase: () => initDatabase,
|
|
618
1107
|
initTurso: () => initTurso,
|
|
619
1108
|
isInitialized: () => isInitialized
|
|
@@ -641,8 +1130,27 @@ function getClient() {
|
|
|
641
1130
|
if (!_resilientClient) {
|
|
642
1131
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
643
1132
|
}
|
|
1133
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1134
|
+
return _resilientClient;
|
|
1135
|
+
}
|
|
1136
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1137
|
+
return _daemonClient;
|
|
1138
|
+
}
|
|
644
1139
|
return _resilientClient;
|
|
645
1140
|
}
|
|
1141
|
+
async function initDaemonClient() {
|
|
1142
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1143
|
+
if (!_resilientClient) return;
|
|
1144
|
+
try {
|
|
1145
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1146
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
process.stderr.write(
|
|
1149
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
1150
|
+
`
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
646
1154
|
function getRawClient() {
|
|
647
1155
|
if (!_client) {
|
|
648
1156
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -1129,6 +1637,12 @@ async function ensureSchema() {
|
|
|
1129
1637
|
} catch {
|
|
1130
1638
|
}
|
|
1131
1639
|
}
|
|
1640
|
+
try {
|
|
1641
|
+
await client.execute(
|
|
1642
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1643
|
+
);
|
|
1644
|
+
} catch {
|
|
1645
|
+
}
|
|
1132
1646
|
await client.executeMultiple(`
|
|
1133
1647
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1134
1648
|
id TEXT PRIMARY KEY,
|
|
@@ -1181,7 +1695,30 @@ async function ensureSchema() {
|
|
|
1181
1695
|
entity_id TEXT NOT NULL,
|
|
1182
1696
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1183
1697
|
);
|
|
1698
|
+
|
|
1699
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1700
|
+
name,
|
|
1701
|
+
content=entities,
|
|
1702
|
+
content_rowid=rowid
|
|
1703
|
+
);
|
|
1704
|
+
|
|
1705
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1706
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1707
|
+
END;
|
|
1708
|
+
|
|
1709
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1710
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1711
|
+
END;
|
|
1712
|
+
|
|
1713
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1714
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1715
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1716
|
+
END;
|
|
1184
1717
|
`);
|
|
1718
|
+
try {
|
|
1719
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1185
1722
|
await client.executeMultiple(`
|
|
1186
1723
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1187
1724
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1362,6 +1899,33 @@ async function ensureSchema() {
|
|
|
1362
1899
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1363
1900
|
ON conversations(channel_id);
|
|
1364
1901
|
`);
|
|
1902
|
+
await client.executeMultiple(`
|
|
1903
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1904
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1905
|
+
agent_id TEXT NOT NULL,
|
|
1906
|
+
session_name TEXT,
|
|
1907
|
+
task_id TEXT,
|
|
1908
|
+
project_name TEXT,
|
|
1909
|
+
started_at TEXT NOT NULL
|
|
1910
|
+
);
|
|
1911
|
+
|
|
1912
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1913
|
+
ON session_agent_map(agent_id);
|
|
1914
|
+
`);
|
|
1915
|
+
try {
|
|
1916
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1917
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1918
|
+
await client.execute({
|
|
1919
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1920
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1921
|
+
FROM memories
|
|
1922
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1923
|
+
GROUP BY session_id, agent_id`,
|
|
1924
|
+
args: []
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
1365
1929
|
try {
|
|
1366
1930
|
await client.execute({
|
|
1367
1931
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1495,15 +2059,41 @@ async function ensureSchema() {
|
|
|
1495
2059
|
});
|
|
1496
2060
|
} catch {
|
|
1497
2061
|
}
|
|
2062
|
+
for (const col of [
|
|
2063
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
2064
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
2065
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
2066
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
2067
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
2068
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
2069
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
2070
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
2071
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
2072
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
2073
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
2074
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
2075
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
2076
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
2077
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
2078
|
+
]) {
|
|
2079
|
+
try {
|
|
2080
|
+
await client.execute(col);
|
|
2081
|
+
} catch {
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1498
2084
|
}
|
|
1499
2085
|
async function disposeDatabase() {
|
|
2086
|
+
if (_daemonClient) {
|
|
2087
|
+
_daemonClient.close();
|
|
2088
|
+
_daemonClient = null;
|
|
2089
|
+
}
|
|
1500
2090
|
if (_client) {
|
|
1501
2091
|
_client.close();
|
|
1502
2092
|
_client = null;
|
|
1503
2093
|
_resilientClient = null;
|
|
1504
2094
|
}
|
|
1505
2095
|
}
|
|
1506
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
2096
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1507
2097
|
var init_database = __esm({
|
|
1508
2098
|
"src/lib/database.ts"() {
|
|
1509
2099
|
"use strict";
|
|
@@ -1511,24 +2101,25 @@ var init_database = __esm({
|
|
|
1511
2101
|
init_employees();
|
|
1512
2102
|
_client = null;
|
|
1513
2103
|
_resilientClient = null;
|
|
2104
|
+
_daemonClient = null;
|
|
1514
2105
|
initTurso = initDatabase;
|
|
1515
2106
|
disposeTurso = disposeDatabase;
|
|
1516
2107
|
}
|
|
1517
2108
|
});
|
|
1518
2109
|
|
|
1519
2110
|
// src/lib/license.ts
|
|
1520
|
-
import { readFileSync as
|
|
1521
|
-
import { randomUUID } from "crypto";
|
|
1522
|
-
import
|
|
2111
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
2112
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2113
|
+
import path8 from "path";
|
|
1523
2114
|
import { jwtVerify, importSPKI } from "jose";
|
|
1524
2115
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
1525
2116
|
var init_license = __esm({
|
|
1526
2117
|
"src/lib/license.ts"() {
|
|
1527
2118
|
"use strict";
|
|
1528
2119
|
init_config();
|
|
1529
|
-
LICENSE_PATH =
|
|
1530
|
-
CACHE_PATH =
|
|
1531
|
-
DEVICE_ID_PATH =
|
|
2120
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
2121
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
2122
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
1532
2123
|
PLAN_LIMITS = {
|
|
1533
2124
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1534
2125
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1540,12 +2131,12 @@ var init_license = __esm({
|
|
|
1540
2131
|
});
|
|
1541
2132
|
|
|
1542
2133
|
// src/lib/plan-limits.ts
|
|
1543
|
-
import { readFileSync as
|
|
1544
|
-
import
|
|
2134
|
+
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
2135
|
+
import path9 from "path";
|
|
1545
2136
|
function getLicenseSync() {
|
|
1546
2137
|
try {
|
|
1547
|
-
if (!
|
|
1548
|
-
const raw = JSON.parse(
|
|
2138
|
+
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
2139
|
+
const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
|
|
1549
2140
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1550
2141
|
const parts = raw.token.split(".");
|
|
1551
2142
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1583,8 +2174,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1583
2174
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
1584
2175
|
let count = 0;
|
|
1585
2176
|
try {
|
|
1586
|
-
if (
|
|
1587
|
-
const raw =
|
|
2177
|
+
if (existsSync8(filePath)) {
|
|
2178
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
1588
2179
|
const employees = JSON.parse(raw);
|
|
1589
2180
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1590
2181
|
}
|
|
@@ -1613,19 +2204,19 @@ var init_plan_limits = __esm({
|
|
|
1613
2204
|
this.name = "PlanLimitError";
|
|
1614
2205
|
}
|
|
1615
2206
|
};
|
|
1616
|
-
CACHE_PATH2 =
|
|
2207
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
1617
2208
|
}
|
|
1618
2209
|
});
|
|
1619
2210
|
|
|
1620
2211
|
// src/lib/notifications.ts
|
|
1621
2212
|
import crypto from "crypto";
|
|
1622
|
-
import
|
|
2213
|
+
import path10 from "path";
|
|
1623
2214
|
import os5 from "os";
|
|
1624
2215
|
import {
|
|
1625
|
-
readFileSync as
|
|
2216
|
+
readFileSync as readFileSync10,
|
|
1626
2217
|
readdirSync as readdirSync2,
|
|
1627
|
-
unlinkSync as
|
|
1628
|
-
existsSync as
|
|
2218
|
+
unlinkSync as unlinkSync4,
|
|
2219
|
+
existsSync as existsSync9,
|
|
1629
2220
|
rmdirSync
|
|
1630
2221
|
} from "fs";
|
|
1631
2222
|
async function writeNotification(notification) {
|
|
@@ -1760,10 +2351,11 @@ var init_state_bus = __esm({
|
|
|
1760
2351
|
|
|
1761
2352
|
// src/lib/tasks-crud.ts
|
|
1762
2353
|
import crypto3 from "crypto";
|
|
1763
|
-
import
|
|
2354
|
+
import path11 from "path";
|
|
2355
|
+
import os6 from "os";
|
|
1764
2356
|
import { execSync as execSync5 } from "child_process";
|
|
1765
2357
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1766
|
-
import { existsSync as
|
|
2358
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
1767
2359
|
async function writeCheckpoint(input2) {
|
|
1768
2360
|
const client = getClient();
|
|
1769
2361
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -1804,6 +2396,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1804
2396
|
function slugify(title) {
|
|
1805
2397
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1806
2398
|
}
|
|
2399
|
+
function buildKeywordIndex() {
|
|
2400
|
+
const idx = /* @__PURE__ */ new Map();
|
|
2401
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
2402
|
+
for (const kw of keywords) {
|
|
2403
|
+
const existing = idx.get(kw) ?? [];
|
|
2404
|
+
existing.push(role);
|
|
2405
|
+
idx.set(kw, existing);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
return idx;
|
|
2409
|
+
}
|
|
2410
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
2411
|
+
const employees = loadEmployeesSync();
|
|
2412
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
2413
|
+
if (!employee) return void 0;
|
|
2414
|
+
const assigneeRole = employee.role;
|
|
2415
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
2416
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
2417
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
2418
|
+
if (text.includes(keyword)) {
|
|
2419
|
+
for (const role of roles) matchedRoles.add(role);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
if (matchedRoles.size === 0) return void 0;
|
|
2423
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
2424
|
+
if (assigneeRole === "COO") return void 0;
|
|
2425
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
2426
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
2427
|
+
}
|
|
1807
2428
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1808
2429
|
const scope = sessionScopeFilter(scopeSession);
|
|
1809
2430
|
let result = await client.execute({
|
|
@@ -1853,7 +2474,14 @@ async function createTaskCore(input2) {
|
|
|
1853
2474
|
const id = crypto3.randomUUID();
|
|
1854
2475
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1855
2476
|
const slug = slugify(input2.title);
|
|
1856
|
-
|
|
2477
|
+
let earlySessionScope = null;
|
|
2478
|
+
try {
|
|
2479
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
2480
|
+
earlySessionScope = resolveExeSession2();
|
|
2481
|
+
} catch {
|
|
2482
|
+
}
|
|
2483
|
+
const scope = earlySessionScope ?? "default";
|
|
2484
|
+
const taskFile = input2.taskFile ?? `tasks/${scope}/${input2.assignedTo}/${slug}.md`;
|
|
1857
2485
|
let blockedById = null;
|
|
1858
2486
|
const initialStatus = input2.blockedBy ? "blocked" : "open";
|
|
1859
2487
|
if (input2.blockedBy) {
|
|
@@ -1893,22 +2521,24 @@ async function createTaskCore(input2) {
|
|
|
1893
2521
|
if (dupCheck.rows.length > 0) {
|
|
1894
2522
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1895
2523
|
}
|
|
2524
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2525
|
+
const laneWarning = checkLaneAffinity(input2.title, input2.context, input2.assignedTo);
|
|
2526
|
+
if (laneWarning) {
|
|
2527
|
+
warning = warning ? `${warning}
|
|
2528
|
+
${laneWarning}` : laneWarning;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
1896
2531
|
if (input2.baseDir) {
|
|
1897
2532
|
try {
|
|
1898
|
-
await mkdir3(
|
|
1899
|
-
await mkdir3(
|
|
2533
|
+
await mkdir3(path11.join(input2.baseDir, "exe", "output"), { recursive: true });
|
|
2534
|
+
await mkdir3(path11.join(input2.baseDir, "exe", "research"), { recursive: true });
|
|
1900
2535
|
await ensureArchitectureDoc(input2.baseDir, input2.projectName);
|
|
1901
2536
|
await ensureGitignoreExe(input2.baseDir);
|
|
1902
2537
|
} catch {
|
|
1903
2538
|
}
|
|
1904
2539
|
}
|
|
1905
2540
|
const complexity = input2.complexity ?? "standard";
|
|
1906
|
-
|
|
1907
|
-
try {
|
|
1908
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1909
|
-
sessionScope = resolveExeSession2();
|
|
1910
|
-
} catch {
|
|
1911
|
-
}
|
|
2541
|
+
const sessionScope = earlySessionScope;
|
|
1912
2542
|
await client.execute({
|
|
1913
2543
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
1914
2544
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1935,6 +2565,43 @@ async function createTaskCore(input2) {
|
|
|
1935
2565
|
now
|
|
1936
2566
|
]
|
|
1937
2567
|
});
|
|
2568
|
+
if (input2.baseDir) {
|
|
2569
|
+
try {
|
|
2570
|
+
const EXE_OS_DIR = path11.join(os6.homedir(), ".exe-os");
|
|
2571
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2572
|
+
const mdDir = path11.dirname(mdPath);
|
|
2573
|
+
if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2574
|
+
const reviewer = input2.reviewer ?? input2.assignedBy;
|
|
2575
|
+
const mdContent = `# ${input2.title}
|
|
2576
|
+
|
|
2577
|
+
**ID:** ${id}
|
|
2578
|
+
**Status:** ${initialStatus}
|
|
2579
|
+
**Priority:** ${input2.priority}
|
|
2580
|
+
**Assigned by:** ${input2.assignedBy}
|
|
2581
|
+
**Assigned to:** ${input2.assignedTo}
|
|
2582
|
+
**Project:** ${input2.projectName}
|
|
2583
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2584
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2585
|
+
**Reviewer:** ${reviewer}
|
|
2586
|
+
|
|
2587
|
+
## Context
|
|
2588
|
+
|
|
2589
|
+
${input2.context}
|
|
2590
|
+
|
|
2591
|
+
## MANDATORY: When done
|
|
2592
|
+
|
|
2593
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2594
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2595
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2596
|
+
`;
|
|
2597
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2598
|
+
} catch (err) {
|
|
2599
|
+
process.stderr.write(
|
|
2600
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
2601
|
+
`
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
1938
2605
|
return {
|
|
1939
2606
|
id,
|
|
1940
2607
|
title: input2.title,
|
|
@@ -2127,7 +2794,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2127
2794
|
return { row, taskFile, now, taskId };
|
|
2128
2795
|
}
|
|
2129
2796
|
}
|
|
2130
|
-
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId
|
|
2797
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || isCoordinatorName(input2.callerAgentId))) {
|
|
2131
2798
|
process.stderr.write(
|
|
2132
2799
|
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
2133
2800
|
`
|
|
@@ -2192,9 +2859,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2192
2859
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2193
2860
|
}
|
|
2194
2861
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2195
|
-
const archPath =
|
|
2862
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2196
2863
|
try {
|
|
2197
|
-
if (
|
|
2864
|
+
if (existsSync10(archPath)) return;
|
|
2198
2865
|
const template = [
|
|
2199
2866
|
`# ${projectName} \u2014 System Architecture`,
|
|
2200
2867
|
"",
|
|
@@ -2227,10 +2894,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2227
2894
|
}
|
|
2228
2895
|
}
|
|
2229
2896
|
async function ensureGitignoreExe(baseDir) {
|
|
2230
|
-
const gitignorePath =
|
|
2897
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2231
2898
|
try {
|
|
2232
|
-
if (
|
|
2233
|
-
const content =
|
|
2899
|
+
if (existsSync10(gitignorePath)) {
|
|
2900
|
+
const content = readFileSync11(gitignorePath, "utf-8");
|
|
2234
2901
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2235
2902
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2236
2903
|
} else {
|
|
@@ -2239,20 +2906,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2239
2906
|
} catch {
|
|
2240
2907
|
}
|
|
2241
2908
|
}
|
|
2242
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2909
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2243
2910
|
var init_tasks_crud = __esm({
|
|
2244
2911
|
"src/lib/tasks-crud.ts"() {
|
|
2245
2912
|
"use strict";
|
|
2246
2913
|
init_database();
|
|
2247
2914
|
init_task_scope();
|
|
2915
|
+
init_employees();
|
|
2916
|
+
LANE_KEYWORDS = {
|
|
2917
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2918
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2919
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2920
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2921
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2922
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2923
|
+
};
|
|
2924
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2248
2925
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2249
2926
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2250
2927
|
}
|
|
2251
2928
|
});
|
|
2252
2929
|
|
|
2253
2930
|
// src/lib/tasks-review.ts
|
|
2254
|
-
import
|
|
2255
|
-
import { existsSync as
|
|
2931
|
+
import path12 from "path";
|
|
2932
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
2256
2933
|
async function countPendingReviews(sessionScope) {
|
|
2257
2934
|
const client = getClient();
|
|
2258
2935
|
if (sessionScope) {
|
|
@@ -2274,7 +2951,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2274
2951
|
const result2 = await client.execute({
|
|
2275
2952
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2276
2953
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2277
|
-
AND
|
|
2954
|
+
AND session_scope = ?`,
|
|
2278
2955
|
args: [sinceIso, sessionScope]
|
|
2279
2956
|
});
|
|
2280
2957
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -2292,7 +2969,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2292
2969
|
const result2 = await client.execute({
|
|
2293
2970
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2294
2971
|
WHERE status = 'needs_review'
|
|
2295
|
-
AND
|
|
2972
|
+
AND session_scope = ?
|
|
2296
2973
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2297
2974
|
args: [sessionScope, limit]
|
|
2298
2975
|
});
|
|
@@ -2413,14 +3090,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2413
3090
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2414
3091
|
const agent = parts[1];
|
|
2415
3092
|
const slug = parts.slice(2).join("-");
|
|
2416
|
-
const
|
|
3093
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2417
3094
|
const result = await client.execute({
|
|
2418
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2419
|
-
args: [now,
|
|
3095
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
3096
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2420
3097
|
});
|
|
2421
3098
|
if (result.rowsAffected > 0) {
|
|
2422
3099
|
process.stderr.write(
|
|
2423
|
-
`[review-cleanup] Cascaded original task to done
|
|
3100
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2424
3101
|
`
|
|
2425
3102
|
);
|
|
2426
3103
|
}
|
|
@@ -2433,11 +3110,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2433
3110
|
);
|
|
2434
3111
|
}
|
|
2435
3112
|
try {
|
|
2436
|
-
const cacheDir =
|
|
2437
|
-
if (
|
|
3113
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
3114
|
+
if (existsSync11(cacheDir)) {
|
|
2438
3115
|
for (const f of readdirSync3(cacheDir)) {
|
|
2439
3116
|
if (f.startsWith("review-notified-")) {
|
|
2440
|
-
|
|
3117
|
+
unlinkSync5(path12.join(cacheDir, f));
|
|
2441
3118
|
}
|
|
2442
3119
|
}
|
|
2443
3120
|
}
|
|
@@ -2458,7 +3135,7 @@ var init_tasks_review = __esm({
|
|
|
2458
3135
|
});
|
|
2459
3136
|
|
|
2460
3137
|
// src/lib/tasks-chain.ts
|
|
2461
|
-
import
|
|
3138
|
+
import path13 from "path";
|
|
2462
3139
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2463
3140
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2464
3141
|
const client = getClient();
|
|
@@ -2475,7 +3152,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2475
3152
|
});
|
|
2476
3153
|
for (const ur of unblockedRows.rows) {
|
|
2477
3154
|
try {
|
|
2478
|
-
const ubFile =
|
|
3155
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
2479
3156
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2480
3157
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2481
3158
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2544,7 +3221,7 @@ var init_tasks_chain = __esm({
|
|
|
2544
3221
|
|
|
2545
3222
|
// src/lib/project-name.ts
|
|
2546
3223
|
import { execSync as execSync6 } from "child_process";
|
|
2547
|
-
import
|
|
3224
|
+
import path14 from "path";
|
|
2548
3225
|
function getProjectName(cwd) {
|
|
2549
3226
|
const dir = cwd ?? process.cwd();
|
|
2550
3227
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2557,7 +3234,7 @@ function getProjectName(cwd) {
|
|
|
2557
3234
|
timeout: 2e3,
|
|
2558
3235
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2559
3236
|
}).trim();
|
|
2560
|
-
repoRoot =
|
|
3237
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
2561
3238
|
} catch {
|
|
2562
3239
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
2563
3240
|
cwd: dir,
|
|
@@ -2566,11 +3243,11 @@ function getProjectName(cwd) {
|
|
|
2566
3243
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2567
3244
|
}).trim();
|
|
2568
3245
|
}
|
|
2569
|
-
_cached2 =
|
|
3246
|
+
_cached2 = path14.basename(repoRoot);
|
|
2570
3247
|
_cachedCwd = dir;
|
|
2571
3248
|
return _cached2;
|
|
2572
3249
|
} catch {
|
|
2573
|
-
_cached2 =
|
|
3250
|
+
_cached2 = path14.basename(dir);
|
|
2574
3251
|
_cachedCwd = dir;
|
|
2575
3252
|
return _cached2;
|
|
2576
3253
|
}
|
|
@@ -2602,7 +3279,7 @@ function findSessionForProject(projectName) {
|
|
|
2602
3279
|
const sessions = listSessions();
|
|
2603
3280
|
for (const s of sessions) {
|
|
2604
3281
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2605
|
-
if (proj === projectName &&
|
|
3282
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2606
3283
|
}
|
|
2607
3284
|
return null;
|
|
2608
3285
|
}
|
|
@@ -2648,7 +3325,7 @@ var init_session_scope = __esm({
|
|
|
2648
3325
|
|
|
2649
3326
|
// src/lib/tasks-notify.ts
|
|
2650
3327
|
async function dispatchTaskToEmployee(input2) {
|
|
2651
|
-
if (
|
|
3328
|
+
if (isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
2652
3329
|
let crossProject = false;
|
|
2653
3330
|
if (input2.projectName) {
|
|
2654
3331
|
try {
|
|
@@ -3043,8 +3720,8 @@ __export(tasks_exports, {
|
|
|
3043
3720
|
updateTaskStatus: () => updateTaskStatus,
|
|
3044
3721
|
writeCheckpoint: () => writeCheckpoint
|
|
3045
3722
|
});
|
|
3046
|
-
import
|
|
3047
|
-
import { writeFileSync as
|
|
3723
|
+
import path15 from "path";
|
|
3724
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
|
|
3048
3725
|
async function createTask(input2) {
|
|
3049
3726
|
const result = await createTaskCore(input2);
|
|
3050
3727
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -3063,14 +3740,14 @@ async function updateTask(input2) {
|
|
|
3063
3740
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
|
|
3064
3741
|
try {
|
|
3065
3742
|
const agent = String(row.assigned_to);
|
|
3066
|
-
const cacheDir =
|
|
3067
|
-
const cachePath =
|
|
3743
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
3744
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
3068
3745
|
if (input2.status === "in_progress") {
|
|
3069
|
-
|
|
3070
|
-
|
|
3746
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
3747
|
+
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3071
3748
|
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
3072
3749
|
try {
|
|
3073
|
-
|
|
3750
|
+
unlinkSync6(cachePath);
|
|
3074
3751
|
} catch {
|
|
3075
3752
|
}
|
|
3076
3753
|
}
|
|
@@ -3127,7 +3804,7 @@ async function updateTask(input2) {
|
|
|
3127
3804
|
}
|
|
3128
3805
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
3129
3806
|
if (isTerminal) {
|
|
3130
|
-
const isCoordinator =
|
|
3807
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3131
3808
|
if (!isCoordinator) {
|
|
3132
3809
|
notifyTaskDone();
|
|
3133
3810
|
}
|
|
@@ -3152,7 +3829,7 @@ async function updateTask(input2) {
|
|
|
3152
3829
|
}
|
|
3153
3830
|
}
|
|
3154
3831
|
}
|
|
3155
|
-
if (input2.status === "done" &&
|
|
3832
|
+
if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3156
3833
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3157
3834
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3158
3835
|
taskId,
|
|
@@ -3168,7 +3845,7 @@ async function updateTask(input2) {
|
|
|
3168
3845
|
});
|
|
3169
3846
|
}
|
|
3170
3847
|
let nextTask;
|
|
3171
|
-
if (isTerminal &&
|
|
3848
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3172
3849
|
try {
|
|
3173
3850
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3174
3851
|
} catch {
|
|
@@ -3512,7 +4189,7 @@ var init_capacity_monitor = __esm({
|
|
|
3512
4189
|
// src/lib/tmux-routing.ts
|
|
3513
4190
|
var tmux_routing_exports = {};
|
|
3514
4191
|
__export(tmux_routing_exports, {
|
|
3515
|
-
acquireSpawnLock: () =>
|
|
4192
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
3516
4193
|
employeeSessionName: () => employeeSessionName,
|
|
3517
4194
|
ensureEmployee: () => ensureEmployee,
|
|
3518
4195
|
extractRootExe: () => extractRootExe,
|
|
@@ -3527,20 +4204,20 @@ __export(tmux_routing_exports, {
|
|
|
3527
4204
|
notifyParentExe: () => notifyParentExe,
|
|
3528
4205
|
parseParentExe: () => parseParentExe,
|
|
3529
4206
|
registerParentExe: () => registerParentExe,
|
|
3530
|
-
releaseSpawnLock: () =>
|
|
4207
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
3531
4208
|
resolveExeSession: () => resolveExeSession,
|
|
3532
4209
|
sendIntercom: () => sendIntercom,
|
|
3533
4210
|
spawnEmployee: () => spawnEmployee,
|
|
3534
4211
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
3535
4212
|
});
|
|
3536
4213
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
3537
|
-
import { readFileSync as
|
|
3538
|
-
import
|
|
3539
|
-
import
|
|
3540
|
-
import { fileURLToPath } from "url";
|
|
3541
|
-
import { unlinkSync as
|
|
4214
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, appendFileSync } from "fs";
|
|
4215
|
+
import path16 from "path";
|
|
4216
|
+
import os7 from "os";
|
|
4217
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4218
|
+
import { unlinkSync as unlinkSync7 } from "fs";
|
|
3542
4219
|
function spawnLockPath(sessionName) {
|
|
3543
|
-
return
|
|
4220
|
+
return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
3544
4221
|
}
|
|
3545
4222
|
function isProcessAlive(pid) {
|
|
3546
4223
|
try {
|
|
@@ -3550,14 +4227,14 @@ function isProcessAlive(pid) {
|
|
|
3550
4227
|
return false;
|
|
3551
4228
|
}
|
|
3552
4229
|
}
|
|
3553
|
-
function
|
|
3554
|
-
if (!
|
|
3555
|
-
|
|
4230
|
+
function acquireSpawnLock2(sessionName) {
|
|
4231
|
+
if (!existsSync12(SPAWN_LOCK_DIR)) {
|
|
4232
|
+
mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
|
|
3556
4233
|
}
|
|
3557
4234
|
const lockFile = spawnLockPath(sessionName);
|
|
3558
|
-
if (
|
|
4235
|
+
if (existsSync12(lockFile)) {
|
|
3559
4236
|
try {
|
|
3560
|
-
const lock = JSON.parse(
|
|
4237
|
+
const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
|
|
3561
4238
|
const age = Date.now() - lock.timestamp;
|
|
3562
4239
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
3563
4240
|
return false;
|
|
@@ -3565,25 +4242,25 @@ function acquireSpawnLock(sessionName) {
|
|
|
3565
4242
|
} catch {
|
|
3566
4243
|
}
|
|
3567
4244
|
}
|
|
3568
|
-
|
|
4245
|
+
writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
3569
4246
|
return true;
|
|
3570
4247
|
}
|
|
3571
|
-
function
|
|
4248
|
+
function releaseSpawnLock2(sessionName) {
|
|
3572
4249
|
try {
|
|
3573
|
-
|
|
4250
|
+
unlinkSync7(spawnLockPath(sessionName));
|
|
3574
4251
|
} catch {
|
|
3575
4252
|
}
|
|
3576
4253
|
}
|
|
3577
4254
|
function resolveBehaviorsExporterScript() {
|
|
3578
4255
|
try {
|
|
3579
|
-
const thisFile =
|
|
3580
|
-
const scriptPath =
|
|
3581
|
-
|
|
4256
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4257
|
+
const scriptPath = path16.join(
|
|
4258
|
+
path16.dirname(thisFile),
|
|
3582
4259
|
"..",
|
|
3583
4260
|
"bin",
|
|
3584
4261
|
"exe-export-behaviors.js"
|
|
3585
4262
|
);
|
|
3586
|
-
return
|
|
4263
|
+
return existsSync12(scriptPath) ? scriptPath : null;
|
|
3587
4264
|
} catch {
|
|
3588
4265
|
return null;
|
|
3589
4266
|
}
|
|
@@ -3649,12 +4326,12 @@ function extractRootExe(name) {
|
|
|
3649
4326
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3650
4327
|
}
|
|
3651
4328
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3652
|
-
if (!
|
|
3653
|
-
|
|
4329
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
4330
|
+
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
3654
4331
|
}
|
|
3655
4332
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3656
|
-
const filePath =
|
|
3657
|
-
|
|
4333
|
+
const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4334
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
3658
4335
|
parentExe: rootExe,
|
|
3659
4336
|
dispatchedBy: dispatchedBy || rootExe,
|
|
3660
4337
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3662,7 +4339,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
3662
4339
|
}
|
|
3663
4340
|
function getParentExe(sessionKey) {
|
|
3664
4341
|
try {
|
|
3665
|
-
const data = JSON.parse(
|
|
4342
|
+
const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
3666
4343
|
return data.parentExe || null;
|
|
3667
4344
|
} catch {
|
|
3668
4345
|
return null;
|
|
@@ -3670,8 +4347,8 @@ function getParentExe(sessionKey) {
|
|
|
3670
4347
|
}
|
|
3671
4348
|
function getDispatchedBy(sessionKey) {
|
|
3672
4349
|
try {
|
|
3673
|
-
const data = JSON.parse(
|
|
3674
|
-
|
|
4350
|
+
const data = JSON.parse(readFileSync12(
|
|
4351
|
+
path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
3675
4352
|
"utf8"
|
|
3676
4353
|
));
|
|
3677
4354
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -3697,10 +4374,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
3697
4374
|
}
|
|
3698
4375
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
3699
4376
|
const base = employeeSessionName(employeeName, exeSession);
|
|
3700
|
-
if (!isAlive(base) &&
|
|
4377
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
3701
4378
|
for (let i = 2; i <= maxInstances; i++) {
|
|
3702
4379
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
3703
|
-
if (!isAlive(candidate) &&
|
|
4380
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
3704
4381
|
}
|
|
3705
4382
|
return null;
|
|
3706
4383
|
}
|
|
@@ -3732,32 +4409,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
3732
4409
|
}
|
|
3733
4410
|
function readDebounceState() {
|
|
3734
4411
|
try {
|
|
3735
|
-
if (!
|
|
3736
|
-
|
|
4412
|
+
if (!existsSync12(DEBOUNCE_FILE)) return {};
|
|
4413
|
+
const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
|
|
4414
|
+
const state = {};
|
|
4415
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
4416
|
+
if (typeof val === "number") {
|
|
4417
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
4418
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
4419
|
+
state[key] = val;
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
return state;
|
|
3737
4423
|
} catch {
|
|
3738
4424
|
return {};
|
|
3739
4425
|
}
|
|
3740
4426
|
}
|
|
3741
4427
|
function writeDebounceState(state) {
|
|
3742
4428
|
try {
|
|
3743
|
-
if (!
|
|
3744
|
-
|
|
4429
|
+
if (!existsSync12(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
4430
|
+
writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
|
|
3745
4431
|
} catch {
|
|
3746
4432
|
}
|
|
3747
4433
|
}
|
|
3748
4434
|
function isDebounced(targetSession) {
|
|
3749
4435
|
const state = readDebounceState();
|
|
3750
|
-
const
|
|
3751
|
-
|
|
4436
|
+
const entry = state[targetSession];
|
|
4437
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
4438
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
4439
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
4440
|
+
state[targetSession].pending++;
|
|
4441
|
+
writeDebounceState(state);
|
|
4442
|
+
return true;
|
|
4443
|
+
}
|
|
4444
|
+
return false;
|
|
3752
4445
|
}
|
|
3753
4446
|
function recordDebounce(targetSession) {
|
|
3754
4447
|
const state = readDebounceState();
|
|
3755
|
-
state[targetSession]
|
|
4448
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
4449
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
3756
4450
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
3757
4451
|
for (const key of Object.keys(state)) {
|
|
3758
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4452
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
3759
4453
|
}
|
|
3760
4454
|
writeDebounceState(state);
|
|
4455
|
+
return batched;
|
|
3761
4456
|
}
|
|
3762
4457
|
function logIntercom(msg) {
|
|
3763
4458
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -3802,7 +4497,7 @@ function sendIntercom(targetSession) {
|
|
|
3802
4497
|
return "skipped_exe";
|
|
3803
4498
|
}
|
|
3804
4499
|
if (isDebounced(targetSession)) {
|
|
3805
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
4500
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
3806
4501
|
return "debounced";
|
|
3807
4502
|
}
|
|
3808
4503
|
try {
|
|
@@ -3814,14 +4509,14 @@ function sendIntercom(targetSession) {
|
|
|
3814
4509
|
const sessionState = getSessionState(targetSession);
|
|
3815
4510
|
if (sessionState === "no_claude") {
|
|
3816
4511
|
queueIntercom(targetSession, "claude not running in session");
|
|
3817
|
-
recordDebounce(targetSession);
|
|
3818
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
4512
|
+
const batched2 = recordDebounce(targetSession);
|
|
4513
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3819
4514
|
return "queued";
|
|
3820
4515
|
}
|
|
3821
4516
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3822
4517
|
queueIntercom(targetSession, "session busy at send time");
|
|
3823
|
-
recordDebounce(targetSession);
|
|
3824
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
4518
|
+
const batched2 = recordDebounce(targetSession);
|
|
4519
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
3825
4520
|
return "queued";
|
|
3826
4521
|
}
|
|
3827
4522
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3829,8 +4524,8 @@ function sendIntercom(targetSession) {
|
|
|
3829
4524
|
transport.sendKeys(targetSession, "q");
|
|
3830
4525
|
}
|
|
3831
4526
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3832
|
-
recordDebounce(targetSession);
|
|
3833
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4527
|
+
const batched = recordDebounce(targetSession);
|
|
4528
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
3834
4529
|
return "delivered";
|
|
3835
4530
|
} catch {
|
|
3836
4531
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3860,7 +4555,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3860
4555
|
return true;
|
|
3861
4556
|
}
|
|
3862
4557
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3863
|
-
if (
|
|
4558
|
+
if (isCoordinatorName(employeeName)) {
|
|
3864
4559
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3865
4560
|
}
|
|
3866
4561
|
try {
|
|
@@ -3932,26 +4627,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3932
4627
|
const transport = getTransport();
|
|
3933
4628
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3934
4629
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3935
|
-
const logDir =
|
|
3936
|
-
const logFile =
|
|
3937
|
-
if (!
|
|
3938
|
-
|
|
4630
|
+
const logDir = path16.join(os7.homedir(), ".exe-os", "session-logs");
|
|
4631
|
+
const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4632
|
+
if (!existsSync12(logDir)) {
|
|
4633
|
+
mkdirSync7(logDir, { recursive: true });
|
|
3939
4634
|
}
|
|
3940
4635
|
transport.kill(sessionName);
|
|
3941
4636
|
let cleanupSuffix = "";
|
|
3942
4637
|
try {
|
|
3943
|
-
const thisFile =
|
|
3944
|
-
const cleanupScript =
|
|
3945
|
-
if (
|
|
4638
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4639
|
+
const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4640
|
+
if (existsSync12(cleanupScript)) {
|
|
3946
4641
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3947
4642
|
}
|
|
3948
4643
|
} catch {
|
|
3949
4644
|
}
|
|
3950
4645
|
try {
|
|
3951
|
-
const claudeJsonPath =
|
|
4646
|
+
const claudeJsonPath = path16.join(os7.homedir(), ".claude.json");
|
|
3952
4647
|
let claudeJson = {};
|
|
3953
4648
|
try {
|
|
3954
|
-
claudeJson = JSON.parse(
|
|
4649
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
3955
4650
|
} catch {
|
|
3956
4651
|
}
|
|
3957
4652
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3959,17 +4654,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3959
4654
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3960
4655
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3961
4656
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3962
|
-
|
|
4657
|
+
writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3963
4658
|
} catch {
|
|
3964
4659
|
}
|
|
3965
4660
|
try {
|
|
3966
|
-
const settingsDir =
|
|
4661
|
+
const settingsDir = path16.join(os7.homedir(), ".claude", "projects");
|
|
3967
4662
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3968
|
-
const projSettingsDir =
|
|
3969
|
-
const settingsPath =
|
|
4663
|
+
const projSettingsDir = path16.join(settingsDir, normalizedKey);
|
|
4664
|
+
const settingsPath = path16.join(projSettingsDir, "settings.json");
|
|
3970
4665
|
let settings = {};
|
|
3971
4666
|
try {
|
|
3972
|
-
settings = JSON.parse(
|
|
4667
|
+
settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
|
|
3973
4668
|
} catch {
|
|
3974
4669
|
}
|
|
3975
4670
|
const perms = settings.permissions ?? {};
|
|
@@ -3997,21 +4692,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3997
4692
|
if (changed) {
|
|
3998
4693
|
perms.allow = allow;
|
|
3999
4694
|
settings.permissions = perms;
|
|
4000
|
-
|
|
4001
|
-
|
|
4695
|
+
mkdirSync7(projSettingsDir, { recursive: true });
|
|
4696
|
+
writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4002
4697
|
}
|
|
4003
4698
|
} catch {
|
|
4004
4699
|
}
|
|
4005
4700
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4006
4701
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4007
|
-
const
|
|
4702
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
4703
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
4704
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
4705
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4008
4706
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4009
4707
|
let identityFlag = "";
|
|
4010
4708
|
let behaviorsFlag = "";
|
|
4011
4709
|
let legacyFallbackWarned = false;
|
|
4012
4710
|
if (!useExeAgent && !useBinSymlink) {
|
|
4013
|
-
const identityPath =
|
|
4014
|
-
|
|
4711
|
+
const identityPath = path16.join(
|
|
4712
|
+
os7.homedir(),
|
|
4015
4713
|
".exe-os",
|
|
4016
4714
|
"identity",
|
|
4017
4715
|
`${employeeName}.md`
|
|
@@ -4020,13 +4718,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4020
4718
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4021
4719
|
if (hasAgentFlag) {
|
|
4022
4720
|
identityFlag = ` --agent ${employeeName}`;
|
|
4023
|
-
} else if (
|
|
4721
|
+
} else if (existsSync12(identityPath)) {
|
|
4024
4722
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
4025
4723
|
legacyFallbackWarned = true;
|
|
4026
4724
|
}
|
|
4027
4725
|
const behaviorsFile = exportBehaviorsSync(
|
|
4028
4726
|
employeeName,
|
|
4029
|
-
|
|
4727
|
+
path16.basename(spawnCwd),
|
|
4030
4728
|
sessionName
|
|
4031
4729
|
);
|
|
4032
4730
|
if (behaviorsFile) {
|
|
@@ -4041,16 +4739,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4041
4739
|
}
|
|
4042
4740
|
let sessionContextFlag = "";
|
|
4043
4741
|
try {
|
|
4044
|
-
const ctxDir =
|
|
4045
|
-
|
|
4046
|
-
const ctxFile =
|
|
4742
|
+
const ctxDir = path16.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4743
|
+
mkdirSync7(ctxDir, { recursive: true });
|
|
4744
|
+
const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4047
4745
|
const ctxContent = [
|
|
4048
4746
|
`## Session Context`,
|
|
4049
4747
|
`You are running in tmux session: ${sessionName}.`,
|
|
4050
4748
|
`Your parent coordinator session is ${exeSession}.`,
|
|
4051
4749
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4052
4750
|
].join("\n");
|
|
4053
|
-
|
|
4751
|
+
writeFileSync8(ctxFile, ctxContent);
|
|
4054
4752
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4055
4753
|
} catch {
|
|
4056
4754
|
}
|
|
@@ -4064,9 +4762,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4064
4762
|
}
|
|
4065
4763
|
}
|
|
4066
4764
|
}
|
|
4765
|
+
if (useCodex) {
|
|
4766
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
4767
|
+
if (codexCfg?.apiKeyEnv) {
|
|
4768
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
4769
|
+
if (keyVal) {
|
|
4770
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
4774
|
+
}
|
|
4775
|
+
if (useOpencode) {
|
|
4776
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
4777
|
+
if (ocCfg?.apiKeyEnv) {
|
|
4778
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
4779
|
+
if (keyVal) {
|
|
4780
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4784
|
+
}
|
|
4785
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
4786
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
4787
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
4788
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4067
4791
|
let spawnCommand;
|
|
4068
4792
|
if (useExeAgent) {
|
|
4069
4793
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4794
|
+
} else if (useCodex) {
|
|
4795
|
+
process.stderr.write(
|
|
4796
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
4797
|
+
`
|
|
4798
|
+
);
|
|
4799
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
4800
|
+
} else if (useOpencode) {
|
|
4801
|
+
const binName = `${employeeName}-opencode`;
|
|
4802
|
+
process.stderr.write(
|
|
4803
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
4804
|
+
`
|
|
4805
|
+
);
|
|
4806
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4070
4807
|
} else if (useBinSymlink) {
|
|
4071
4808
|
const binName = `${employeeName}-${ccProvider}`;
|
|
4072
4809
|
process.stderr.write(
|
|
@@ -4082,17 +4819,19 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4082
4819
|
command: spawnCommand
|
|
4083
4820
|
});
|
|
4084
4821
|
if (spawnResult.error) {
|
|
4085
|
-
|
|
4822
|
+
releaseSpawnLock2(sessionName);
|
|
4086
4823
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4087
4824
|
}
|
|
4088
4825
|
transport.pipeLog(sessionName, logFile);
|
|
4089
4826
|
try {
|
|
4090
4827
|
const mySession = getMySession();
|
|
4091
|
-
const dispatchInfo =
|
|
4092
|
-
|
|
4828
|
+
const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4829
|
+
writeFileSync8(dispatchInfo, JSON.stringify({
|
|
4093
4830
|
dispatchedBy: mySession,
|
|
4094
4831
|
rootExe: exeSession,
|
|
4095
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4832
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
4833
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
4834
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
4096
4835
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4097
4836
|
}));
|
|
4098
4837
|
} catch {
|
|
@@ -4110,6 +4849,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4110
4849
|
booted = true;
|
|
4111
4850
|
break;
|
|
4112
4851
|
}
|
|
4852
|
+
} else if (useCodex) {
|
|
4853
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
4854
|
+
booted = true;
|
|
4855
|
+
break;
|
|
4856
|
+
}
|
|
4113
4857
|
} else {
|
|
4114
4858
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4115
4859
|
booted = true;
|
|
@@ -4120,10 +4864,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4120
4864
|
}
|
|
4121
4865
|
}
|
|
4122
4866
|
if (!booted) {
|
|
4123
|
-
|
|
4124
|
-
|
|
4867
|
+
releaseSpawnLock2(sessionName);
|
|
4868
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
4869
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
4125
4870
|
}
|
|
4126
|
-
if (!useExeAgent) {
|
|
4871
|
+
if (!useExeAgent && !useCodex) {
|
|
4127
4872
|
try {
|
|
4128
4873
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
4129
4874
|
} catch {
|
|
@@ -4137,7 +4882,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4137
4882
|
pid: 0,
|
|
4138
4883
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4139
4884
|
});
|
|
4140
|
-
|
|
4885
|
+
releaseSpawnLock2(sessionName);
|
|
4141
4886
|
return { sessionName };
|
|
4142
4887
|
}
|
|
4143
4888
|
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;
|
|
@@ -4150,17 +4895,19 @@ var init_tmux_routing = __esm({
|
|
|
4150
4895
|
init_cc_agent_support();
|
|
4151
4896
|
init_mcp_prefix();
|
|
4152
4897
|
init_provider_table();
|
|
4898
|
+
init_agent_config();
|
|
4899
|
+
init_runtime_table();
|
|
4153
4900
|
init_intercom_queue();
|
|
4154
4901
|
init_plan_limits();
|
|
4155
4902
|
init_employees();
|
|
4156
|
-
SPAWN_LOCK_DIR =
|
|
4157
|
-
SESSION_CACHE =
|
|
4903
|
+
SPAWN_LOCK_DIR = path16.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4904
|
+
SESSION_CACHE = path16.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4158
4905
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4159
4906
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4160
4907
|
VERIFY_PANE_LINES = 200;
|
|
4161
4908
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4162
|
-
INTERCOM_LOG2 =
|
|
4163
|
-
DEBOUNCE_FILE =
|
|
4909
|
+
INTERCOM_LOG2 = path16.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4910
|
+
DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4164
4911
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4165
4912
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4166
4913
|
}
|
|
@@ -4201,14 +4948,14 @@ var init_memory = __esm({
|
|
|
4201
4948
|
|
|
4202
4949
|
// src/lib/keychain.ts
|
|
4203
4950
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import
|
|
4206
|
-
import
|
|
4951
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4952
|
+
import path17 from "path";
|
|
4953
|
+
import os8 from "os";
|
|
4207
4954
|
function getKeyDir() {
|
|
4208
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4955
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os8.homedir(), ".exe-os");
|
|
4209
4956
|
}
|
|
4210
4957
|
function getKeyPath() {
|
|
4211
|
-
return
|
|
4958
|
+
return path17.join(getKeyDir(), "master.key");
|
|
4212
4959
|
}
|
|
4213
4960
|
async function tryKeytar() {
|
|
4214
4961
|
try {
|
|
@@ -4229,13 +4976,21 @@ async function getMasterKey() {
|
|
|
4229
4976
|
}
|
|
4230
4977
|
}
|
|
4231
4978
|
const keyPath = getKeyPath();
|
|
4232
|
-
if (!
|
|
4979
|
+
if (!existsSync13(keyPath)) {
|
|
4980
|
+
process.stderr.write(
|
|
4981
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4982
|
+
`
|
|
4983
|
+
);
|
|
4233
4984
|
return null;
|
|
4234
4985
|
}
|
|
4235
4986
|
try {
|
|
4236
4987
|
const content = await readFile4(keyPath, "utf-8");
|
|
4237
4988
|
return Buffer.from(content.trim(), "base64");
|
|
4238
|
-
} catch {
|
|
4989
|
+
} catch (err) {
|
|
4990
|
+
process.stderr.write(
|
|
4991
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4992
|
+
`
|
|
4993
|
+
);
|
|
4239
4994
|
return null;
|
|
4240
4995
|
}
|
|
4241
4996
|
}
|
|
@@ -4261,13 +5016,13 @@ __export(shard_manager_exports, {
|
|
|
4261
5016
|
listShards: () => listShards,
|
|
4262
5017
|
shardExists: () => shardExists
|
|
4263
5018
|
});
|
|
4264
|
-
import
|
|
4265
|
-
import { existsSync as
|
|
5019
|
+
import path18 from "path";
|
|
5020
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync4 } from "fs";
|
|
4266
5021
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4267
5022
|
function initShardManager(encryptionKey) {
|
|
4268
5023
|
_encryptionKey = encryptionKey;
|
|
4269
|
-
if (!
|
|
4270
|
-
|
|
5024
|
+
if (!existsSync14(SHARDS_DIR)) {
|
|
5025
|
+
mkdirSync8(SHARDS_DIR, { recursive: true });
|
|
4271
5026
|
}
|
|
4272
5027
|
_shardingEnabled = true;
|
|
4273
5028
|
}
|
|
@@ -4287,7 +5042,7 @@ function getShardClient(projectName) {
|
|
|
4287
5042
|
}
|
|
4288
5043
|
const cached = _shards.get(safeName);
|
|
4289
5044
|
if (cached) return cached;
|
|
4290
|
-
const dbPath =
|
|
5045
|
+
const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
|
|
4291
5046
|
const client = createClient2({
|
|
4292
5047
|
url: `file:${dbPath}`,
|
|
4293
5048
|
encryptionKey: _encryptionKey
|
|
@@ -4297,10 +5052,10 @@ function getShardClient(projectName) {
|
|
|
4297
5052
|
}
|
|
4298
5053
|
function shardExists(projectName) {
|
|
4299
5054
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4300
|
-
return
|
|
5055
|
+
return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
|
|
4301
5056
|
}
|
|
4302
5057
|
function listShards() {
|
|
4303
|
-
if (!
|
|
5058
|
+
if (!existsSync14(SHARDS_DIR)) return [];
|
|
4304
5059
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4305
5060
|
}
|
|
4306
5061
|
async function ensureShardSchema(client) {
|
|
@@ -4486,7 +5241,7 @@ var init_shard_manager = __esm({
|
|
|
4486
5241
|
"src/lib/shard-manager.ts"() {
|
|
4487
5242
|
"use strict";
|
|
4488
5243
|
init_config();
|
|
4489
|
-
SHARDS_DIR =
|
|
5244
|
+
SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
|
|
4490
5245
|
_shards = /* @__PURE__ */ new Map();
|
|
4491
5246
|
_encryptionKey = null;
|
|
4492
5247
|
_shardingEnabled = false;
|
|
@@ -4611,7 +5366,7 @@ __export(global_procedures_exports, {
|
|
|
4611
5366
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
4612
5367
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
4613
5368
|
});
|
|
4614
|
-
import { randomUUID as
|
|
5369
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4615
5370
|
async function loadGlobalProcedures() {
|
|
4616
5371
|
const client = getClient();
|
|
4617
5372
|
const result = await client.execute({
|
|
@@ -4640,7 +5395,7 @@ ${sections.join("\n\n")}
|
|
|
4640
5395
|
`;
|
|
4641
5396
|
}
|
|
4642
5397
|
async function storeGlobalProcedure(input2) {
|
|
4643
|
-
const id =
|
|
5398
|
+
const id = randomUUID3();
|
|
4644
5399
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4645
5400
|
const client = getClient();
|
|
4646
5401
|
await client.execute({
|
|
@@ -4691,6 +5446,7 @@ __export(store_exports, {
|
|
|
4691
5446
|
vectorToBlob: () => vectorToBlob,
|
|
4692
5447
|
writeMemory: () => writeMemory
|
|
4693
5448
|
});
|
|
5449
|
+
import { createHash } from "crypto";
|
|
4694
5450
|
function isBusyError2(err) {
|
|
4695
5451
|
if (err instanceof Error) {
|
|
4696
5452
|
const msg = err.message.toLowerCase();
|
|
@@ -4764,12 +5520,52 @@ function classifyTier(record) {
|
|
|
4764
5520
|
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4765
5521
|
return 3;
|
|
4766
5522
|
}
|
|
5523
|
+
function inferFilePaths(record) {
|
|
5524
|
+
if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
|
|
5525
|
+
const firstLine = record.raw_text.split("\n")[0] ?? "";
|
|
5526
|
+
const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
|
|
5527
|
+
return match ? JSON.stringify([match[1]]) : null;
|
|
5528
|
+
}
|
|
5529
|
+
function inferCommitHash(record) {
|
|
5530
|
+
if (record.tool_name !== "Bash") return null;
|
|
5531
|
+
const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
|
|
5532
|
+
return match ? match[1] : null;
|
|
5533
|
+
}
|
|
5534
|
+
function inferLanguageType(record) {
|
|
5535
|
+
const text = record.raw_text;
|
|
5536
|
+
if (!text || text.length < 10) return null;
|
|
5537
|
+
const trimmed = text.trimStart();
|
|
5538
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5539
|
+
if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
|
|
5540
|
+
if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
|
|
5541
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
|
|
5542
|
+
return "mixed";
|
|
5543
|
+
}
|
|
5544
|
+
function inferDomain(record) {
|
|
5545
|
+
const proj = (record.project_name ?? "").toLowerCase();
|
|
5546
|
+
if (proj.includes("marketing") || proj.includes("content")) return "marketing";
|
|
5547
|
+
if (proj.includes("crm") || proj.includes("customer")) return "customer";
|
|
5548
|
+
return null;
|
|
5549
|
+
}
|
|
4767
5550
|
async function writeMemory(record) {
|
|
4768
5551
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4769
5552
|
throw new Error(
|
|
4770
5553
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4771
5554
|
);
|
|
4772
5555
|
}
|
|
5556
|
+
const contentHash = createHash("md5").update(record.raw_text).digest("hex");
|
|
5557
|
+
if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
|
|
5558
|
+
return;
|
|
5559
|
+
}
|
|
5560
|
+
try {
|
|
5561
|
+
const client = getClient();
|
|
5562
|
+
const existing = await client.execute({
|
|
5563
|
+
sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
|
|
5564
|
+
args: [contentHash, record.agent_id]
|
|
5565
|
+
});
|
|
5566
|
+
if (existing.rows.length > 0) return;
|
|
5567
|
+
} catch {
|
|
5568
|
+
}
|
|
4773
5569
|
const dbRow = {
|
|
4774
5570
|
id: record.id,
|
|
4775
5571
|
agent_id: record.agent_id,
|
|
@@ -4799,7 +5595,23 @@ async function writeMemory(record) {
|
|
|
4799
5595
|
supersedes_id: record.supersedes_id ?? null,
|
|
4800
5596
|
draft: record.draft ? 1 : 0,
|
|
4801
5597
|
memory_type: record.memory_type ?? "raw",
|
|
4802
|
-
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5598
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
5599
|
+
content_hash: contentHash,
|
|
5600
|
+
intent: record.intent ?? null,
|
|
5601
|
+
outcome: record.outcome ?? null,
|
|
5602
|
+
domain: record.domain ?? inferDomain(record),
|
|
5603
|
+
referenced_entities: record.referenced_entities ?? null,
|
|
5604
|
+
retrieval_count: record.retrieval_count ?? 0,
|
|
5605
|
+
chain_position: record.chain_position ?? null,
|
|
5606
|
+
review_status: record.review_status ?? null,
|
|
5607
|
+
context_window_pct: record.context_window_pct ?? null,
|
|
5608
|
+
file_paths: record.file_paths ?? inferFilePaths(record),
|
|
5609
|
+
commit_hash: record.commit_hash ?? inferCommitHash(record),
|
|
5610
|
+
duration_ms: record.duration_ms ?? null,
|
|
5611
|
+
token_cost: record.token_cost ?? null,
|
|
5612
|
+
audience: record.audience ?? null,
|
|
5613
|
+
language_type: record.language_type ?? inferLanguageType(record),
|
|
5614
|
+
parent_memory_id: record.parent_memory_id ?? null
|
|
4803
5615
|
};
|
|
4804
5616
|
_pendingRecords.push(dbRow);
|
|
4805
5617
|
orgBus.emit({
|
|
@@ -4857,80 +5669,85 @@ async function flushBatch() {
|
|
|
4857
5669
|
const draft = row.draft ? 1 : 0;
|
|
4858
5670
|
const memoryType = row.memory_type ?? "raw";
|
|
4859
5671
|
const trajectory = row.trajectory ?? null;
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
5672
|
+
const contentHash = row.content_hash ?? null;
|
|
5673
|
+
const intent = row.intent ?? null;
|
|
5674
|
+
const outcome = row.outcome ?? null;
|
|
5675
|
+
const domain = row.domain ?? null;
|
|
5676
|
+
const referencedEntities = row.referenced_entities ?? null;
|
|
5677
|
+
const retrievalCount = row.retrieval_count ?? 0;
|
|
5678
|
+
const chainPosition = row.chain_position ?? null;
|
|
5679
|
+
const reviewStatus = row.review_status ?? null;
|
|
5680
|
+
const contextWindowPct = row.context_window_pct ?? null;
|
|
5681
|
+
const filePaths = row.file_paths ?? null;
|
|
5682
|
+
const commitHash = row.commit_hash ?? null;
|
|
5683
|
+
const durationMs = row.duration_ms ?? null;
|
|
5684
|
+
const tokenCost = row.token_cost ?? null;
|
|
5685
|
+
const audience = row.audience ?? null;
|
|
5686
|
+
const languageType = row.language_type ?? null;
|
|
5687
|
+
const parentMemoryId = row.parent_memory_id ?? null;
|
|
5688
|
+
const cols = `id, agent_id, agent_role, session_id, timestamp,
|
|
4863
5689
|
tool_name, project_name,
|
|
4864
5690
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4865
5691
|
confidence, last_accessed,
|
|
4866
5692
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4867
|
-
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
pageNumber,
|
|
4926
|
-
sourcePath,
|
|
4927
|
-
sourceType,
|
|
4928
|
-
tier,
|
|
4929
|
-
supersedesId,
|
|
4930
|
-
draft,
|
|
4931
|
-
memoryType,
|
|
4932
|
-
trajectory
|
|
4933
|
-
]
|
|
5693
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
|
|
5694
|
+
intent, outcome, domain, referenced_entities, retrieval_count,
|
|
5695
|
+
chain_position, review_status, context_window_pct, file_paths, commit_hash,
|
|
5696
|
+
duration_ms, token_cost, audience, language_type, parent_memory_id`;
|
|
5697
|
+
const metaArgs = [
|
|
5698
|
+
intent,
|
|
5699
|
+
outcome,
|
|
5700
|
+
domain,
|
|
5701
|
+
referencedEntities,
|
|
5702
|
+
retrievalCount,
|
|
5703
|
+
chainPosition,
|
|
5704
|
+
reviewStatus,
|
|
5705
|
+
contextWindowPct,
|
|
5706
|
+
filePaths,
|
|
5707
|
+
commitHash,
|
|
5708
|
+
durationMs,
|
|
5709
|
+
tokenCost,
|
|
5710
|
+
audience,
|
|
5711
|
+
languageType,
|
|
5712
|
+
parentMemoryId
|
|
5713
|
+
];
|
|
5714
|
+
const baseArgs = [
|
|
5715
|
+
row.id,
|
|
5716
|
+
row.agent_id,
|
|
5717
|
+
row.agent_role,
|
|
5718
|
+
row.session_id,
|
|
5719
|
+
row.timestamp,
|
|
5720
|
+
row.tool_name,
|
|
5721
|
+
row.project_name,
|
|
5722
|
+
row.has_error,
|
|
5723
|
+
row.raw_text
|
|
5724
|
+
];
|
|
5725
|
+
const sharedArgs = [
|
|
5726
|
+
row.version,
|
|
5727
|
+
taskId,
|
|
5728
|
+
importance,
|
|
5729
|
+
status,
|
|
5730
|
+
confidence,
|
|
5731
|
+
lastAccessed,
|
|
5732
|
+
workspaceId,
|
|
5733
|
+
documentId,
|
|
5734
|
+
userId,
|
|
5735
|
+
charOffset,
|
|
5736
|
+
pageNumber,
|
|
5737
|
+
sourcePath,
|
|
5738
|
+
sourceType,
|
|
5739
|
+
tier,
|
|
5740
|
+
supersedesId,
|
|
5741
|
+
draft,
|
|
5742
|
+
memoryType,
|
|
5743
|
+
trajectory,
|
|
5744
|
+
contentHash
|
|
5745
|
+
];
|
|
5746
|
+
return {
|
|
5747
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
|
|
5748
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
|
|
5749
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5750
|
+
args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
|
|
4934
5751
|
};
|
|
4935
5752
|
};
|
|
4936
5753
|
const globalClient = getClient();
|