@askexenow/exe-os 0.8.83 → 0.8.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/bin/exe-boot.js
CHANGED
|
@@ -427,6 +427,443 @@ var init_db_retry = __esm({
|
|
|
427
427
|
}
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
// src/lib/exe-daemon-client.ts
|
|
431
|
+
import net from "net";
|
|
432
|
+
import { spawn } from "child_process";
|
|
433
|
+
import { randomUUID } from "crypto";
|
|
434
|
+
import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
435
|
+
import path3 from "path";
|
|
436
|
+
import { fileURLToPath } from "url";
|
|
437
|
+
function handleData(chunk) {
|
|
438
|
+
_buffer += chunk.toString();
|
|
439
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
440
|
+
_buffer = "";
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
let newlineIdx;
|
|
444
|
+
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
445
|
+
const line = _buffer.slice(0, newlineIdx).trim();
|
|
446
|
+
_buffer = _buffer.slice(newlineIdx + 1);
|
|
447
|
+
if (!line) continue;
|
|
448
|
+
try {
|
|
449
|
+
const response = JSON.parse(line);
|
|
450
|
+
const id = response.id;
|
|
451
|
+
if (!id) continue;
|
|
452
|
+
const entry = _pending.get(id);
|
|
453
|
+
if (entry) {
|
|
454
|
+
clearTimeout(entry.timer);
|
|
455
|
+
_pending.delete(id);
|
|
456
|
+
entry.resolve(response);
|
|
457
|
+
}
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function cleanupStaleFiles() {
|
|
463
|
+
if (existsSync3(PID_PATH)) {
|
|
464
|
+
try {
|
|
465
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
466
|
+
if (pid > 0) {
|
|
467
|
+
try {
|
|
468
|
+
process.kill(pid, 0);
|
|
469
|
+
return;
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
unlinkSync2(PID_PATH);
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
unlinkSync2(SOCKET_PATH);
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function findPackageRoot() {
|
|
486
|
+
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
487
|
+
const { root } = path3.parse(dir);
|
|
488
|
+
while (dir !== root) {
|
|
489
|
+
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
490
|
+
dir = path3.dirname(dir);
|
|
491
|
+
}
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
function spawnDaemon() {
|
|
495
|
+
const pkgRoot = findPackageRoot();
|
|
496
|
+
if (!pkgRoot) {
|
|
497
|
+
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
501
|
+
if (!existsSync3(daemonPath)) {
|
|
502
|
+
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
503
|
+
`);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const resolvedPath = daemonPath;
|
|
507
|
+
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
508
|
+
`);
|
|
509
|
+
const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
|
|
510
|
+
let stderrFd = "ignore";
|
|
511
|
+
try {
|
|
512
|
+
stderrFd = openSync(logPath, "a");
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
const child = spawn(process.execPath, [resolvedPath], {
|
|
516
|
+
detached: true,
|
|
517
|
+
stdio: ["ignore", "ignore", stderrFd],
|
|
518
|
+
env: {
|
|
519
|
+
...process.env,
|
|
520
|
+
TMUX: void 0,
|
|
521
|
+
// Daemon is global — must not inherit session scope
|
|
522
|
+
TMUX_PANE: void 0,
|
|
523
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
524
|
+
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
525
|
+
EXE_DAEMON_PID: PID_PATH
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
child.unref();
|
|
529
|
+
if (typeof stderrFd === "number") {
|
|
530
|
+
try {
|
|
531
|
+
closeSync(stderrFd);
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function acquireSpawnLock() {
|
|
537
|
+
try {
|
|
538
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
539
|
+
closeSync(fd);
|
|
540
|
+
return true;
|
|
541
|
+
} catch {
|
|
542
|
+
try {
|
|
543
|
+
const stat = statSync(SPAWN_LOCK_PATH);
|
|
544
|
+
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
545
|
+
try {
|
|
546
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
const fd = openSync(SPAWN_LOCK_PATH, "wx");
|
|
551
|
+
closeSync(fd);
|
|
552
|
+
return true;
|
|
553
|
+
} catch {
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function releaseSpawnLock() {
|
|
562
|
+
try {
|
|
563
|
+
unlinkSync2(SPAWN_LOCK_PATH);
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function connectToSocket() {
|
|
568
|
+
return new Promise((resolve) => {
|
|
569
|
+
if (_socket && _connected) {
|
|
570
|
+
resolve(true);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const socket = net.createConnection({ path: SOCKET_PATH });
|
|
574
|
+
const connectTimeout = setTimeout(() => {
|
|
575
|
+
socket.destroy();
|
|
576
|
+
resolve(false);
|
|
577
|
+
}, 2e3);
|
|
578
|
+
socket.on("connect", () => {
|
|
579
|
+
clearTimeout(connectTimeout);
|
|
580
|
+
_socket = socket;
|
|
581
|
+
_connected = true;
|
|
582
|
+
_buffer = "";
|
|
583
|
+
socket.on("data", handleData);
|
|
584
|
+
socket.on("close", () => {
|
|
585
|
+
_connected = false;
|
|
586
|
+
_socket = null;
|
|
587
|
+
for (const [id, entry] of _pending) {
|
|
588
|
+
clearTimeout(entry.timer);
|
|
589
|
+
_pending.delete(id);
|
|
590
|
+
entry.resolve({ error: "Connection closed" });
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
socket.on("error", () => {
|
|
594
|
+
_connected = false;
|
|
595
|
+
_socket = null;
|
|
596
|
+
});
|
|
597
|
+
resolve(true);
|
|
598
|
+
});
|
|
599
|
+
socket.on("error", () => {
|
|
600
|
+
clearTimeout(connectTimeout);
|
|
601
|
+
resolve(false);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async function connectEmbedDaemon() {
|
|
606
|
+
if (_socket && _connected) return true;
|
|
607
|
+
if (await connectToSocket()) return true;
|
|
608
|
+
if (acquireSpawnLock()) {
|
|
609
|
+
try {
|
|
610
|
+
cleanupStaleFiles();
|
|
611
|
+
spawnDaemon();
|
|
612
|
+
} finally {
|
|
613
|
+
releaseSpawnLock();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const start = Date.now();
|
|
617
|
+
let delay2 = 100;
|
|
618
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
619
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
620
|
+
if (await connectToSocket()) return true;
|
|
621
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
626
|
+
return new Promise((resolve) => {
|
|
627
|
+
if (!_socket || !_connected) {
|
|
628
|
+
resolve({ error: "Not connected" });
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const id = randomUUID();
|
|
632
|
+
const timer = setTimeout(() => {
|
|
633
|
+
_pending.delete(id);
|
|
634
|
+
resolve({ error: "Request timeout" });
|
|
635
|
+
}, timeoutMs);
|
|
636
|
+
_pending.set(id, { resolve, timer });
|
|
637
|
+
try {
|
|
638
|
+
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
639
|
+
} catch {
|
|
640
|
+
clearTimeout(timer);
|
|
641
|
+
_pending.delete(id);
|
|
642
|
+
resolve({ error: "Write failed" });
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
function isClientConnected() {
|
|
647
|
+
return _connected;
|
|
648
|
+
}
|
|
649
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
|
|
650
|
+
var init_exe_daemon_client = __esm({
|
|
651
|
+
"src/lib/exe-daemon-client.ts"() {
|
|
652
|
+
"use strict";
|
|
653
|
+
init_config();
|
|
654
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
|
|
655
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
|
|
656
|
+
SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
657
|
+
SPAWN_LOCK_STALE_MS = 3e4;
|
|
658
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
659
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
660
|
+
_socket = null;
|
|
661
|
+
_connected = false;
|
|
662
|
+
_buffer = "";
|
|
663
|
+
_pending = /* @__PURE__ */ new Map();
|
|
664
|
+
MAX_BUFFER = 1e7;
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// src/lib/daemon-protocol.ts
|
|
669
|
+
function serializeValue(v) {
|
|
670
|
+
if (v === null || v === void 0) return null;
|
|
671
|
+
if (typeof v === "bigint") return Number(v);
|
|
672
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
673
|
+
if (v instanceof Uint8Array) {
|
|
674
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
675
|
+
}
|
|
676
|
+
if (ArrayBuffer.isView(v)) {
|
|
677
|
+
return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
|
|
678
|
+
}
|
|
679
|
+
if (v instanceof ArrayBuffer) {
|
|
680
|
+
return { __blob: Buffer.from(v).toString("base64") };
|
|
681
|
+
}
|
|
682
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
683
|
+
return String(v);
|
|
684
|
+
}
|
|
685
|
+
function deserializeValue(v) {
|
|
686
|
+
if (v === null) return null;
|
|
687
|
+
if (typeof v === "object" && v !== null && "__blob" in v) {
|
|
688
|
+
const buf = Buffer.from(v.__blob, "base64");
|
|
689
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
690
|
+
}
|
|
691
|
+
return v;
|
|
692
|
+
}
|
|
693
|
+
function deserializeResultSet(srs) {
|
|
694
|
+
const rows = srs.rows.map((obj) => {
|
|
695
|
+
const values = srs.columns.map(
|
|
696
|
+
(col) => deserializeValue(obj[col] ?? null)
|
|
697
|
+
);
|
|
698
|
+
const row = values;
|
|
699
|
+
for (let i = 0; i < srs.columns.length; i++) {
|
|
700
|
+
const col = srs.columns[i];
|
|
701
|
+
if (col !== void 0) {
|
|
702
|
+
row[col] = values[i] ?? null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
Object.defineProperty(row, "length", {
|
|
706
|
+
value: values.length,
|
|
707
|
+
enumerable: false
|
|
708
|
+
});
|
|
709
|
+
return row;
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
columns: srs.columns,
|
|
713
|
+
columnTypes: srs.columnTypes ?? [],
|
|
714
|
+
rows,
|
|
715
|
+
rowsAffected: srs.rowsAffected,
|
|
716
|
+
lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
|
|
717
|
+
toJSON: () => ({
|
|
718
|
+
columns: srs.columns,
|
|
719
|
+
columnTypes: srs.columnTypes ?? [],
|
|
720
|
+
rows: srs.rows,
|
|
721
|
+
rowsAffected: srs.rowsAffected,
|
|
722
|
+
lastInsertRowid: srs.lastInsertRowid
|
|
723
|
+
})
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
var init_daemon_protocol = __esm({
|
|
727
|
+
"src/lib/daemon-protocol.ts"() {
|
|
728
|
+
"use strict";
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// src/lib/db-daemon-client.ts
|
|
733
|
+
var db_daemon_client_exports = {};
|
|
734
|
+
__export(db_daemon_client_exports, {
|
|
735
|
+
createDaemonDbClient: () => createDaemonDbClient,
|
|
736
|
+
initDaemonDbClient: () => initDaemonDbClient
|
|
737
|
+
});
|
|
738
|
+
function normalizeStatement(stmt) {
|
|
739
|
+
if (typeof stmt === "string") {
|
|
740
|
+
return { sql: stmt, args: [] };
|
|
741
|
+
}
|
|
742
|
+
const sql = stmt.sql;
|
|
743
|
+
let args = [];
|
|
744
|
+
if (Array.isArray(stmt.args)) {
|
|
745
|
+
args = stmt.args.map((v) => serializeValue(v));
|
|
746
|
+
} else if (stmt.args && typeof stmt.args === "object") {
|
|
747
|
+
const named = {};
|
|
748
|
+
for (const [key, val] of Object.entries(stmt.args)) {
|
|
749
|
+
named[key] = serializeValue(val);
|
|
750
|
+
}
|
|
751
|
+
return { sql, args: named };
|
|
752
|
+
}
|
|
753
|
+
return { sql, args };
|
|
754
|
+
}
|
|
755
|
+
function createDaemonDbClient(fallbackClient) {
|
|
756
|
+
let _useDaemon = false;
|
|
757
|
+
const client = {
|
|
758
|
+
async execute(stmt) {
|
|
759
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
760
|
+
return fallbackClient.execute(stmt);
|
|
761
|
+
}
|
|
762
|
+
const { sql, args } = normalizeStatement(stmt);
|
|
763
|
+
const response = await sendDaemonRequest({
|
|
764
|
+
type: "db-execute",
|
|
765
|
+
sql,
|
|
766
|
+
args
|
|
767
|
+
});
|
|
768
|
+
if (response.error) {
|
|
769
|
+
const errMsg = String(response.error);
|
|
770
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
771
|
+
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
772
|
+
`);
|
|
773
|
+
return fallbackClient.execute(stmt);
|
|
774
|
+
}
|
|
775
|
+
throw new Error(errMsg);
|
|
776
|
+
}
|
|
777
|
+
if (response.db) {
|
|
778
|
+
return deserializeResultSet(response.db);
|
|
779
|
+
}
|
|
780
|
+
process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
|
|
781
|
+
return fallbackClient.execute(stmt);
|
|
782
|
+
},
|
|
783
|
+
async batch(stmts, mode) {
|
|
784
|
+
if (!_useDaemon || !isClientConnected()) {
|
|
785
|
+
return fallbackClient.batch(stmts, mode);
|
|
786
|
+
}
|
|
787
|
+
const statements = stmts.map(normalizeStatement);
|
|
788
|
+
const response = await sendDaemonRequest({
|
|
789
|
+
type: "db-batch",
|
|
790
|
+
statements,
|
|
791
|
+
mode: mode ?? "deferred"
|
|
792
|
+
});
|
|
793
|
+
if (response.error) {
|
|
794
|
+
const errMsg = String(response.error);
|
|
795
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
796
|
+
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
797
|
+
`);
|
|
798
|
+
return fallbackClient.batch(stmts, mode);
|
|
799
|
+
}
|
|
800
|
+
throw new Error(errMsg);
|
|
801
|
+
}
|
|
802
|
+
const batchResults = response["db-batch"];
|
|
803
|
+
if (batchResults) {
|
|
804
|
+
return batchResults.map(deserializeResultSet);
|
|
805
|
+
}
|
|
806
|
+
process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
|
|
807
|
+
return fallbackClient.batch(stmts, mode);
|
|
808
|
+
},
|
|
809
|
+
// Transaction support — delegate to fallback (transactions need direct connection)
|
|
810
|
+
async transaction(mode) {
|
|
811
|
+
return fallbackClient.transaction(mode);
|
|
812
|
+
},
|
|
813
|
+
// executeMultiple — delegate to fallback (used only for schema migrations)
|
|
814
|
+
async executeMultiple(sql) {
|
|
815
|
+
return fallbackClient.executeMultiple(sql);
|
|
816
|
+
},
|
|
817
|
+
// migrate — delegate to fallback
|
|
818
|
+
async migrate(stmts) {
|
|
819
|
+
return fallbackClient.migrate(stmts);
|
|
820
|
+
},
|
|
821
|
+
// Sync mode — delegate to fallback
|
|
822
|
+
sync() {
|
|
823
|
+
return fallbackClient.sync();
|
|
824
|
+
},
|
|
825
|
+
close() {
|
|
826
|
+
_useDaemon = false;
|
|
827
|
+
},
|
|
828
|
+
get closed() {
|
|
829
|
+
return fallbackClient.closed;
|
|
830
|
+
},
|
|
831
|
+
get protocol() {
|
|
832
|
+
return fallbackClient.protocol;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
return {
|
|
836
|
+
...client,
|
|
837
|
+
/** Enable daemon routing (call after confirming daemon is connected) */
|
|
838
|
+
_enableDaemon() {
|
|
839
|
+
_useDaemon = true;
|
|
840
|
+
},
|
|
841
|
+
/** Check if daemon routing is active */
|
|
842
|
+
_isDaemonActive() {
|
|
843
|
+
return _useDaemon && isClientConnected();
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
async function initDaemonDbClient(fallbackClient) {
|
|
848
|
+
if (process.env.EXE_IS_DAEMON === "1") return null;
|
|
849
|
+
const connected = await connectEmbedDaemon();
|
|
850
|
+
if (!connected) {
|
|
851
|
+
process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
const client = createDaemonDbClient(fallbackClient);
|
|
855
|
+
client._enableDaemon();
|
|
856
|
+
process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
|
|
857
|
+
return client;
|
|
858
|
+
}
|
|
859
|
+
var init_db_daemon_client = __esm({
|
|
860
|
+
"src/lib/db-daemon-client.ts"() {
|
|
861
|
+
"use strict";
|
|
862
|
+
init_exe_daemon_client();
|
|
863
|
+
init_daemon_protocol();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
430
867
|
// src/lib/database.ts
|
|
431
868
|
var database_exports = {};
|
|
432
869
|
__export(database_exports, {
|
|
@@ -435,6 +872,7 @@ __export(database_exports, {
|
|
|
435
872
|
ensureSchema: () => ensureSchema,
|
|
436
873
|
getClient: () => getClient,
|
|
437
874
|
getRawClient: () => getRawClient,
|
|
875
|
+
initDaemonClient: () => initDaemonClient,
|
|
438
876
|
initDatabase: () => initDatabase,
|
|
439
877
|
initTurso: () => initTurso,
|
|
440
878
|
isInitialized: () => isInitialized
|
|
@@ -462,8 +900,27 @@ function getClient() {
|
|
|
462
900
|
if (!_resilientClient) {
|
|
463
901
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
464
902
|
}
|
|
903
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
904
|
+
return _resilientClient;
|
|
905
|
+
}
|
|
906
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
907
|
+
return _daemonClient;
|
|
908
|
+
}
|
|
465
909
|
return _resilientClient;
|
|
466
910
|
}
|
|
911
|
+
async function initDaemonClient() {
|
|
912
|
+
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
913
|
+
if (!_resilientClient) return;
|
|
914
|
+
try {
|
|
915
|
+
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
916
|
+
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
917
|
+
} catch (err) {
|
|
918
|
+
process.stderr.write(
|
|
919
|
+
`[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
920
|
+
`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
467
924
|
function getRawClient() {
|
|
468
925
|
if (!_client) {
|
|
469
926
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
@@ -950,6 +1407,12 @@ async function ensureSchema() {
|
|
|
950
1407
|
} catch {
|
|
951
1408
|
}
|
|
952
1409
|
}
|
|
1410
|
+
try {
|
|
1411
|
+
await client.execute(
|
|
1412
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1413
|
+
);
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
953
1416
|
await client.executeMultiple(`
|
|
954
1417
|
CREATE TABLE IF NOT EXISTS entities (
|
|
955
1418
|
id TEXT PRIMARY KEY,
|
|
@@ -1002,7 +1465,30 @@ async function ensureSchema() {
|
|
|
1002
1465
|
entity_id TEXT NOT NULL,
|
|
1003
1466
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1004
1467
|
);
|
|
1468
|
+
|
|
1469
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1470
|
+
name,
|
|
1471
|
+
content=entities,
|
|
1472
|
+
content_rowid=rowid
|
|
1473
|
+
);
|
|
1474
|
+
|
|
1475
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1476
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1477
|
+
END;
|
|
1478
|
+
|
|
1479
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1480
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1481
|
+
END;
|
|
1482
|
+
|
|
1483
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1484
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1485
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1486
|
+
END;
|
|
1005
1487
|
`);
|
|
1488
|
+
try {
|
|
1489
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1006
1492
|
await client.executeMultiple(`
|
|
1007
1493
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1008
1494
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1183,6 +1669,33 @@ async function ensureSchema() {
|
|
|
1183
1669
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1184
1670
|
ON conversations(channel_id);
|
|
1185
1671
|
`);
|
|
1672
|
+
await client.executeMultiple(`
|
|
1673
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1674
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1675
|
+
agent_id TEXT NOT NULL,
|
|
1676
|
+
session_name TEXT,
|
|
1677
|
+
task_id TEXT,
|
|
1678
|
+
project_name TEXT,
|
|
1679
|
+
started_at TEXT NOT NULL
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1683
|
+
ON session_agent_map(agent_id);
|
|
1684
|
+
`);
|
|
1685
|
+
try {
|
|
1686
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1687
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1688
|
+
await client.execute({
|
|
1689
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1690
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1691
|
+
FROM memories
|
|
1692
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1693
|
+
GROUP BY session_id, agent_id`,
|
|
1694
|
+
args: []
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1186
1699
|
try {
|
|
1187
1700
|
await client.execute({
|
|
1188
1701
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1316,15 +1829,41 @@ async function ensureSchema() {
|
|
|
1316
1829
|
});
|
|
1317
1830
|
} catch {
|
|
1318
1831
|
}
|
|
1832
|
+
for (const col of [
|
|
1833
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1834
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1835
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1836
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1837
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1838
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1839
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1840
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1841
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1842
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1843
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1844
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1845
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1846
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1847
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1848
|
+
]) {
|
|
1849
|
+
try {
|
|
1850
|
+
await client.execute(col);
|
|
1851
|
+
} catch {
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1319
1854
|
}
|
|
1320
1855
|
async function disposeDatabase() {
|
|
1856
|
+
if (_daemonClient) {
|
|
1857
|
+
_daemonClient.close();
|
|
1858
|
+
_daemonClient = null;
|
|
1859
|
+
}
|
|
1321
1860
|
if (_client) {
|
|
1322
1861
|
_client.close();
|
|
1323
1862
|
_client = null;
|
|
1324
1863
|
_resilientClient = null;
|
|
1325
1864
|
}
|
|
1326
1865
|
}
|
|
1327
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1866
|
+
var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
|
|
1328
1867
|
var init_database = __esm({
|
|
1329
1868
|
"src/lib/database.ts"() {
|
|
1330
1869
|
"use strict";
|
|
@@ -1332,6 +1871,7 @@ var init_database = __esm({
|
|
|
1332
1871
|
init_employees();
|
|
1333
1872
|
_client = null;
|
|
1334
1873
|
_resilientClient = null;
|
|
1874
|
+
_daemonClient = null;
|
|
1335
1875
|
initTurso = initDatabase;
|
|
1336
1876
|
disposeTurso = disposeDatabase;
|
|
1337
1877
|
}
|
|
@@ -1455,7 +1995,7 @@ __export(global_procedures_exports, {
|
|
|
1455
1995
|
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
1456
1996
|
storeGlobalProcedure: () => storeGlobalProcedure
|
|
1457
1997
|
});
|
|
1458
|
-
import { randomUUID } from "crypto";
|
|
1998
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1459
1999
|
async function loadGlobalProcedures() {
|
|
1460
2000
|
const client = getClient();
|
|
1461
2001
|
const result = await client.execute({
|
|
@@ -1484,7 +2024,7 @@ ${sections.join("\n\n")}
|
|
|
1484
2024
|
`;
|
|
1485
2025
|
}
|
|
1486
2026
|
async function storeGlobalProcedure(input) {
|
|
1487
|
-
const id =
|
|
2027
|
+
const id = randomUUID2();
|
|
1488
2028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1489
2029
|
const client = getClient();
|
|
1490
2030
|
await client.execute({
|
|
@@ -1535,14 +2075,14 @@ __export(keychain_exports, {
|
|
|
1535
2075
|
setMasterKey: () => setMasterKey
|
|
1536
2076
|
});
|
|
1537
2077
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
1538
|
-
import { existsSync as
|
|
1539
|
-
import
|
|
2078
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2079
|
+
import path4 from "path";
|
|
1540
2080
|
import os3 from "os";
|
|
1541
2081
|
function getKeyDir() {
|
|
1542
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
2082
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os3.homedir(), ".exe-os");
|
|
1543
2083
|
}
|
|
1544
2084
|
function getKeyPath() {
|
|
1545
|
-
return
|
|
2085
|
+
return path4.join(getKeyDir(), "master.key");
|
|
1546
2086
|
}
|
|
1547
2087
|
async function tryKeytar() {
|
|
1548
2088
|
try {
|
|
@@ -1563,13 +2103,21 @@ async function getMasterKey() {
|
|
|
1563
2103
|
}
|
|
1564
2104
|
}
|
|
1565
2105
|
const keyPath = getKeyPath();
|
|
1566
|
-
if (!
|
|
2106
|
+
if (!existsSync4(keyPath)) {
|
|
2107
|
+
process.stderr.write(
|
|
2108
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
2109
|
+
`
|
|
2110
|
+
);
|
|
1567
2111
|
return null;
|
|
1568
2112
|
}
|
|
1569
2113
|
try {
|
|
1570
2114
|
const content = await readFile3(keyPath, "utf-8");
|
|
1571
2115
|
return Buffer.from(content.trim(), "base64");
|
|
1572
|
-
} catch {
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
process.stderr.write(
|
|
2118
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2119
|
+
`
|
|
2120
|
+
);
|
|
1573
2121
|
return null;
|
|
1574
2122
|
}
|
|
1575
2123
|
}
|
|
@@ -1598,7 +2146,7 @@ async function deleteMasterKey() {
|
|
|
1598
2146
|
}
|
|
1599
2147
|
}
|
|
1600
2148
|
const keyPath = getKeyPath();
|
|
1601
|
-
if (
|
|
2149
|
+
if (existsSync4(keyPath)) {
|
|
1602
2150
|
await unlink(keyPath);
|
|
1603
2151
|
}
|
|
1604
2152
|
}
|
|
@@ -1708,12 +2256,12 @@ __export(shard_manager_exports, {
|
|
|
1708
2256
|
listShards: () => listShards,
|
|
1709
2257
|
shardExists: () => shardExists
|
|
1710
2258
|
});
|
|
1711
|
-
import
|
|
1712
|
-
import { existsSync as
|
|
2259
|
+
import path5 from "path";
|
|
2260
|
+
import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
|
|
1713
2261
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1714
2262
|
function initShardManager(encryptionKey) {
|
|
1715
2263
|
_encryptionKey = encryptionKey;
|
|
1716
|
-
if (!
|
|
2264
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1717
2265
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1718
2266
|
}
|
|
1719
2267
|
_shardingEnabled = true;
|
|
@@ -1734,7 +2282,7 @@ function getShardClient(projectName) {
|
|
|
1734
2282
|
}
|
|
1735
2283
|
const cached = _shards.get(safeName);
|
|
1736
2284
|
if (cached) return cached;
|
|
1737
|
-
const dbPath =
|
|
2285
|
+
const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
|
|
1738
2286
|
const client = createClient2({
|
|
1739
2287
|
url: `file:${dbPath}`,
|
|
1740
2288
|
encryptionKey: _encryptionKey
|
|
@@ -1744,10 +2292,10 @@ function getShardClient(projectName) {
|
|
|
1744
2292
|
}
|
|
1745
2293
|
function shardExists(projectName) {
|
|
1746
2294
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1747
|
-
return
|
|
2295
|
+
return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
|
|
1748
2296
|
}
|
|
1749
2297
|
function listShards() {
|
|
1750
|
-
if (!
|
|
2298
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1751
2299
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1752
2300
|
}
|
|
1753
2301
|
async function ensureShardSchema(client) {
|
|
@@ -1933,7 +2481,7 @@ var init_shard_manager = __esm({
|
|
|
1933
2481
|
"src/lib/shard-manager.ts"() {
|
|
1934
2482
|
"use strict";
|
|
1935
2483
|
init_config();
|
|
1936
|
-
SHARDS_DIR =
|
|
2484
|
+
SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
|
|
1937
2485
|
_shards = /* @__PURE__ */ new Map();
|
|
1938
2486
|
_encryptionKey = null;
|
|
1939
2487
|
_shardingEnabled = false;
|
|
@@ -1941,6 +2489,7 @@ var init_shard_manager = __esm({
|
|
|
1941
2489
|
});
|
|
1942
2490
|
|
|
1943
2491
|
// src/lib/store.ts
|
|
2492
|
+
import { createHash } from "crypto";
|
|
1944
2493
|
function isBusyError2(err) {
|
|
1945
2494
|
if (err instanceof Error) {
|
|
1946
2495
|
const msg = err.message.toLowerCase();
|
|
@@ -2036,13 +2585,13 @@ __export(session_registry_exports, {
|
|
|
2036
2585
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
2037
2586
|
registerSession: () => registerSession
|
|
2038
2587
|
});
|
|
2039
|
-
import { readFileSync as
|
|
2588
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
|
|
2040
2589
|
import { execSync as execSync2 } from "child_process";
|
|
2041
|
-
import
|
|
2590
|
+
import path6 from "path";
|
|
2042
2591
|
import os4 from "os";
|
|
2043
2592
|
function registerSession(entry) {
|
|
2044
|
-
const dir =
|
|
2045
|
-
if (!
|
|
2593
|
+
const dir = path6.dirname(REGISTRY_PATH);
|
|
2594
|
+
if (!existsSync6(dir)) {
|
|
2046
2595
|
mkdirSync2(dir, { recursive: true });
|
|
2047
2596
|
}
|
|
2048
2597
|
const sessions = listSessions();
|
|
@@ -2056,7 +2605,7 @@ function registerSession(entry) {
|
|
|
2056
2605
|
}
|
|
2057
2606
|
function listSessions() {
|
|
2058
2607
|
try {
|
|
2059
|
-
const raw =
|
|
2608
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
2060
2609
|
return JSON.parse(raw);
|
|
2061
2610
|
} catch {
|
|
2062
2611
|
return [];
|
|
@@ -2085,7 +2634,7 @@ var REGISTRY_PATH;
|
|
|
2085
2634
|
var init_session_registry = __esm({
|
|
2086
2635
|
"src/lib/session-registry.ts"() {
|
|
2087
2636
|
"use strict";
|
|
2088
|
-
REGISTRY_PATH =
|
|
2637
|
+
REGISTRY_PATH = path6.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
2089
2638
|
}
|
|
2090
2639
|
});
|
|
2091
2640
|
|
|
@@ -2305,17 +2854,17 @@ var init_provider_table = __esm({
|
|
|
2305
2854
|
});
|
|
2306
2855
|
|
|
2307
2856
|
// src/lib/intercom-queue.ts
|
|
2308
|
-
import { readFileSync as
|
|
2309
|
-
import
|
|
2857
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2858
|
+
import path7 from "path";
|
|
2310
2859
|
import os5 from "os";
|
|
2311
2860
|
function ensureDir() {
|
|
2312
|
-
const dir =
|
|
2313
|
-
if (!
|
|
2861
|
+
const dir = path7.dirname(QUEUE_PATH);
|
|
2862
|
+
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
2314
2863
|
}
|
|
2315
2864
|
function readQueue() {
|
|
2316
2865
|
try {
|
|
2317
|
-
if (!
|
|
2318
|
-
return JSON.parse(
|
|
2866
|
+
if (!existsSync7(QUEUE_PATH)) return [];
|
|
2867
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
2319
2868
|
} catch {
|
|
2320
2869
|
return [];
|
|
2321
2870
|
}
|
|
@@ -2347,9 +2896,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
2347
2896
|
var init_intercom_queue = __esm({
|
|
2348
2897
|
"src/lib/intercom-queue.ts"() {
|
|
2349
2898
|
"use strict";
|
|
2350
|
-
QUEUE_PATH =
|
|
2899
|
+
QUEUE_PATH = path7.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
2351
2900
|
TTL_MS = 60 * 60 * 1e3;
|
|
2352
|
-
INTERCOM_LOG =
|
|
2901
|
+
INTERCOM_LOG = path7.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
2353
2902
|
}
|
|
2354
2903
|
});
|
|
2355
2904
|
|
|
@@ -2370,9 +2919,9 @@ __export(license_exports, {
|
|
|
2370
2919
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
2371
2920
|
validateLicense: () => validateLicense
|
|
2372
2921
|
});
|
|
2373
|
-
import { readFileSync as
|
|
2374
|
-
import { randomUUID as
|
|
2375
|
-
import
|
|
2922
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2923
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2924
|
+
import path8 from "path";
|
|
2376
2925
|
import { jwtVerify, importSPKI } from "jose";
|
|
2377
2926
|
async function fetchRetry(url, init) {
|
|
2378
2927
|
try {
|
|
@@ -2383,30 +2932,30 @@ async function fetchRetry(url, init) {
|
|
|
2383
2932
|
}
|
|
2384
2933
|
}
|
|
2385
2934
|
function loadDeviceId() {
|
|
2386
|
-
const deviceJsonPath =
|
|
2935
|
+
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
2387
2936
|
try {
|
|
2388
|
-
if (
|
|
2389
|
-
const data = JSON.parse(
|
|
2937
|
+
if (existsSync8(deviceJsonPath)) {
|
|
2938
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
2390
2939
|
if (data.deviceId) return data.deviceId;
|
|
2391
2940
|
}
|
|
2392
2941
|
} catch {
|
|
2393
2942
|
}
|
|
2394
2943
|
try {
|
|
2395
|
-
if (
|
|
2396
|
-
const id2 =
|
|
2944
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
2945
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
2397
2946
|
if (id2) return id2;
|
|
2398
2947
|
}
|
|
2399
2948
|
} catch {
|
|
2400
2949
|
}
|
|
2401
|
-
const id =
|
|
2950
|
+
const id = randomUUID3();
|
|
2402
2951
|
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
2403
2952
|
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
2404
2953
|
return id;
|
|
2405
2954
|
}
|
|
2406
2955
|
function loadLicense() {
|
|
2407
2956
|
try {
|
|
2408
|
-
if (!
|
|
2409
|
-
return
|
|
2957
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
2958
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
2410
2959
|
} catch {
|
|
2411
2960
|
return null;
|
|
2412
2961
|
}
|
|
@@ -2439,8 +2988,8 @@ async function verifyLicenseJwt(token) {
|
|
|
2439
2988
|
}
|
|
2440
2989
|
async function getCachedLicense() {
|
|
2441
2990
|
try {
|
|
2442
|
-
if (!
|
|
2443
|
-
const raw = JSON.parse(
|
|
2991
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
2992
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2444
2993
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
2445
2994
|
return await verifyLicenseJwt(raw.token);
|
|
2446
2995
|
} catch {
|
|
@@ -2449,8 +2998,8 @@ async function getCachedLicense() {
|
|
|
2449
2998
|
}
|
|
2450
2999
|
function readCachedToken() {
|
|
2451
3000
|
try {
|
|
2452
|
-
if (!
|
|
2453
|
-
const raw = JSON.parse(
|
|
3001
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3002
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
2454
3003
|
return typeof raw.token === "string" ? raw.token : null;
|
|
2455
3004
|
} catch {
|
|
2456
3005
|
return null;
|
|
@@ -2537,8 +3086,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
2537
3086
|
}
|
|
2538
3087
|
function getCacheAgeMs() {
|
|
2539
3088
|
try {
|
|
2540
|
-
const { statSync:
|
|
2541
|
-
const s =
|
|
3089
|
+
const { statSync: statSync3 } = __require("fs");
|
|
3090
|
+
const s = statSync3(CACHE_PATH);
|
|
2542
3091
|
return Date.now() - s.mtimeMs;
|
|
2543
3092
|
} catch {
|
|
2544
3093
|
return Infinity;
|
|
@@ -2548,9 +3097,9 @@ async function checkLicense() {
|
|
|
2548
3097
|
let key = loadLicense();
|
|
2549
3098
|
if (!key) {
|
|
2550
3099
|
try {
|
|
2551
|
-
const configPath =
|
|
2552
|
-
if (
|
|
2553
|
-
const raw = JSON.parse(
|
|
3100
|
+
const configPath = path8.join(EXE_AI_DIR, "config.json");
|
|
3101
|
+
if (existsSync8(configPath)) {
|
|
3102
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
2554
3103
|
const cloud = raw.cloud;
|
|
2555
3104
|
if (cloud?.apiKey) {
|
|
2556
3105
|
key = cloud.apiKey;
|
|
@@ -2709,9 +3258,9 @@ var init_license = __esm({
|
|
|
2709
3258
|
"src/lib/license.ts"() {
|
|
2710
3259
|
"use strict";
|
|
2711
3260
|
init_config();
|
|
2712
|
-
LICENSE_PATH =
|
|
2713
|
-
CACHE_PATH =
|
|
2714
|
-
DEVICE_ID_PATH =
|
|
3261
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
3262
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
3263
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
2715
3264
|
API_BASE = "https://askexe.com/cloud";
|
|
2716
3265
|
RETRY_DELAY_MS = 500;
|
|
2717
3266
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -2741,12 +3290,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
2741
3290
|
});
|
|
2742
3291
|
|
|
2743
3292
|
// src/lib/plan-limits.ts
|
|
2744
|
-
import { readFileSync as
|
|
2745
|
-
import
|
|
3293
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
3294
|
+
import path9 from "path";
|
|
2746
3295
|
function getLicenseSync() {
|
|
2747
3296
|
try {
|
|
2748
|
-
if (!
|
|
2749
|
-
const raw = JSON.parse(
|
|
3297
|
+
if (!existsSync9(CACHE_PATH2)) return freeLicense();
|
|
3298
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
2750
3299
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
2751
3300
|
const parts = raw.token.split(".");
|
|
2752
3301
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -2784,8 +3333,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2784
3333
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
2785
3334
|
let count = 0;
|
|
2786
3335
|
try {
|
|
2787
|
-
if (
|
|
2788
|
-
const raw =
|
|
3336
|
+
if (existsSync9(filePath)) {
|
|
3337
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
2789
3338
|
const employees = JSON.parse(raw);
|
|
2790
3339
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
2791
3340
|
}
|
|
@@ -2814,19 +3363,19 @@ var init_plan_limits = __esm({
|
|
|
2814
3363
|
this.name = "PlanLimitError";
|
|
2815
3364
|
}
|
|
2816
3365
|
};
|
|
2817
|
-
CACHE_PATH2 =
|
|
3366
|
+
CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
2818
3367
|
}
|
|
2819
3368
|
});
|
|
2820
3369
|
|
|
2821
3370
|
// src/lib/notifications.ts
|
|
2822
3371
|
import crypto from "crypto";
|
|
2823
|
-
import
|
|
3372
|
+
import path10 from "path";
|
|
2824
3373
|
import os6 from "os";
|
|
2825
3374
|
import {
|
|
2826
|
-
readFileSync as
|
|
3375
|
+
readFileSync as readFileSync8,
|
|
2827
3376
|
readdirSync as readdirSync2,
|
|
2828
|
-
unlinkSync as
|
|
2829
|
-
existsSync as
|
|
3377
|
+
unlinkSync as unlinkSync3,
|
|
3378
|
+
existsSync as existsSync10,
|
|
2830
3379
|
rmdirSync
|
|
2831
3380
|
} from "fs";
|
|
2832
3381
|
async function writeNotification(notification) {
|
|
@@ -2939,9 +3488,9 @@ async function markDoneTaskNotificationsAsRead() {
|
|
|
2939
3488
|
}
|
|
2940
3489
|
}
|
|
2941
3490
|
async function migrateJsonNotifications() {
|
|
2942
|
-
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR ||
|
|
2943
|
-
const notifDir =
|
|
2944
|
-
if (!
|
|
3491
|
+
const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path10.join(os6.homedir(), ".exe-os");
|
|
3492
|
+
const notifDir = path10.join(base, "notifications");
|
|
3493
|
+
if (!existsSync10(notifDir)) return 0;
|
|
2945
3494
|
let migrated = 0;
|
|
2946
3495
|
try {
|
|
2947
3496
|
const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
|
|
@@ -2949,8 +3498,8 @@ async function migrateJsonNotifications() {
|
|
|
2949
3498
|
const client = getClient();
|
|
2950
3499
|
for (const file of files) {
|
|
2951
3500
|
try {
|
|
2952
|
-
const filePath =
|
|
2953
|
-
const data = JSON.parse(
|
|
3501
|
+
const filePath = path10.join(notifDir, file);
|
|
3502
|
+
const data = JSON.parse(readFileSync8(filePath, "utf8"));
|
|
2954
3503
|
await client.execute({
|
|
2955
3504
|
sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
2956
3505
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -2966,7 +3515,7 @@ async function migrateJsonNotifications() {
|
|
|
2966
3515
|
data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2967
3516
|
]
|
|
2968
3517
|
});
|
|
2969
|
-
|
|
3518
|
+
unlinkSync3(filePath);
|
|
2970
3519
|
migrated++;
|
|
2971
3520
|
} catch {
|
|
2972
3521
|
}
|
|
@@ -3097,10 +3646,11 @@ var init_session_kill_telemetry = __esm({
|
|
|
3097
3646
|
|
|
3098
3647
|
// src/lib/tasks-crud.ts
|
|
3099
3648
|
import crypto3 from "crypto";
|
|
3100
|
-
import
|
|
3649
|
+
import path11 from "path";
|
|
3650
|
+
import os7 from "os";
|
|
3101
3651
|
import { execSync as execSync5 } from "child_process";
|
|
3102
3652
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3103
|
-
import { existsSync as
|
|
3653
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3104
3654
|
async function writeCheckpoint(input) {
|
|
3105
3655
|
const client = getClient();
|
|
3106
3656
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -3141,6 +3691,35 @@ function extractParentFromContext(contextBody) {
|
|
|
3141
3691
|
function slugify(title) {
|
|
3142
3692
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3143
3693
|
}
|
|
3694
|
+
function buildKeywordIndex() {
|
|
3695
|
+
const idx = /* @__PURE__ */ new Map();
|
|
3696
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
3697
|
+
for (const kw of keywords) {
|
|
3698
|
+
const existing = idx.get(kw) ?? [];
|
|
3699
|
+
existing.push(role);
|
|
3700
|
+
idx.set(kw, existing);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
return idx;
|
|
3704
|
+
}
|
|
3705
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
3706
|
+
const employees = loadEmployeesSync();
|
|
3707
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
3708
|
+
if (!employee) return void 0;
|
|
3709
|
+
const assigneeRole = employee.role;
|
|
3710
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
3711
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
3712
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
3713
|
+
if (text.includes(keyword)) {
|
|
3714
|
+
for (const role of roles) matchedRoles.add(role);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (matchedRoles.size === 0) return void 0;
|
|
3718
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
3719
|
+
if (assigneeRole === "COO") return void 0;
|
|
3720
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
3721
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
3722
|
+
}
|
|
3144
3723
|
async function resolveTask(client, identifier, scopeSession) {
|
|
3145
3724
|
const scope = sessionScopeFilter(scopeSession);
|
|
3146
3725
|
let result = await client.execute({
|
|
@@ -3190,7 +3769,14 @@ async function createTaskCore(input) {
|
|
|
3190
3769
|
const id = crypto3.randomUUID();
|
|
3191
3770
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3192
3771
|
const slug = slugify(input.title);
|
|
3193
|
-
|
|
3772
|
+
let earlySessionScope = null;
|
|
3773
|
+
try {
|
|
3774
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3775
|
+
earlySessionScope = resolveExeSession2();
|
|
3776
|
+
} catch {
|
|
3777
|
+
}
|
|
3778
|
+
const scope = earlySessionScope ?? "default";
|
|
3779
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
3194
3780
|
let blockedById = null;
|
|
3195
3781
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
3196
3782
|
if (input.blockedBy) {
|
|
@@ -3230,22 +3816,24 @@ async function createTaskCore(input) {
|
|
|
3230
3816
|
if (dupCheck.rows.length > 0) {
|
|
3231
3817
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
3232
3818
|
}
|
|
3819
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
3820
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
3821
|
+
if (laneWarning) {
|
|
3822
|
+
warning = warning ? `${warning}
|
|
3823
|
+
${laneWarning}` : laneWarning;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3233
3826
|
if (input.baseDir) {
|
|
3234
3827
|
try {
|
|
3235
|
-
await mkdir4(
|
|
3236
|
-
await mkdir4(
|
|
3828
|
+
await mkdir4(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
3829
|
+
await mkdir4(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
3237
3830
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
3238
3831
|
await ensureGitignoreExe(input.baseDir);
|
|
3239
3832
|
} catch {
|
|
3240
3833
|
}
|
|
3241
3834
|
}
|
|
3242
3835
|
const complexity = input.complexity ?? "standard";
|
|
3243
|
-
|
|
3244
|
-
try {
|
|
3245
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3246
|
-
sessionScope = resolveExeSession2();
|
|
3247
|
-
} catch {
|
|
3248
|
-
}
|
|
3836
|
+
const sessionScope = earlySessionScope;
|
|
3249
3837
|
await client.execute({
|
|
3250
3838
|
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)
|
|
3251
3839
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -3272,6 +3860,39 @@ async function createTaskCore(input) {
|
|
|
3272
3860
|
now
|
|
3273
3861
|
]
|
|
3274
3862
|
});
|
|
3863
|
+
if (input.baseDir) {
|
|
3864
|
+
try {
|
|
3865
|
+
const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
|
|
3866
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
3867
|
+
const mdDir = path11.dirname(mdPath);
|
|
3868
|
+
if (!existsSync11(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
3869
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3870
|
+
const mdContent = `# ${input.title}
|
|
3871
|
+
|
|
3872
|
+
**ID:** ${id}
|
|
3873
|
+
**Status:** ${initialStatus}
|
|
3874
|
+
**Priority:** ${input.priority}
|
|
3875
|
+
**Assigned by:** ${input.assignedBy}
|
|
3876
|
+
**Assigned to:** ${input.assignedTo}
|
|
3877
|
+
**Project:** ${input.projectName}
|
|
3878
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
3879
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
3880
|
+
**Reviewer:** ${reviewer}
|
|
3881
|
+
|
|
3882
|
+
## Context
|
|
3883
|
+
|
|
3884
|
+
${input.context}
|
|
3885
|
+
|
|
3886
|
+
## MANDATORY: When done
|
|
3887
|
+
|
|
3888
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3889
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3890
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3891
|
+
`;
|
|
3892
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
3893
|
+
} catch {
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3275
3896
|
return {
|
|
3276
3897
|
id,
|
|
3277
3898
|
title: input.title,
|
|
@@ -3464,7 +4085,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3464
4085
|
return { row, taskFile, now, taskId };
|
|
3465
4086
|
}
|
|
3466
4087
|
}
|
|
3467
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
4088
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
3468
4089
|
process.stderr.write(
|
|
3469
4090
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
3470
4091
|
`
|
|
@@ -3529,9 +4150,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
3529
4150
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
3530
4151
|
}
|
|
3531
4152
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
3532
|
-
const archPath =
|
|
4153
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
3533
4154
|
try {
|
|
3534
|
-
if (
|
|
4155
|
+
if (existsSync11(archPath)) return;
|
|
3535
4156
|
const template = [
|
|
3536
4157
|
`# ${projectName} \u2014 System Architecture`,
|
|
3537
4158
|
"",
|
|
@@ -3564,10 +4185,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
3564
4185
|
}
|
|
3565
4186
|
}
|
|
3566
4187
|
async function ensureGitignoreExe(baseDir) {
|
|
3567
|
-
const gitignorePath =
|
|
4188
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
3568
4189
|
try {
|
|
3569
|
-
if (
|
|
3570
|
-
const content =
|
|
4190
|
+
if (existsSync11(gitignorePath)) {
|
|
4191
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
3571
4192
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
3572
4193
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
3573
4194
|
} else {
|
|
@@ -3576,20 +4197,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3576
4197
|
} catch {
|
|
3577
4198
|
}
|
|
3578
4199
|
}
|
|
3579
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
4200
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3580
4201
|
var init_tasks_crud = __esm({
|
|
3581
4202
|
"src/lib/tasks-crud.ts"() {
|
|
3582
4203
|
"use strict";
|
|
3583
4204
|
init_database();
|
|
3584
4205
|
init_task_scope();
|
|
4206
|
+
init_employees();
|
|
4207
|
+
LANE_KEYWORDS = {
|
|
4208
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
4209
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
4210
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
4211
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
4212
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
4213
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
4214
|
+
};
|
|
4215
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
3585
4216
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
3586
4217
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
3587
4218
|
}
|
|
3588
4219
|
});
|
|
3589
4220
|
|
|
3590
4221
|
// src/lib/tasks-review.ts
|
|
3591
|
-
import
|
|
3592
|
-
import { existsSync as
|
|
4222
|
+
import path12 from "path";
|
|
4223
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3593
4224
|
async function countPendingReviews(sessionScope) {
|
|
3594
4225
|
const client = getClient();
|
|
3595
4226
|
if (sessionScope) {
|
|
@@ -3611,7 +4242,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
3611
4242
|
const result2 = await client.execute({
|
|
3612
4243
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3613
4244
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
3614
|
-
AND
|
|
4245
|
+
AND session_scope = ?`,
|
|
3615
4246
|
args: [sinceIso, sessionScope]
|
|
3616
4247
|
});
|
|
3617
4248
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -3629,7 +4260,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
3629
4260
|
const result2 = await client.execute({
|
|
3630
4261
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
3631
4262
|
WHERE status = 'needs_review'
|
|
3632
|
-
AND
|
|
4263
|
+
AND session_scope = ?
|
|
3633
4264
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
3634
4265
|
args: [sessionScope, limit]
|
|
3635
4266
|
});
|
|
@@ -3750,14 +4381,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3750
4381
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
3751
4382
|
const agent = parts[1];
|
|
3752
4383
|
const slug = parts.slice(2).join("-");
|
|
3753
|
-
const
|
|
4384
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
3754
4385
|
const result = await client.execute({
|
|
3755
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
3756
|
-
args: [now,
|
|
4386
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
4387
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
3757
4388
|
});
|
|
3758
4389
|
if (result.rowsAffected > 0) {
|
|
3759
4390
|
process.stderr.write(
|
|
3760
|
-
`[review-cleanup] Cascaded original task to done
|
|
4391
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
3761
4392
|
`
|
|
3762
4393
|
);
|
|
3763
4394
|
}
|
|
@@ -3770,11 +4401,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3770
4401
|
);
|
|
3771
4402
|
}
|
|
3772
4403
|
try {
|
|
3773
|
-
const cacheDir =
|
|
3774
|
-
if (
|
|
4404
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
4405
|
+
if (existsSync12(cacheDir)) {
|
|
3775
4406
|
for (const f of readdirSync3(cacheDir)) {
|
|
3776
4407
|
if (f.startsWith("review-notified-")) {
|
|
3777
|
-
|
|
4408
|
+
unlinkSync4(path12.join(cacheDir, f));
|
|
3778
4409
|
}
|
|
3779
4410
|
}
|
|
3780
4411
|
}
|
|
@@ -3795,7 +4426,7 @@ var init_tasks_review = __esm({
|
|
|
3795
4426
|
});
|
|
3796
4427
|
|
|
3797
4428
|
// src/lib/tasks-chain.ts
|
|
3798
|
-
import
|
|
4429
|
+
import path13 from "path";
|
|
3799
4430
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
3800
4431
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
3801
4432
|
const client = getClient();
|
|
@@ -3812,7 +4443,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3812
4443
|
});
|
|
3813
4444
|
for (const ur of unblockedRows.rows) {
|
|
3814
4445
|
try {
|
|
3815
|
-
const ubFile =
|
|
4446
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
3816
4447
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
3817
4448
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
3818
4449
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -3886,7 +4517,7 @@ __export(project_name_exports, {
|
|
|
3886
4517
|
getProjectName: () => getProjectName
|
|
3887
4518
|
});
|
|
3888
4519
|
import { execSync as execSync6 } from "child_process";
|
|
3889
|
-
import
|
|
4520
|
+
import path14 from "path";
|
|
3890
4521
|
function getProjectName(cwd) {
|
|
3891
4522
|
const dir = cwd ?? process.cwd();
|
|
3892
4523
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -3899,7 +4530,7 @@ function getProjectName(cwd) {
|
|
|
3899
4530
|
timeout: 2e3,
|
|
3900
4531
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3901
4532
|
}).trim();
|
|
3902
|
-
repoRoot =
|
|
4533
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
3903
4534
|
} catch {
|
|
3904
4535
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3905
4536
|
cwd: dir,
|
|
@@ -3908,11 +4539,11 @@ function getProjectName(cwd) {
|
|
|
3908
4539
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3909
4540
|
}).trim();
|
|
3910
4541
|
}
|
|
3911
|
-
_cached2 =
|
|
4542
|
+
_cached2 = path14.basename(repoRoot);
|
|
3912
4543
|
_cachedCwd = dir;
|
|
3913
4544
|
return _cached2;
|
|
3914
4545
|
} catch {
|
|
3915
|
-
_cached2 =
|
|
4546
|
+
_cached2 = path14.basename(dir);
|
|
3916
4547
|
_cachedCwd = dir;
|
|
3917
4548
|
return _cached2;
|
|
3918
4549
|
}
|
|
@@ -3948,7 +4579,7 @@ function findSessionForProject(projectName) {
|
|
|
3948
4579
|
const sessions = listSessions();
|
|
3949
4580
|
for (const s of sessions) {
|
|
3950
4581
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3951
|
-
if (proj === projectName &&
|
|
4582
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
3952
4583
|
}
|
|
3953
4584
|
return null;
|
|
3954
4585
|
}
|
|
@@ -3994,7 +4625,7 @@ var init_session_scope = __esm({
|
|
|
3994
4625
|
|
|
3995
4626
|
// src/lib/tasks-notify.ts
|
|
3996
4627
|
async function dispatchTaskToEmployee(input) {
|
|
3997
|
-
if (
|
|
4628
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
3998
4629
|
let crossProject = false;
|
|
3999
4630
|
if (input.projectName) {
|
|
4000
4631
|
try {
|
|
@@ -4389,8 +5020,8 @@ __export(tasks_exports, {
|
|
|
4389
5020
|
updateTaskStatus: () => updateTaskStatus,
|
|
4390
5021
|
writeCheckpoint: () => writeCheckpoint
|
|
4391
5022
|
});
|
|
4392
|
-
import
|
|
4393
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as
|
|
5023
|
+
import path15 from "path";
|
|
5024
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
4394
5025
|
async function createTask(input) {
|
|
4395
5026
|
const result = await createTaskCore(input);
|
|
4396
5027
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4409,14 +5040,14 @@ async function updateTask(input) {
|
|
|
4409
5040
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
4410
5041
|
try {
|
|
4411
5042
|
const agent = String(row.assigned_to);
|
|
4412
|
-
const cacheDir =
|
|
4413
|
-
const cachePath =
|
|
5043
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
5044
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
4414
5045
|
if (input.status === "in_progress") {
|
|
4415
5046
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4416
5047
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4417
5048
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
4418
5049
|
try {
|
|
4419
|
-
|
|
5050
|
+
unlinkSync5(cachePath);
|
|
4420
5051
|
} catch {
|
|
4421
5052
|
}
|
|
4422
5053
|
}
|
|
@@ -4473,7 +5104,7 @@ async function updateTask(input) {
|
|
|
4473
5104
|
}
|
|
4474
5105
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4475
5106
|
if (isTerminal) {
|
|
4476
|
-
const isCoordinator =
|
|
5107
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4477
5108
|
if (!isCoordinator) {
|
|
4478
5109
|
notifyTaskDone();
|
|
4479
5110
|
}
|
|
@@ -4498,7 +5129,7 @@ async function updateTask(input) {
|
|
|
4498
5129
|
}
|
|
4499
5130
|
}
|
|
4500
5131
|
}
|
|
4501
|
-
if (input.status === "done" &&
|
|
5132
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4502
5133
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4503
5134
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4504
5135
|
taskId,
|
|
@@ -4514,7 +5145,7 @@ async function updateTask(input) {
|
|
|
4514
5145
|
});
|
|
4515
5146
|
}
|
|
4516
5147
|
let nextTask;
|
|
4517
|
-
if (isTerminal &&
|
|
5148
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
4518
5149
|
try {
|
|
4519
5150
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
4520
5151
|
} catch {
|
|
@@ -4858,7 +5489,7 @@ var init_capacity_monitor = __esm({
|
|
|
4858
5489
|
// src/lib/tmux-routing.ts
|
|
4859
5490
|
var tmux_routing_exports = {};
|
|
4860
5491
|
__export(tmux_routing_exports, {
|
|
4861
|
-
acquireSpawnLock: () =>
|
|
5492
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
4862
5493
|
employeeSessionName: () => employeeSessionName,
|
|
4863
5494
|
ensureEmployee: () => ensureEmployee,
|
|
4864
5495
|
extractRootExe: () => extractRootExe,
|
|
@@ -4873,20 +5504,20 @@ __export(tmux_routing_exports, {
|
|
|
4873
5504
|
notifyParentExe: () => notifyParentExe,
|
|
4874
5505
|
parseParentExe: () => parseParentExe,
|
|
4875
5506
|
registerParentExe: () => registerParentExe,
|
|
4876
|
-
releaseSpawnLock: () =>
|
|
5507
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
4877
5508
|
resolveExeSession: () => resolveExeSession,
|
|
4878
5509
|
sendIntercom: () => sendIntercom,
|
|
4879
5510
|
spawnEmployee: () => spawnEmployee,
|
|
4880
5511
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4881
5512
|
});
|
|
4882
5513
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4883
|
-
import { readFileSync as
|
|
4884
|
-
import
|
|
4885
|
-
import
|
|
4886
|
-
import { fileURLToPath } from "url";
|
|
4887
|
-
import { unlinkSync as
|
|
5514
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync } from "fs";
|
|
5515
|
+
import path16 from "path";
|
|
5516
|
+
import os8 from "os";
|
|
5517
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5518
|
+
import { unlinkSync as unlinkSync6 } from "fs";
|
|
4888
5519
|
function spawnLockPath(sessionName) {
|
|
4889
|
-
return
|
|
5520
|
+
return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4890
5521
|
}
|
|
4891
5522
|
function isProcessAlive(pid) {
|
|
4892
5523
|
try {
|
|
@@ -4896,14 +5527,14 @@ function isProcessAlive(pid) {
|
|
|
4896
5527
|
return false;
|
|
4897
5528
|
}
|
|
4898
5529
|
}
|
|
4899
|
-
function
|
|
4900
|
-
if (!
|
|
5530
|
+
function acquireSpawnLock2(sessionName) {
|
|
5531
|
+
if (!existsSync13(SPAWN_LOCK_DIR)) {
|
|
4901
5532
|
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4902
5533
|
}
|
|
4903
5534
|
const lockFile = spawnLockPath(sessionName);
|
|
4904
|
-
if (
|
|
5535
|
+
if (existsSync13(lockFile)) {
|
|
4905
5536
|
try {
|
|
4906
|
-
const lock = JSON.parse(
|
|
5537
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
4907
5538
|
const age = Date.now() - lock.timestamp;
|
|
4908
5539
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4909
5540
|
return false;
|
|
@@ -4914,22 +5545,22 @@ function acquireSpawnLock(sessionName) {
|
|
|
4914
5545
|
writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4915
5546
|
return true;
|
|
4916
5547
|
}
|
|
4917
|
-
function
|
|
5548
|
+
function releaseSpawnLock2(sessionName) {
|
|
4918
5549
|
try {
|
|
4919
|
-
|
|
5550
|
+
unlinkSync6(spawnLockPath(sessionName));
|
|
4920
5551
|
} catch {
|
|
4921
5552
|
}
|
|
4922
5553
|
}
|
|
4923
5554
|
function resolveBehaviorsExporterScript() {
|
|
4924
5555
|
try {
|
|
4925
|
-
const thisFile =
|
|
4926
|
-
const scriptPath =
|
|
4927
|
-
|
|
5556
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
5557
|
+
const scriptPath = path16.join(
|
|
5558
|
+
path16.dirname(thisFile),
|
|
4928
5559
|
"..",
|
|
4929
5560
|
"bin",
|
|
4930
5561
|
"exe-export-behaviors.js"
|
|
4931
5562
|
);
|
|
4932
|
-
return
|
|
5563
|
+
return existsSync13(scriptPath) ? scriptPath : null;
|
|
4933
5564
|
} catch {
|
|
4934
5565
|
return null;
|
|
4935
5566
|
}
|
|
@@ -4995,11 +5626,11 @@ function extractRootExe(name) {
|
|
|
4995
5626
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4996
5627
|
}
|
|
4997
5628
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4998
|
-
if (!
|
|
5629
|
+
if (!existsSync13(SESSION_CACHE)) {
|
|
4999
5630
|
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5000
5631
|
}
|
|
5001
5632
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
5002
|
-
const filePath =
|
|
5633
|
+
const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
5003
5634
|
writeFileSync6(filePath, JSON.stringify({
|
|
5004
5635
|
parentExe: rootExe,
|
|
5005
5636
|
dispatchedBy: dispatchedBy || rootExe,
|
|
@@ -5008,7 +5639,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
5008
5639
|
}
|
|
5009
5640
|
function getParentExe(sessionKey) {
|
|
5010
5641
|
try {
|
|
5011
|
-
const data = JSON.parse(
|
|
5642
|
+
const data = JSON.parse(readFileSync10(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
5012
5643
|
return data.parentExe || null;
|
|
5013
5644
|
} catch {
|
|
5014
5645
|
return null;
|
|
@@ -5016,8 +5647,8 @@ function getParentExe(sessionKey) {
|
|
|
5016
5647
|
}
|
|
5017
5648
|
function getDispatchedBy(sessionKey) {
|
|
5018
5649
|
try {
|
|
5019
|
-
const data = JSON.parse(
|
|
5020
|
-
|
|
5650
|
+
const data = JSON.parse(readFileSync10(
|
|
5651
|
+
path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
5021
5652
|
"utf8"
|
|
5022
5653
|
));
|
|
5023
5654
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -5043,10 +5674,10 @@ function isEmployeeAlive(sessionName) {
|
|
|
5043
5674
|
}
|
|
5044
5675
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
5045
5676
|
const base = employeeSessionName(employeeName, exeSession);
|
|
5046
|
-
if (!isAlive(base) &&
|
|
5677
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
5047
5678
|
for (let i = 2; i <= maxInstances; i++) {
|
|
5048
5679
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
5049
|
-
if (!isAlive(candidate) &&
|
|
5680
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
5050
5681
|
}
|
|
5051
5682
|
return null;
|
|
5052
5683
|
}
|
|
@@ -5078,15 +5709,15 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
5078
5709
|
}
|
|
5079
5710
|
function readDebounceState() {
|
|
5080
5711
|
try {
|
|
5081
|
-
if (!
|
|
5082
|
-
return JSON.parse(
|
|
5712
|
+
if (!existsSync13(DEBOUNCE_FILE)) return {};
|
|
5713
|
+
return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
5083
5714
|
} catch {
|
|
5084
5715
|
return {};
|
|
5085
5716
|
}
|
|
5086
5717
|
}
|
|
5087
5718
|
function writeDebounceState(state) {
|
|
5088
5719
|
try {
|
|
5089
|
-
if (!
|
|
5720
|
+
if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
5090
5721
|
writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
|
|
5091
5722
|
} catch {
|
|
5092
5723
|
}
|
|
@@ -5206,7 +5837,7 @@ function notifyParentExe(sessionKey) {
|
|
|
5206
5837
|
return true;
|
|
5207
5838
|
}
|
|
5208
5839
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5209
|
-
if (
|
|
5840
|
+
if (isCoordinatorName(employeeName)) {
|
|
5210
5841
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
5211
5842
|
}
|
|
5212
5843
|
try {
|
|
@@ -5278,26 +5909,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5278
5909
|
const transport = getTransport();
|
|
5279
5910
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
5280
5911
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
5281
|
-
const logDir =
|
|
5282
|
-
const logFile =
|
|
5283
|
-
if (!
|
|
5912
|
+
const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
|
|
5913
|
+
const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
5914
|
+
if (!existsSync13(logDir)) {
|
|
5284
5915
|
mkdirSync6(logDir, { recursive: true });
|
|
5285
5916
|
}
|
|
5286
5917
|
transport.kill(sessionName);
|
|
5287
5918
|
let cleanupSuffix = "";
|
|
5288
5919
|
try {
|
|
5289
|
-
const thisFile =
|
|
5290
|
-
const cleanupScript =
|
|
5291
|
-
if (
|
|
5920
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
5921
|
+
const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
5922
|
+
if (existsSync13(cleanupScript)) {
|
|
5292
5923
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
5293
5924
|
}
|
|
5294
5925
|
} catch {
|
|
5295
5926
|
}
|
|
5296
5927
|
try {
|
|
5297
|
-
const claudeJsonPath =
|
|
5928
|
+
const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
|
|
5298
5929
|
let claudeJson = {};
|
|
5299
5930
|
try {
|
|
5300
|
-
claudeJson = JSON.parse(
|
|
5931
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
5301
5932
|
} catch {
|
|
5302
5933
|
}
|
|
5303
5934
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -5309,13 +5940,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5309
5940
|
} catch {
|
|
5310
5941
|
}
|
|
5311
5942
|
try {
|
|
5312
|
-
const settingsDir =
|
|
5943
|
+
const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
|
|
5313
5944
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
5314
|
-
const projSettingsDir =
|
|
5315
|
-
const settingsPath =
|
|
5945
|
+
const projSettingsDir = path16.join(settingsDir, normalizedKey);
|
|
5946
|
+
const settingsPath = path16.join(projSettingsDir, "settings.json");
|
|
5316
5947
|
let settings = {};
|
|
5317
5948
|
try {
|
|
5318
|
-
settings = JSON.parse(
|
|
5949
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
5319
5950
|
} catch {
|
|
5320
5951
|
}
|
|
5321
5952
|
const perms = settings.permissions ?? {};
|
|
@@ -5356,8 +5987,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5356
5987
|
let behaviorsFlag = "";
|
|
5357
5988
|
let legacyFallbackWarned = false;
|
|
5358
5989
|
if (!useExeAgent && !useBinSymlink) {
|
|
5359
|
-
const identityPath =
|
|
5360
|
-
|
|
5990
|
+
const identityPath = path16.join(
|
|
5991
|
+
os8.homedir(),
|
|
5361
5992
|
".exe-os",
|
|
5362
5993
|
"identity",
|
|
5363
5994
|
`${employeeName}.md`
|
|
@@ -5366,13 +5997,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5366
5997
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
5367
5998
|
if (hasAgentFlag) {
|
|
5368
5999
|
identityFlag = ` --agent ${employeeName}`;
|
|
5369
|
-
} else if (
|
|
6000
|
+
} else if (existsSync13(identityPath)) {
|
|
5370
6001
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
5371
6002
|
legacyFallbackWarned = true;
|
|
5372
6003
|
}
|
|
5373
6004
|
const behaviorsFile = exportBehaviorsSync(
|
|
5374
6005
|
employeeName,
|
|
5375
|
-
|
|
6006
|
+
path16.basename(spawnCwd),
|
|
5376
6007
|
sessionName
|
|
5377
6008
|
);
|
|
5378
6009
|
if (behaviorsFile) {
|
|
@@ -5387,9 +6018,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5387
6018
|
}
|
|
5388
6019
|
let sessionContextFlag = "";
|
|
5389
6020
|
try {
|
|
5390
|
-
const ctxDir =
|
|
6021
|
+
const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
5391
6022
|
mkdirSync6(ctxDir, { recursive: true });
|
|
5392
|
-
const ctxFile =
|
|
6023
|
+
const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
|
|
5393
6024
|
const ctxContent = [
|
|
5394
6025
|
`## Session Context`,
|
|
5395
6026
|
`You are running in tmux session: ${sessionName}.`,
|
|
@@ -5428,13 +6059,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5428
6059
|
command: spawnCommand
|
|
5429
6060
|
});
|
|
5430
6061
|
if (spawnResult.error) {
|
|
5431
|
-
|
|
6062
|
+
releaseSpawnLock2(sessionName);
|
|
5432
6063
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
5433
6064
|
}
|
|
5434
6065
|
transport.pipeLog(sessionName, logFile);
|
|
5435
6066
|
try {
|
|
5436
6067
|
const mySession = getMySession();
|
|
5437
|
-
const dispatchInfo =
|
|
6068
|
+
const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
5438
6069
|
writeFileSync6(dispatchInfo, JSON.stringify({
|
|
5439
6070
|
dispatchedBy: mySession,
|
|
5440
6071
|
rootExe: exeSession,
|
|
@@ -5466,7 +6097,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5466
6097
|
}
|
|
5467
6098
|
}
|
|
5468
6099
|
if (!booted) {
|
|
5469
|
-
|
|
6100
|
+
releaseSpawnLock2(sessionName);
|
|
5470
6101
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
5471
6102
|
}
|
|
5472
6103
|
if (!useExeAgent) {
|
|
@@ -5483,7 +6114,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5483
6114
|
pid: 0,
|
|
5484
6115
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5485
6116
|
});
|
|
5486
|
-
|
|
6117
|
+
releaseSpawnLock2(sessionName);
|
|
5487
6118
|
return { sessionName };
|
|
5488
6119
|
}
|
|
5489
6120
|
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;
|
|
@@ -5499,14 +6130,14 @@ var init_tmux_routing = __esm({
|
|
|
5499
6130
|
init_intercom_queue();
|
|
5500
6131
|
init_plan_limits();
|
|
5501
6132
|
init_employees();
|
|
5502
|
-
SPAWN_LOCK_DIR =
|
|
5503
|
-
SESSION_CACHE =
|
|
6133
|
+
SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
|
|
6134
|
+
SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
|
|
5504
6135
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5505
6136
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
5506
6137
|
VERIFY_PANE_LINES = 200;
|
|
5507
6138
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5508
|
-
INTERCOM_LOG2 =
|
|
5509
|
-
DEBOUNCE_FILE =
|
|
6139
|
+
INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
|
|
6140
|
+
DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5510
6141
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5511
6142
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
5512
6143
|
}
|
|
@@ -5547,9 +6178,10 @@ __export(task_scanner_exports, {
|
|
|
5547
6178
|
formatText: () => formatText,
|
|
5548
6179
|
scanAgentTasks: () => scanAgentTasks
|
|
5549
6180
|
});
|
|
5550
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
6181
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12, existsSync as existsSync14, statSync as statSync2 } from "fs";
|
|
5551
6182
|
import { execSync as execSync9 } from "child_process";
|
|
5552
|
-
import
|
|
6183
|
+
import path18 from "path";
|
|
6184
|
+
import os9 from "os";
|
|
5553
6185
|
function getProjectRoot() {
|
|
5554
6186
|
try {
|
|
5555
6187
|
return execSync9("git rev-parse --show-toplevel", {
|
|
@@ -5561,19 +6193,31 @@ function getProjectRoot() {
|
|
|
5561
6193
|
return process.cwd();
|
|
5562
6194
|
}
|
|
5563
6195
|
}
|
|
5564
|
-
function
|
|
5565
|
-
|
|
6196
|
+
function getSessionScope() {
|
|
6197
|
+
try {
|
|
6198
|
+
const out = execSync9("tmux display-message -p '#{session_name}' 2>/dev/null", {
|
|
6199
|
+
encoding: "utf8",
|
|
6200
|
+
timeout: 1e3
|
|
6201
|
+
}).trim();
|
|
6202
|
+
if (!out) return "default";
|
|
6203
|
+
const dashIdx = out.lastIndexOf("-");
|
|
6204
|
+
return dashIdx > 0 ? out.slice(dashIdx + 1) : out;
|
|
6205
|
+
} catch {
|
|
6206
|
+
return "default";
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
function scanDir(dirPath, seen) {
|
|
5566
6210
|
const open = [];
|
|
5567
6211
|
const inProgress = [];
|
|
5568
6212
|
let done = 0;
|
|
5569
|
-
|
|
5570
|
-
if (!existsSync13(taskDir)) return { open, inProgress, done, total };
|
|
6213
|
+
if (!existsSync14(dirPath)) return { open, inProgress, done };
|
|
5571
6214
|
try {
|
|
5572
|
-
const files = readdirSync5(
|
|
5573
|
-
total = files.length;
|
|
6215
|
+
const files = readdirSync5(dirPath).filter((f) => f.endsWith(".md"));
|
|
5574
6216
|
for (const f of files) {
|
|
6217
|
+
if (seen.has(f)) continue;
|
|
6218
|
+
seen.add(f);
|
|
5575
6219
|
try {
|
|
5576
|
-
const content =
|
|
6220
|
+
const content = readFileSync12(path18.join(dirPath, f), "utf8");
|
|
5577
6221
|
const statusMatch = content.match(STATUS_RE);
|
|
5578
6222
|
const status = statusMatch ? statusMatch[1].toLowerCase() : null;
|
|
5579
6223
|
if (status === "done") {
|
|
@@ -5600,6 +6244,25 @@ function scanAgentTasks(agentId) {
|
|
|
5600
6244
|
}
|
|
5601
6245
|
} catch {
|
|
5602
6246
|
}
|
|
6247
|
+
return { open, inProgress, done };
|
|
6248
|
+
}
|
|
6249
|
+
function scanAgentTasks(agentId) {
|
|
6250
|
+
const open = [];
|
|
6251
|
+
const inProgress = [];
|
|
6252
|
+
let done = 0;
|
|
6253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6254
|
+
const scope = getSessionScope();
|
|
6255
|
+
const centralDir = path18.join(os9.homedir(), ".exe-os", "tasks", scope, agentId);
|
|
6256
|
+
const central = scanDir(centralDir, seen);
|
|
6257
|
+
open.push(...central.open);
|
|
6258
|
+
inProgress.push(...central.inProgress);
|
|
6259
|
+
done += central.done;
|
|
6260
|
+
const legacyDir = path18.join(getProjectRoot(), "exe", agentId);
|
|
6261
|
+
const legacy = scanDir(legacyDir, seen);
|
|
6262
|
+
open.push(...legacy.open);
|
|
6263
|
+
inProgress.push(...legacy.inProgress);
|
|
6264
|
+
done += legacy.done;
|
|
6265
|
+
const total = open.length + inProgress.length + done;
|
|
5603
6266
|
open.sort((a, b) => a.priority.localeCompare(b.priority));
|
|
5604
6267
|
inProgress.sort((a, b) => a.priority.localeCompare(b.priority));
|
|
5605
6268
|
return { open, inProgress, done, total };
|
|
@@ -5629,7 +6292,7 @@ function formatMandatory(agentId, result) {
|
|
|
5629
6292
|
const current = inProgress[0];
|
|
5630
6293
|
let stale = false;
|
|
5631
6294
|
try {
|
|
5632
|
-
const stat =
|
|
6295
|
+
const stat = statSync2(path18.join(getProjectRoot(), "exe", agentId, current.file));
|
|
5633
6296
|
const ageMin = (Date.now() - stat.mtimeMs) / 6e4;
|
|
5634
6297
|
if (ageMin > 30) stale = true;
|
|
5635
6298
|
} catch {
|
|
@@ -5681,13 +6344,13 @@ __export(worker_gate_exports, {
|
|
|
5681
6344
|
tryAcquireBackfillLock: () => tryAcquireBackfillLock,
|
|
5682
6345
|
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
5683
6346
|
});
|
|
5684
|
-
import { readdirSync as readdirSync6, writeFileSync as writeFileSync8, unlinkSync as
|
|
5685
|
-
import
|
|
6347
|
+
import { readdirSync as readdirSync6, writeFileSync as writeFileSync8, unlinkSync as unlinkSync8, mkdirSync as mkdirSync8, existsSync as existsSync15 } from "fs";
|
|
6348
|
+
import path19 from "path";
|
|
5686
6349
|
function tryAcquireWorkerSlot() {
|
|
5687
6350
|
try {
|
|
5688
6351
|
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5689
6352
|
const reservationId = `res-${process.pid}-${Date.now()}`;
|
|
5690
|
-
const reservationPath =
|
|
6353
|
+
const reservationPath = path19.join(WORKER_PID_DIR, `${reservationId}.pid`);
|
|
5691
6354
|
writeFileSync8(reservationPath, String(process.pid));
|
|
5692
6355
|
const files = readdirSync6(WORKER_PID_DIR);
|
|
5693
6356
|
let alive = 0;
|
|
@@ -5705,20 +6368,20 @@ function tryAcquireWorkerSlot() {
|
|
|
5705
6368
|
alive++;
|
|
5706
6369
|
} catch {
|
|
5707
6370
|
try {
|
|
5708
|
-
|
|
6371
|
+
unlinkSync8(path19.join(WORKER_PID_DIR, f));
|
|
5709
6372
|
} catch {
|
|
5710
6373
|
}
|
|
5711
6374
|
}
|
|
5712
6375
|
}
|
|
5713
6376
|
if (alive > MAX_CONCURRENT_WORKERS) {
|
|
5714
6377
|
try {
|
|
5715
|
-
|
|
6378
|
+
unlinkSync8(reservationPath);
|
|
5716
6379
|
} catch {
|
|
5717
6380
|
}
|
|
5718
6381
|
return false;
|
|
5719
6382
|
}
|
|
5720
6383
|
try {
|
|
5721
|
-
|
|
6384
|
+
unlinkSync8(reservationPath);
|
|
5722
6385
|
} catch {
|
|
5723
6386
|
}
|
|
5724
6387
|
return true;
|
|
@@ -5729,20 +6392,20 @@ function tryAcquireWorkerSlot() {
|
|
|
5729
6392
|
function registerWorkerPid(pid) {
|
|
5730
6393
|
try {
|
|
5731
6394
|
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5732
|
-
writeFileSync8(
|
|
6395
|
+
writeFileSync8(path19.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
5733
6396
|
} catch {
|
|
5734
6397
|
}
|
|
5735
6398
|
}
|
|
5736
6399
|
function cleanupWorkerPid() {
|
|
5737
6400
|
try {
|
|
5738
|
-
|
|
6401
|
+
unlinkSync8(path19.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
5739
6402
|
} catch {
|
|
5740
6403
|
}
|
|
5741
6404
|
}
|
|
5742
6405
|
function tryAcquireBackfillLock() {
|
|
5743
6406
|
try {
|
|
5744
6407
|
mkdirSync8(WORKER_PID_DIR, { recursive: true });
|
|
5745
|
-
if (
|
|
6408
|
+
if (existsSync15(BACKFILL_LOCK)) {
|
|
5746
6409
|
try {
|
|
5747
6410
|
const pid = parseInt(
|
|
5748
6411
|
__require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
|
|
@@ -5766,7 +6429,7 @@ function tryAcquireBackfillLock() {
|
|
|
5766
6429
|
}
|
|
5767
6430
|
function releaseBackfillLock() {
|
|
5768
6431
|
try {
|
|
5769
|
-
|
|
6432
|
+
unlinkSync8(BACKFILL_LOCK);
|
|
5770
6433
|
} catch {
|
|
5771
6434
|
}
|
|
5772
6435
|
}
|
|
@@ -5775,9 +6438,9 @@ var init_worker_gate = __esm({
|
|
|
5775
6438
|
"src/lib/worker-gate.ts"() {
|
|
5776
6439
|
"use strict";
|
|
5777
6440
|
init_config();
|
|
5778
|
-
WORKER_PID_DIR =
|
|
6441
|
+
WORKER_PID_DIR = path19.join(EXE_AI_DIR, "worker-pids");
|
|
5779
6442
|
MAX_CONCURRENT_WORKERS = 3;
|
|
5780
|
-
BACKFILL_LOCK =
|
|
6443
|
+
BACKFILL_LOCK = path19.join(WORKER_PID_DIR, "backfill.lock");
|
|
5781
6444
|
}
|
|
5782
6445
|
});
|
|
5783
6446
|
|
|
@@ -5860,6 +6523,232 @@ var init_compress = __esm({
|
|
|
5860
6523
|
}
|
|
5861
6524
|
});
|
|
5862
6525
|
|
|
6526
|
+
// src/lib/crdt-sync.ts
|
|
6527
|
+
var crdt_sync_exports = {};
|
|
6528
|
+
__export(crdt_sync_exports, {
|
|
6529
|
+
_setStatePath: () => _setStatePath,
|
|
6530
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
6531
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
6532
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
6533
|
+
getFullState: () => getFullState,
|
|
6534
|
+
getStateVector: () => getStateVector,
|
|
6535
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
6536
|
+
importExistingMemories: () => importExistingMemories,
|
|
6537
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
6538
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
6539
|
+
onUpdate: () => onUpdate,
|
|
6540
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
6541
|
+
readAllMemories: () => readAllMemories,
|
|
6542
|
+
rebuildFromDb: () => rebuildFromDb
|
|
6543
|
+
});
|
|
6544
|
+
import * as Y from "yjs";
|
|
6545
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync9 } from "fs";
|
|
6546
|
+
import path20 from "path";
|
|
6547
|
+
import { homedir } from "os";
|
|
6548
|
+
function getStatePath() {
|
|
6549
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
6550
|
+
}
|
|
6551
|
+
function _setStatePath(p) {
|
|
6552
|
+
_statePathOverride = p;
|
|
6553
|
+
}
|
|
6554
|
+
function initCrdtDoc() {
|
|
6555
|
+
if (doc) return doc;
|
|
6556
|
+
doc = new Y.Doc();
|
|
6557
|
+
const sp = getStatePath();
|
|
6558
|
+
if (existsSync16(sp)) {
|
|
6559
|
+
try {
|
|
6560
|
+
const state = readFileSync13(sp);
|
|
6561
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
6562
|
+
} catch {
|
|
6563
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
6564
|
+
try {
|
|
6565
|
+
unlinkSync9(sp);
|
|
6566
|
+
} catch {
|
|
6567
|
+
}
|
|
6568
|
+
rebuildFromDb().catch((err) => {
|
|
6569
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
6570
|
+
});
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
doc.on("update", () => {
|
|
6574
|
+
persistState();
|
|
6575
|
+
});
|
|
6576
|
+
return doc;
|
|
6577
|
+
}
|
|
6578
|
+
function getMemoriesMap() {
|
|
6579
|
+
const d = initCrdtDoc();
|
|
6580
|
+
return d.getMap("memories");
|
|
6581
|
+
}
|
|
6582
|
+
function getBehaviorsMap() {
|
|
6583
|
+
const d = initCrdtDoc();
|
|
6584
|
+
return d.getMap("behaviors");
|
|
6585
|
+
}
|
|
6586
|
+
function applyRemoteUpdate(update) {
|
|
6587
|
+
const d = initCrdtDoc();
|
|
6588
|
+
Y.applyUpdate(d, update);
|
|
6589
|
+
}
|
|
6590
|
+
function getFullState() {
|
|
6591
|
+
const d = initCrdtDoc();
|
|
6592
|
+
return Y.encodeStateAsUpdate(d);
|
|
6593
|
+
}
|
|
6594
|
+
function getDiffUpdate(remoteStateVector) {
|
|
6595
|
+
const d = initCrdtDoc();
|
|
6596
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
6597
|
+
}
|
|
6598
|
+
function getStateVector() {
|
|
6599
|
+
const d = initCrdtDoc();
|
|
6600
|
+
return Y.encodeStateVector(d);
|
|
6601
|
+
}
|
|
6602
|
+
function importExistingMemories(memories) {
|
|
6603
|
+
const map = getMemoriesMap();
|
|
6604
|
+
const d = initCrdtDoc();
|
|
6605
|
+
let imported = 0;
|
|
6606
|
+
d.transact(() => {
|
|
6607
|
+
for (const mem of memories) {
|
|
6608
|
+
if (!mem.id) continue;
|
|
6609
|
+
if (map.has(mem.id)) continue;
|
|
6610
|
+
const entry = new Y.Map();
|
|
6611
|
+
entry.set("id", mem.id);
|
|
6612
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
6613
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
6614
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
6615
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
6616
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
6617
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
6618
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
6619
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
6620
|
+
entry.set("version", mem.version ?? 0);
|
|
6621
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
6622
|
+
entry.set("scope", mem.scope ?? "business");
|
|
6623
|
+
map.set(mem.id, entry);
|
|
6624
|
+
imported++;
|
|
6625
|
+
}
|
|
6626
|
+
});
|
|
6627
|
+
return imported;
|
|
6628
|
+
}
|
|
6629
|
+
function importExistingBehaviors(behaviors) {
|
|
6630
|
+
const map = getBehaviorsMap();
|
|
6631
|
+
const d = initCrdtDoc();
|
|
6632
|
+
let imported = 0;
|
|
6633
|
+
d.transact(() => {
|
|
6634
|
+
for (const beh of behaviors) {
|
|
6635
|
+
if (!beh.id) continue;
|
|
6636
|
+
if (map.has(beh.id)) continue;
|
|
6637
|
+
const entry = new Y.Map();
|
|
6638
|
+
entry.set("id", beh.id);
|
|
6639
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
6640
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
6641
|
+
entry.set("domain", beh.domain ?? null);
|
|
6642
|
+
entry.set("content", beh.content ?? null);
|
|
6643
|
+
entry.set("active", beh.active ?? 1);
|
|
6644
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
6645
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
6646
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
6647
|
+
map.set(beh.id, entry);
|
|
6648
|
+
imported++;
|
|
6649
|
+
}
|
|
6650
|
+
});
|
|
6651
|
+
return imported;
|
|
6652
|
+
}
|
|
6653
|
+
function readAllMemories() {
|
|
6654
|
+
const map = getMemoriesMap();
|
|
6655
|
+
const records = [];
|
|
6656
|
+
map.forEach((entry, id) => {
|
|
6657
|
+
records.push({
|
|
6658
|
+
id,
|
|
6659
|
+
agent_id: entry.get("agent_id"),
|
|
6660
|
+
agent_role: entry.get("agent_role"),
|
|
6661
|
+
session_id: entry.get("session_id"),
|
|
6662
|
+
timestamp: entry.get("timestamp"),
|
|
6663
|
+
tool_name: entry.get("tool_name"),
|
|
6664
|
+
project_name: entry.get("project_name"),
|
|
6665
|
+
has_error: entry.get("has_error"),
|
|
6666
|
+
raw_text: entry.get("raw_text"),
|
|
6667
|
+
version: entry.get("version"),
|
|
6668
|
+
author_device_id: entry.get("author_device_id"),
|
|
6669
|
+
scope: entry.get("scope")
|
|
6670
|
+
});
|
|
6671
|
+
});
|
|
6672
|
+
return records;
|
|
6673
|
+
}
|
|
6674
|
+
function readAllBehaviors() {
|
|
6675
|
+
const map = getBehaviorsMap();
|
|
6676
|
+
const records = [];
|
|
6677
|
+
map.forEach((entry, id) => {
|
|
6678
|
+
records.push({
|
|
6679
|
+
id,
|
|
6680
|
+
agent_id: entry.get("agent_id"),
|
|
6681
|
+
project_name: entry.get("project_name"),
|
|
6682
|
+
domain: entry.get("domain"),
|
|
6683
|
+
content: entry.get("content"),
|
|
6684
|
+
active: entry.get("active"),
|
|
6685
|
+
priority: entry.get("priority"),
|
|
6686
|
+
created_at: entry.get("created_at"),
|
|
6687
|
+
updated_at: entry.get("updated_at")
|
|
6688
|
+
});
|
|
6689
|
+
});
|
|
6690
|
+
return records;
|
|
6691
|
+
}
|
|
6692
|
+
function onUpdate(callback) {
|
|
6693
|
+
const d = initCrdtDoc();
|
|
6694
|
+
const handler = (update) => callback(update);
|
|
6695
|
+
d.on("update", handler);
|
|
6696
|
+
return () => d.off("update", handler);
|
|
6697
|
+
}
|
|
6698
|
+
function persistState() {
|
|
6699
|
+
if (!doc) return;
|
|
6700
|
+
try {
|
|
6701
|
+
const sp = getStatePath();
|
|
6702
|
+
const dir = path20.dirname(sp);
|
|
6703
|
+
if (!existsSync16(dir)) mkdirSync9(dir, { recursive: true });
|
|
6704
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
6705
|
+
writeFileSync9(sp, Buffer.from(state));
|
|
6706
|
+
} catch {
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
function isCrdtSyncEnabled() {
|
|
6710
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
6711
|
+
}
|
|
6712
|
+
async function rebuildFromDb() {
|
|
6713
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
6714
|
+
const client = getClient2();
|
|
6715
|
+
const result = await client.execute(
|
|
6716
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
6717
|
+
);
|
|
6718
|
+
const memories = result.rows.map((row) => ({
|
|
6719
|
+
id: String(row.id),
|
|
6720
|
+
agent_id: row.agent_id,
|
|
6721
|
+
agent_role: row.agent_role,
|
|
6722
|
+
session_id: row.session_id,
|
|
6723
|
+
timestamp: row.timestamp,
|
|
6724
|
+
tool_name: row.tool_name,
|
|
6725
|
+
project_name: row.project_name,
|
|
6726
|
+
has_error: row.has_error,
|
|
6727
|
+
raw_text: row.raw_text,
|
|
6728
|
+
version: row.version,
|
|
6729
|
+
author_device_id: row.author_device_id,
|
|
6730
|
+
scope: row.scope
|
|
6731
|
+
}));
|
|
6732
|
+
const count = importExistingMemories(memories);
|
|
6733
|
+
persistState();
|
|
6734
|
+
return count;
|
|
6735
|
+
}
|
|
6736
|
+
function destroyCrdtDoc() {
|
|
6737
|
+
if (doc) {
|
|
6738
|
+
doc.destroy();
|
|
6739
|
+
doc = null;
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
6742
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
6743
|
+
var init_crdt_sync = __esm({
|
|
6744
|
+
"src/lib/crdt-sync.ts"() {
|
|
6745
|
+
"use strict";
|
|
6746
|
+
DEFAULT_STATE_PATH = path20.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
6747
|
+
_statePathOverride = null;
|
|
6748
|
+
doc = null;
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6751
|
+
|
|
5863
6752
|
// src/lib/cloud-sync.ts
|
|
5864
6753
|
var cloud_sync_exports = {};
|
|
5865
6754
|
__export(cloud_sync_exports, {
|
|
@@ -5888,16 +6777,16 @@ __export(cloud_sync_exports, {
|
|
|
5888
6777
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
5889
6778
|
recordRosterDeletion: () => recordRosterDeletion
|
|
5890
6779
|
});
|
|
5891
|
-
import { readFileSync as
|
|
6780
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, readdirSync as readdirSync7, mkdirSync as mkdirSync10, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
5892
6781
|
import crypto7 from "crypto";
|
|
5893
|
-
import
|
|
5894
|
-
import { homedir } from "os";
|
|
6782
|
+
import path21 from "path";
|
|
6783
|
+
import { homedir as homedir2 } from "os";
|
|
5895
6784
|
function sqlSafe(v) {
|
|
5896
6785
|
return v === void 0 ? null : v;
|
|
5897
6786
|
}
|
|
5898
6787
|
function logError(msg) {
|
|
5899
6788
|
try {
|
|
5900
|
-
const logPath =
|
|
6789
|
+
const logPath = path21.join(homedir2(), ".exe-os", "workers.log");
|
|
5901
6790
|
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
5902
6791
|
`);
|
|
5903
6792
|
} catch {
|
|
@@ -5905,20 +6794,20 @@ function logError(msg) {
|
|
|
5905
6794
|
}
|
|
5906
6795
|
async function withRosterLock(fn) {
|
|
5907
6796
|
try {
|
|
5908
|
-
const fd =
|
|
5909
|
-
|
|
5910
|
-
|
|
6797
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
6798
|
+
closeSync2(fd);
|
|
6799
|
+
writeFileSync10(ROSTER_LOCK_PATH, String(Date.now()));
|
|
5911
6800
|
} catch (err) {
|
|
5912
6801
|
if (err.code === "EEXIST") {
|
|
5913
6802
|
try {
|
|
5914
|
-
const ts = parseInt(
|
|
6803
|
+
const ts = parseInt(readFileSync14(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
5915
6804
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
5916
6805
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
5917
6806
|
}
|
|
5918
|
-
|
|
5919
|
-
const fd =
|
|
5920
|
-
|
|
5921
|
-
|
|
6807
|
+
unlinkSync10(ROSTER_LOCK_PATH);
|
|
6808
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
6809
|
+
closeSync2(fd);
|
|
6810
|
+
writeFileSync10(ROSTER_LOCK_PATH, String(Date.now()));
|
|
5922
6811
|
} catch (retryErr) {
|
|
5923
6812
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
5924
6813
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -5931,7 +6820,7 @@ async function withRosterLock(fn) {
|
|
|
5931
6820
|
return await fn();
|
|
5932
6821
|
} finally {
|
|
5933
6822
|
try {
|
|
5934
|
-
|
|
6823
|
+
unlinkSync10(ROSTER_LOCK_PATH);
|
|
5935
6824
|
} catch {
|
|
5936
6825
|
}
|
|
5937
6826
|
}
|
|
@@ -6076,29 +6965,75 @@ async function cloudSync(config) {
|
|
|
6076
6965
|
const pullResult = await cloudPull(lastPullVersion, config);
|
|
6077
6966
|
let pulled = 0;
|
|
6078
6967
|
if (pullResult.records.length > 0) {
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6968
|
+
if (isCrdtSyncEnabled()) {
|
|
6969
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
6970
|
+
initCrdtDoc2();
|
|
6971
|
+
importExistingMemories2(
|
|
6972
|
+
pullResult.records.map((rec) => ({
|
|
6973
|
+
id: String(rec.id ?? ""),
|
|
6974
|
+
agent_id: rec.agent_id,
|
|
6975
|
+
agent_role: rec.agent_role,
|
|
6976
|
+
session_id: rec.session_id,
|
|
6977
|
+
timestamp: rec.timestamp,
|
|
6978
|
+
tool_name: rec.tool_name,
|
|
6979
|
+
project_name: rec.project_name,
|
|
6980
|
+
has_error: rec.has_error ?? 0,
|
|
6981
|
+
raw_text: rec.raw_text ?? "",
|
|
6982
|
+
version: rec.version ?? 0,
|
|
6983
|
+
author_device_id: rec.author_device_id,
|
|
6984
|
+
scope: rec.scope ?? "business"
|
|
6985
|
+
}))
|
|
6986
|
+
);
|
|
6987
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
6988
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
6989
|
+
const stmts = merged.map((rec) => ({
|
|
6990
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
6991
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
6992
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
6993
|
+
author_device_id, scope)
|
|
6994
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6995
|
+
args: [
|
|
6996
|
+
sqlSafe(rec.id),
|
|
6997
|
+
sqlSafe(rec.agent_id),
|
|
6998
|
+
sqlSafe(rec.agent_role),
|
|
6999
|
+
sqlSafe(rec.session_id),
|
|
7000
|
+
sqlSafe(rec.timestamp),
|
|
7001
|
+
sqlSafe(rec.tool_name),
|
|
7002
|
+
sqlSafe(rec.project_name),
|
|
7003
|
+
sqlSafe(rec.has_error ?? 0),
|
|
7004
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
7005
|
+
sqlSafe(rec.version ?? 0),
|
|
7006
|
+
sqlSafe(rec.author_device_id),
|
|
7007
|
+
sqlSafe(rec.scope ?? "business")
|
|
7008
|
+
]
|
|
7009
|
+
}));
|
|
7010
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
7011
|
+
pulled = pullResult.records.length;
|
|
7012
|
+
} else {
|
|
7013
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
7014
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
7015
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
7016
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
7017
|
+
author_device_id, scope)
|
|
7018
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7019
|
+
args: [
|
|
7020
|
+
sqlSafe(rec.id),
|
|
7021
|
+
sqlSafe(rec.agent_id),
|
|
7022
|
+
sqlSafe(rec.agent_role),
|
|
7023
|
+
sqlSafe(rec.session_id),
|
|
7024
|
+
sqlSafe(rec.timestamp),
|
|
7025
|
+
sqlSafe(rec.tool_name),
|
|
7026
|
+
sqlSafe(rec.project_name),
|
|
7027
|
+
sqlSafe(rec.has_error ?? 0),
|
|
7028
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
7029
|
+
sqlSafe(rec.version ?? 0),
|
|
7030
|
+
sqlSafe(rec.author_device_id),
|
|
7031
|
+
sqlSafe(rec.scope ?? "business")
|
|
7032
|
+
]
|
|
7033
|
+
}));
|
|
7034
|
+
await client.batch(stmts, "write");
|
|
7035
|
+
pulled = pullResult.records.length;
|
|
7036
|
+
}
|
|
6102
7037
|
}
|
|
6103
7038
|
if (pullResult.maxVersion > lastPullVersion) {
|
|
6104
7039
|
await client.execute({
|
|
@@ -6246,8 +7181,8 @@ async function cloudSync(config) {
|
|
|
6246
7181
|
try {
|
|
6247
7182
|
const employees = await loadEmployees();
|
|
6248
7183
|
rosterResult.employees = employees.length;
|
|
6249
|
-
const idDir =
|
|
6250
|
-
if (
|
|
7184
|
+
const idDir = path21.join(EXE_AI_DIR, "identity");
|
|
7185
|
+
if (existsSync17(idDir)) {
|
|
6251
7186
|
rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
|
|
6252
7187
|
}
|
|
6253
7188
|
} catch {
|
|
@@ -6268,48 +7203,48 @@ async function cloudSync(config) {
|
|
|
6268
7203
|
function recordRosterDeletion(name) {
|
|
6269
7204
|
let deletions = [];
|
|
6270
7205
|
try {
|
|
6271
|
-
if (
|
|
6272
|
-
deletions = JSON.parse(
|
|
7206
|
+
if (existsSync17(ROSTER_DELETIONS_PATH)) {
|
|
7207
|
+
deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
6273
7208
|
}
|
|
6274
7209
|
} catch {
|
|
6275
7210
|
}
|
|
6276
7211
|
if (!deletions.includes(name)) deletions.push(name);
|
|
6277
|
-
|
|
7212
|
+
writeFileSync10(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
6278
7213
|
}
|
|
6279
7214
|
function consumeRosterDeletions() {
|
|
6280
7215
|
try {
|
|
6281
|
-
if (!
|
|
6282
|
-
const deletions = JSON.parse(
|
|
6283
|
-
|
|
7216
|
+
if (!existsSync17(ROSTER_DELETIONS_PATH)) return [];
|
|
7217
|
+
const deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
7218
|
+
writeFileSync10(ROSTER_DELETIONS_PATH, "[]");
|
|
6284
7219
|
return deletions;
|
|
6285
7220
|
} catch {
|
|
6286
7221
|
return [];
|
|
6287
7222
|
}
|
|
6288
7223
|
}
|
|
6289
7224
|
function buildRosterBlob(paths) {
|
|
6290
|
-
const rosterPath = paths?.rosterPath ??
|
|
6291
|
-
const identityDir = paths?.identityDir ??
|
|
6292
|
-
const configPath = paths?.configPath ??
|
|
7225
|
+
const rosterPath = paths?.rosterPath ?? path21.join(EXE_AI_DIR, "exe-employees.json");
|
|
7226
|
+
const identityDir = paths?.identityDir ?? path21.join(EXE_AI_DIR, "identity");
|
|
7227
|
+
const configPath = paths?.configPath ?? path21.join(EXE_AI_DIR, "config.json");
|
|
6293
7228
|
let roster = [];
|
|
6294
|
-
if (
|
|
7229
|
+
if (existsSync17(rosterPath)) {
|
|
6295
7230
|
try {
|
|
6296
|
-
roster = JSON.parse(
|
|
7231
|
+
roster = JSON.parse(readFileSync14(rosterPath, "utf-8"));
|
|
6297
7232
|
} catch {
|
|
6298
7233
|
}
|
|
6299
7234
|
}
|
|
6300
7235
|
const identities = {};
|
|
6301
|
-
if (
|
|
7236
|
+
if (existsSync17(identityDir)) {
|
|
6302
7237
|
for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
6303
7238
|
try {
|
|
6304
|
-
identities[file] =
|
|
7239
|
+
identities[file] = readFileSync14(path21.join(identityDir, file), "utf-8");
|
|
6305
7240
|
} catch {
|
|
6306
7241
|
}
|
|
6307
7242
|
}
|
|
6308
7243
|
}
|
|
6309
7244
|
let config;
|
|
6310
|
-
if (
|
|
7245
|
+
if (existsSync17(configPath)) {
|
|
6311
7246
|
try {
|
|
6312
|
-
config = JSON.parse(
|
|
7247
|
+
config = JSON.parse(readFileSync14(configPath, "utf-8"));
|
|
6313
7248
|
} catch {
|
|
6314
7249
|
}
|
|
6315
7250
|
}
|
|
@@ -6385,23 +7320,23 @@ async function cloudPullRoster(config) {
|
|
|
6385
7320
|
}
|
|
6386
7321
|
}
|
|
6387
7322
|
function mergeConfig(remoteConfig, configPath) {
|
|
6388
|
-
const cfgPath = configPath ??
|
|
7323
|
+
const cfgPath = configPath ?? path21.join(EXE_AI_DIR, "config.json");
|
|
6389
7324
|
let local = {};
|
|
6390
|
-
if (
|
|
7325
|
+
if (existsSync17(cfgPath)) {
|
|
6391
7326
|
try {
|
|
6392
|
-
local = JSON.parse(
|
|
7327
|
+
local = JSON.parse(readFileSync14(cfgPath, "utf-8"));
|
|
6393
7328
|
} catch {
|
|
6394
7329
|
}
|
|
6395
7330
|
}
|
|
6396
7331
|
const merged = { ...remoteConfig, ...local };
|
|
6397
|
-
const dir =
|
|
6398
|
-
if (!
|
|
6399
|
-
|
|
7332
|
+
const dir = path21.dirname(cfgPath);
|
|
7333
|
+
if (!existsSync17(dir)) mkdirSync10(dir, { recursive: true });
|
|
7334
|
+
writeFileSync10(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
6400
7335
|
}
|
|
6401
7336
|
async function mergeRosterFromRemote(remote, paths) {
|
|
6402
7337
|
return withRosterLock(async () => {
|
|
6403
7338
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
6404
|
-
const identityDir = paths?.identityDir ??
|
|
7339
|
+
const identityDir = paths?.identityDir ?? path21.join(EXE_AI_DIR, "identity");
|
|
6405
7340
|
const localEmployees = await loadEmployees(rosterPath);
|
|
6406
7341
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
6407
7342
|
let added = 0;
|
|
@@ -6422,15 +7357,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
6422
7357
|
) ?? lookupKey;
|
|
6423
7358
|
const remoteIdentity = remote.identities[matchedKey];
|
|
6424
7359
|
if (remoteIdentity) {
|
|
6425
|
-
if (!
|
|
6426
|
-
const idPath =
|
|
7360
|
+
if (!existsSync17(identityDir)) mkdirSync10(identityDir, { recursive: true });
|
|
7361
|
+
const idPath = path21.join(identityDir, `${remoteEmp.name}.md`);
|
|
6427
7362
|
let localIdentity = null;
|
|
6428
7363
|
try {
|
|
6429
|
-
localIdentity =
|
|
7364
|
+
localIdentity = existsSync17(idPath) ? readFileSync14(idPath, "utf-8") : null;
|
|
6430
7365
|
} catch {
|
|
6431
7366
|
}
|
|
6432
7367
|
if (localIdentity !== remoteIdentity) {
|
|
6433
|
-
|
|
7368
|
+
writeFileSync10(idPath, remoteIdentity, "utf-8");
|
|
6434
7369
|
identitiesUpdated++;
|
|
6435
7370
|
}
|
|
6436
7371
|
}
|
|
@@ -6883,13 +7818,14 @@ var init_cloud_sync = __esm({
|
|
|
6883
7818
|
init_compress();
|
|
6884
7819
|
init_license();
|
|
6885
7820
|
init_config();
|
|
7821
|
+
init_crdt_sync();
|
|
6886
7822
|
init_employees();
|
|
6887
7823
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
6888
7824
|
FETCH_TIMEOUT_MS = 3e4;
|
|
6889
7825
|
PUSH_BATCH_SIZE = 5e3;
|
|
6890
|
-
ROSTER_LOCK_PATH =
|
|
7826
|
+
ROSTER_LOCK_PATH = path21.join(EXE_AI_DIR, "roster-merge.lock");
|
|
6891
7827
|
LOCK_STALE_MS = 3e4;
|
|
6892
|
-
ROSTER_DELETIONS_PATH =
|
|
7828
|
+
ROSTER_DELETIONS_PATH = path21.join(EXE_AI_DIR, "roster-deletions.json");
|
|
6893
7829
|
}
|
|
6894
7830
|
});
|
|
6895
7831
|
|
|
@@ -7071,10 +8007,10 @@ var init_schedules = __esm({
|
|
|
7071
8007
|
|
|
7072
8008
|
// src/bin/exe-boot.ts
|
|
7073
8009
|
init_employees();
|
|
7074
|
-
import
|
|
8010
|
+
import path22 from "path";
|
|
7075
8011
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
7076
|
-
import { existsSync as
|
|
7077
|
-
import
|
|
8012
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
|
|
8013
|
+
import os10 from "os";
|
|
7078
8014
|
|
|
7079
8015
|
// src/lib/employee-templates.ts
|
|
7080
8016
|
init_global_procedures();
|
|
@@ -7532,12 +8468,13 @@ init_task_scope();
|
|
|
7532
8468
|
|
|
7533
8469
|
// src/lib/is-main.ts
|
|
7534
8470
|
import { realpathSync } from "fs";
|
|
7535
|
-
import { fileURLToPath as
|
|
8471
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7536
8472
|
function isMainModule(importMetaUrl) {
|
|
7537
8473
|
if (process.argv[1] == null) return false;
|
|
8474
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
7538
8475
|
try {
|
|
7539
8476
|
const scriptPath = realpathSync(process.argv[1]);
|
|
7540
|
-
const modulePath = realpathSync(
|
|
8477
|
+
const modulePath = realpathSync(fileURLToPath3(importMetaUrl));
|
|
7541
8478
|
return scriptPath === modulePath;
|
|
7542
8479
|
} catch {
|
|
7543
8480
|
return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
|
|
@@ -7549,19 +8486,19 @@ init_notifications();
|
|
|
7549
8486
|
|
|
7550
8487
|
// src/adapters/claude/active-agent.ts
|
|
7551
8488
|
init_config();
|
|
7552
|
-
import { readFileSync as
|
|
8489
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync7, readdirSync as readdirSync4 } from "fs";
|
|
7553
8490
|
import { execSync as execSync8 } from "child_process";
|
|
7554
|
-
import
|
|
8491
|
+
import path17 from "path";
|
|
7555
8492
|
|
|
7556
8493
|
// src/adapters/claude/session-key.ts
|
|
7557
8494
|
init_session_key();
|
|
7558
8495
|
|
|
7559
8496
|
// src/adapters/claude/active-agent.ts
|
|
7560
8497
|
init_employees();
|
|
7561
|
-
var CACHE_DIR =
|
|
8498
|
+
var CACHE_DIR = path17.join(EXE_AI_DIR, "session-cache");
|
|
7562
8499
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
7563
8500
|
function getMarkerPath() {
|
|
7564
|
-
return
|
|
8501
|
+
return path17.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
7565
8502
|
}
|
|
7566
8503
|
function writeActiveAgent(agentId, agentRole) {
|
|
7567
8504
|
try {
|
|
@@ -7576,11 +8513,11 @@ function writeActiveAgent(agentId, agentRole) {
|
|
|
7576
8513
|
function cleanupSessionMarkers() {
|
|
7577
8514
|
const key = getSessionKey();
|
|
7578
8515
|
try {
|
|
7579
|
-
|
|
8516
|
+
unlinkSync7(path17.join(CACHE_DIR, `active-agent-${key}.json`));
|
|
7580
8517
|
} catch {
|
|
7581
8518
|
}
|
|
7582
8519
|
try {
|
|
7583
|
-
|
|
8520
|
+
unlinkSync7(path17.join(CACHE_DIR, "active-agent-undefined.json"));
|
|
7584
8521
|
} catch {
|
|
7585
8522
|
}
|
|
7586
8523
|
}
|
|
@@ -7670,7 +8607,7 @@ async function boot(options) {
|
|
|
7670
8607
|
const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
|
|
7671
8608
|
for (const dir of employeeDirs) {
|
|
7672
8609
|
const employee = dir.name;
|
|
7673
|
-
const taskDir =
|
|
8610
|
+
const taskDir = path22.join(exeDir, employee);
|
|
7674
8611
|
let files;
|
|
7675
8612
|
try {
|
|
7676
8613
|
files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
|
|
@@ -7681,7 +8618,7 @@ async function boot(options) {
|
|
|
7681
8618
|
const taskFilePath = `exe/${employee}/${file}`;
|
|
7682
8619
|
let content;
|
|
7683
8620
|
try {
|
|
7684
|
-
content = readFs(
|
|
8621
|
+
content = readFs(path22.join(taskDir, file), "utf8");
|
|
7685
8622
|
} catch {
|
|
7686
8623
|
continue;
|
|
7687
8624
|
}
|
|
@@ -7767,12 +8704,12 @@ async function boot(options) {
|
|
|
7767
8704
|
}
|
|
7768
8705
|
try {
|
|
7769
8706
|
for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
|
|
7770
|
-
const reviewDir =
|
|
7771
|
-
if (
|
|
8707
|
+
const reviewDir = path22.join(process.cwd(), "exe", reviewDirName);
|
|
8708
|
+
if (existsSync18(reviewDir)) {
|
|
7772
8709
|
for (const f of readdirSync8(reviewDir)) {
|
|
7773
8710
|
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
7774
8711
|
try {
|
|
7775
|
-
|
|
8712
|
+
unlinkSync11(path22.join(reviewDir, f));
|
|
7776
8713
|
} catch {
|
|
7777
8714
|
}
|
|
7778
8715
|
}
|
|
@@ -7818,12 +8755,12 @@ async function boot(options) {
|
|
|
7818
8755
|
});
|
|
7819
8756
|
const taskFile = String(r.task_file);
|
|
7820
8757
|
try {
|
|
7821
|
-
const filePath =
|
|
7822
|
-
if (
|
|
7823
|
-
let content =
|
|
8758
|
+
const filePath = path22.join(process.cwd(), taskFile);
|
|
8759
|
+
if (existsSync18(filePath)) {
|
|
8760
|
+
let content = readFileSync15(filePath, "utf8");
|
|
7824
8761
|
content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
|
|
7825
|
-
const { writeFileSync:
|
|
7826
|
-
|
|
8762
|
+
const { writeFileSync: writeFileSync11 } = await import("fs");
|
|
8763
|
+
writeFileSync11(filePath, content);
|
|
7827
8764
|
}
|
|
7828
8765
|
} catch {
|
|
7829
8766
|
}
|
|
@@ -8154,13 +9091,13 @@ async function boot(options) {
|
|
|
8154
9091
|
}));
|
|
8155
9092
|
const assignedResult = await client.execute({
|
|
8156
9093
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
8157
|
-
WHERE project_name = ? AND
|
|
9094
|
+
WHERE project_name = ? AND assigned_by = ?
|
|
8158
9095
|
AND created_at > datetime('now', '-7 days')${pScope.sql}`,
|
|
8159
9096
|
args: [p.projectName, coordinatorName, ...pScope.args]
|
|
8160
9097
|
});
|
|
8161
9098
|
const tasksAssigned = Number(assignedResult.rows[0]?.cnt) || 0;
|
|
8162
9099
|
if (tasksAssigned > 0) {
|
|
8163
|
-
const exeEntry = activity.find((a) => a.name === coordinatorName || a.name
|
|
9100
|
+
const exeEntry = activity.find((a) => a.name === coordinatorName || isCoordinatorName(a.name));
|
|
8164
9101
|
if (exeEntry) {
|
|
8165
9102
|
exeEntry.tasksAssigned = tasksAssigned;
|
|
8166
9103
|
} else {
|
|
@@ -8184,7 +9121,7 @@ async function boot(options) {
|
|
|
8184
9121
|
const projName = s.projectDir.split("/").pop() ?? "";
|
|
8185
9122
|
const proj = projects.find((p) => p.projectName === projName);
|
|
8186
9123
|
if (!proj || proj.sessionName) continue;
|
|
8187
|
-
if (s.agentId === coordinatorName || s.agentId
|
|
9124
|
+
if (s.agentId === coordinatorName || isCoordinatorName(s.agentId)) {
|
|
8188
9125
|
proj.sessionName = s.windowName;
|
|
8189
9126
|
} else if (s.parentExe) {
|
|
8190
9127
|
proj.sessionName = s.parentExe;
|
|
@@ -8292,19 +9229,19 @@ async function boot(options) {
|
|
|
8292
9229
|
})()
|
|
8293
9230
|
]);
|
|
8294
9231
|
try {
|
|
8295
|
-
const configPath =
|
|
8296
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
9232
|
+
const configPath = path22.join(
|
|
9233
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
|
|
8297
9234
|
"config.json"
|
|
8298
9235
|
);
|
|
8299
|
-
if (
|
|
8300
|
-
const raw = JSON.parse(
|
|
9236
|
+
if (existsSync18(configPath)) {
|
|
9237
|
+
const raw = JSON.parse(readFileSync15(configPath, "utf8"));
|
|
8301
9238
|
briefData.cloudConnected = !!(raw.cloud || raw.turso);
|
|
8302
9239
|
}
|
|
8303
9240
|
} catch {
|
|
8304
9241
|
}
|
|
8305
9242
|
try {
|
|
8306
|
-
const backfillFlagPath =
|
|
8307
|
-
const isBackfillNeeded = () =>
|
|
9243
|
+
const backfillFlagPath = path22.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
9244
|
+
const isBackfillNeeded = () => existsSync18(backfillFlagPath);
|
|
8308
9245
|
const coverageResult = await client.execute({
|
|
8309
9246
|
sql: `SELECT COUNT(*) as total,
|
|
8310
9247
|
SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
|
|
@@ -8326,16 +9263,16 @@ async function boot(options) {
|
|
|
8326
9263
|
let daemonRunning = false;
|
|
8327
9264
|
let daemonUptime;
|
|
8328
9265
|
let daemonRequestsServed;
|
|
8329
|
-
const socketPath =
|
|
8330
|
-
if (
|
|
9266
|
+
const socketPath = path22.join(EXE_AI_DIR, "exed.sock");
|
|
9267
|
+
if (existsSync18(socketPath)) {
|
|
8331
9268
|
try {
|
|
8332
|
-
const
|
|
9269
|
+
const net2 = await import("net");
|
|
8333
9270
|
const health = await new Promise((resolve) => {
|
|
8334
9271
|
const timer = setTimeout(() => {
|
|
8335
9272
|
sock.destroy();
|
|
8336
9273
|
resolve(null);
|
|
8337
9274
|
}, 2e3);
|
|
8338
|
-
const sock =
|
|
9275
|
+
const sock = net2.createConnection({ path: socketPath });
|
|
8339
9276
|
let buf = "";
|
|
8340
9277
|
sock.on("connect", () => {
|
|
8341
9278
|
sock.write(JSON.stringify({ id: "boot-health", type: "health" }) + "\n");
|
|
@@ -8369,10 +9306,10 @@ async function boot(options) {
|
|
|
8369
9306
|
}
|
|
8370
9307
|
}
|
|
8371
9308
|
if (!daemonRunning) {
|
|
8372
|
-
const pidPath =
|
|
8373
|
-
if (
|
|
9309
|
+
const pidPath = path22.join(EXE_AI_DIR, "exed.pid");
|
|
9310
|
+
if (existsSync18(pidPath)) {
|
|
8374
9311
|
try {
|
|
8375
|
-
const pid = parseInt(
|
|
9312
|
+
const pid = parseInt(readFileSync15(pidPath, "utf8").trim(), 10);
|
|
8376
9313
|
if (pid > 0) {
|
|
8377
9314
|
process.kill(pid, 0);
|
|
8378
9315
|
daemonRunning = true;
|
|
@@ -8383,10 +9320,10 @@ async function boot(options) {
|
|
|
8383
9320
|
}
|
|
8384
9321
|
if (nullCount === 0) {
|
|
8385
9322
|
try {
|
|
8386
|
-
const flagPath =
|
|
8387
|
-
if (
|
|
8388
|
-
const { unlinkSync:
|
|
8389
|
-
|
|
9323
|
+
const flagPath = path22.join(EXE_AI_DIR, "session-cache", "needs-backfill");
|
|
9324
|
+
if (existsSync18(flagPath)) {
|
|
9325
|
+
const { unlinkSync: unlinkSync12 } = await import("fs");
|
|
9326
|
+
unlinkSync12(flagPath);
|
|
8390
9327
|
}
|
|
8391
9328
|
} catch {
|
|
8392
9329
|
}
|
|
@@ -8407,26 +9344,26 @@ async function boot(options) {
|
|
|
8407
9344
|
if (!tryAcquireWorkerSlot2()) {
|
|
8408
9345
|
process.stderr.write("[exe-boot] Backfill needed but worker gate full \u2014 skipping\n");
|
|
8409
9346
|
} else {
|
|
8410
|
-
const { spawn } = await import("child_process");
|
|
8411
|
-
const { fileURLToPath:
|
|
8412
|
-
const thisFile =
|
|
8413
|
-
const backfillPath =
|
|
8414
|
-
if (
|
|
8415
|
-
const { openSync:
|
|
8416
|
-
const workerLogPath =
|
|
9347
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
9348
|
+
const { fileURLToPath: fileURLToPath4 } = await import("url");
|
|
9349
|
+
const thisFile = fileURLToPath4(import.meta.url);
|
|
9350
|
+
const backfillPath = path22.resolve(path22.dirname(thisFile), "backfill-vectors.js");
|
|
9351
|
+
if (existsSync18(backfillPath)) {
|
|
9352
|
+
const { openSync: openSync3, closeSync: closeSync3 } = await import("fs");
|
|
9353
|
+
const workerLogPath = path22.join(EXE_AI_DIR, "workers.log");
|
|
8417
9354
|
let stderrFd = "ignore";
|
|
8418
9355
|
try {
|
|
8419
|
-
stderrFd =
|
|
9356
|
+
stderrFd = openSync3(workerLogPath, "a");
|
|
8420
9357
|
} catch {
|
|
8421
9358
|
}
|
|
8422
|
-
const child =
|
|
9359
|
+
const child = spawn2(process.execPath, [backfillPath], {
|
|
8423
9360
|
detached: true,
|
|
8424
9361
|
stdio: ["ignore", "ignore", stderrFd]
|
|
8425
9362
|
});
|
|
8426
9363
|
child.unref();
|
|
8427
9364
|
if (child.pid) registerWorkerPid2(child.pid);
|
|
8428
9365
|
if (typeof stderrFd === "number") try {
|
|
8429
|
-
|
|
9366
|
+
closeSync3(stderrFd);
|
|
8430
9367
|
} catch {
|
|
8431
9368
|
}
|
|
8432
9369
|
briefData.embedding.backfillRunning = true;
|
|
@@ -8438,13 +9375,13 @@ async function boot(options) {
|
|
|
8438
9375
|
} catch {
|
|
8439
9376
|
}
|
|
8440
9377
|
try {
|
|
8441
|
-
const { fileURLToPath:
|
|
8442
|
-
const thisFile =
|
|
9378
|
+
const { fileURLToPath: fileURLToPath4 } = await import("url");
|
|
9379
|
+
const thisFile = fileURLToPath4(import.meta.url);
|
|
8443
9380
|
const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
|
|
8444
9381
|
const missing = [];
|
|
8445
9382
|
for (const bin of criticalBinaries) {
|
|
8446
|
-
const binPath =
|
|
8447
|
-
if (!
|
|
9383
|
+
const binPath = path22.resolve(path22.dirname(thisFile), bin);
|
|
9384
|
+
if (!existsSync18(binPath)) {
|
|
8448
9385
|
missing.push(`dist/bin/${bin}`);
|
|
8449
9386
|
}
|
|
8450
9387
|
}
|
|
@@ -8473,7 +9410,7 @@ async function boot(options) {
|
|
|
8473
9410
|
console.log(brief);
|
|
8474
9411
|
return;
|
|
8475
9412
|
}
|
|
8476
|
-
const sessionDir =
|
|
9413
|
+
const sessionDir = path22.join(EXE_AI_DIR, "sessions", coordinatorName);
|
|
8477
9414
|
await mkdir5(sessionDir, { recursive: true });
|
|
8478
9415
|
const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
|
|
8479
9416
|
|
|
@@ -8482,7 +9419,7 @@ async function boot(options) {
|
|
|
8482
9419
|
# Status Brief
|
|
8483
9420
|
|
|
8484
9421
|
${brief}`;
|
|
8485
|
-
await writeFile6(
|
|
9422
|
+
await writeFile6(path22.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
|
|
8486
9423
|
const unread = await readUnreadNotifications();
|
|
8487
9424
|
if (unread.length > 0) {
|
|
8488
9425
|
console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
|
|
@@ -8491,12 +9428,12 @@ ${brief}`;
|
|
|
8491
9428
|
await cleanupOldNotifications();
|
|
8492
9429
|
console.log(brief);
|
|
8493
9430
|
try {
|
|
8494
|
-
const configPath2 =
|
|
8495
|
-
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
9431
|
+
const configPath2 = path22.join(
|
|
9432
|
+
process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
|
|
8496
9433
|
"config.json"
|
|
8497
9434
|
);
|
|
8498
|
-
if (
|
|
8499
|
-
const rawCfg = JSON.parse(
|
|
9435
|
+
if (existsSync18(configPath2)) {
|
|
9436
|
+
const rawCfg = JSON.parse(readFileSync15(configPath2, "utf8"));
|
|
8500
9437
|
const cloudCfg = rawCfg.cloud;
|
|
8501
9438
|
if (cloudCfg?.apiKey) {
|
|
8502
9439
|
const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|