@askexenow/exe-os 0.8.60 → 0.8.61
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/assets/tmux.conf +37 -0
- package/dist/bin/cli.js +151 -111
- package/dist/bin/exe-new-employee.js +862 -114
- package/dist/bin/exe-start.sh +133 -0
- package/dist/bin/install.js +182 -20
- package/dist/hooks/response-ingest-worker.js +3 -1
- package/dist/hooks/summary-worker.js +3 -1
- package/dist/lib/session-wrappers.js +107 -0
- package/package.json +3 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# exe-start — unified session launcher for exe-os Mode 1
|
|
3
|
+
#
|
|
4
|
+
# Invoked by thin wrappers (exe1, yoshi1, etc.) that pass their $0 as first arg.
|
|
5
|
+
#
|
|
6
|
+
# Usage (via wrapper):
|
|
7
|
+
# exe1 → creates tmux session + boots COO
|
|
8
|
+
# exe1 go → attaches to existing exe1 session
|
|
9
|
+
# yoshi1 go → attaches to yoshi-exe1 session
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
ROSTER_PATH="${HOME}/.exe-os/exe-employees.json"
|
|
14
|
+
|
|
15
|
+
# --- Parse wrapper name ---
|
|
16
|
+
# Wrappers call: exec ~/.exe-os/bin/exe-start "$0" "$@"
|
|
17
|
+
# So $1 = wrapper path, remaining = user args
|
|
18
|
+
INVOKED_AS="$(basename "${1:-exe-start}")"
|
|
19
|
+
shift # Remove $0 passthrough; remaining args are user's
|
|
20
|
+
|
|
21
|
+
# Extract name + number: exe1 → exe,1 / yoshi3 → yoshi,3
|
|
22
|
+
NAME="$(echo "$INVOKED_AS" | sed 's/[0-9]*$//')"
|
|
23
|
+
NUM="$(echo "$INVOKED_AS" | grep -oE '[0-9]+$' || true)"
|
|
24
|
+
|
|
25
|
+
if [ -z "$NAME" ] || [ -z "$NUM" ]; then
|
|
26
|
+
echo "Could not parse name and number from: $INVOKED_AS"
|
|
27
|
+
echo " Expected format: exe1, yoshi3, etc."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# --- Load COO name from roster ---
|
|
32
|
+
if [ ! -f "$ROSTER_PATH" ]; then
|
|
33
|
+
echo "Employee roster not found at $ROSTER_PATH"
|
|
34
|
+
echo " Run 'exe-os setup' first."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
COO_NAME="$(node -e "
|
|
39
|
+
const r = JSON.parse(require('fs').readFileSync('${ROSTER_PATH}','utf8'));
|
|
40
|
+
const coo = r.find(e => e.role === 'COO');
|
|
41
|
+
console.log(coo ? coo.name : '');
|
|
42
|
+
" 2>/dev/null)"
|
|
43
|
+
|
|
44
|
+
if [ -z "$COO_NAME" ]; then
|
|
45
|
+
echo "No COO found in roster. Run 'exe-os setup' first."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# --- Mode B: Attach ("go") ---
|
|
50
|
+
if [ "${1:-}" = "go" ]; then
|
|
51
|
+
if [ "$NAME" = "$COO_NAME" ]; then
|
|
52
|
+
SESSION="${COO_NAME}${NUM}"
|
|
53
|
+
else
|
|
54
|
+
SESSION="${NAME}-${COO_NAME}${NUM}"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
58
|
+
exec tmux attach -t "$SESSION"
|
|
59
|
+
else
|
|
60
|
+
if [ "$NAME" = "$COO_NAME" ]; then
|
|
61
|
+
echo " ${SESSION} is not running. Use ${COO_NAME}${NUM} to start a new session."
|
|
62
|
+
else
|
|
63
|
+
echo " ${SESSION} is not running. Dispatch work to ${NAME} via /exe inside ${COO_NAME}${NUM}."
|
|
64
|
+
fi
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# --- Mode A: Create session (COO only) ---
|
|
70
|
+
if [ "$NAME" != "$COO_NAME" ]; then
|
|
71
|
+
echo " ${INVOKED_AS} is attach-only. Use ${INVOKED_AS} go to connect."
|
|
72
|
+
echo " Only ${COO_NAME}${NUM} can start new sessions."
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Project folder guard — block $HOME, /, /tmp and /tmp subdirectories
|
|
77
|
+
case "$PWD" in
|
|
78
|
+
"$HOME")
|
|
79
|
+
echo " ${COO_NAME}${NUM} must be run from a project folder, not your home directory."
|
|
80
|
+
echo ""
|
|
81
|
+
echo " Create or cd into a project folder first:"
|
|
82
|
+
echo " mkdir ~/my-project && cd ~/my-project"
|
|
83
|
+
echo " ${COO_NAME}${NUM}"
|
|
84
|
+
exit 1
|
|
85
|
+
;;
|
|
86
|
+
"/")
|
|
87
|
+
echo " ${COO_NAME}${NUM} must be run from a project folder, not /."
|
|
88
|
+
echo ""
|
|
89
|
+
echo " Create or cd into a project folder first:"
|
|
90
|
+
echo " mkdir ~/my-project && cd ~/my-project"
|
|
91
|
+
echo " ${COO_NAME}${NUM}"
|
|
92
|
+
exit 1
|
|
93
|
+
;;
|
|
94
|
+
/tmp|/tmp/*)
|
|
95
|
+
echo " ${COO_NAME}${NUM} must be run from a project folder, not a temp directory."
|
|
96
|
+
echo ""
|
|
97
|
+
echo " Create or cd into a project folder first:"
|
|
98
|
+
echo " mkdir ~/my-project && cd ~/my-project"
|
|
99
|
+
echo " ${COO_NAME}${NUM}"
|
|
100
|
+
exit 1
|
|
101
|
+
;;
|
|
102
|
+
esac
|
|
103
|
+
|
|
104
|
+
SESSION="${COO_NAME}${NUM}"
|
|
105
|
+
|
|
106
|
+
# Session collision guard
|
|
107
|
+
if tmux has-session -t "$SESSION" 2>/dev/null; then
|
|
108
|
+
# Find next free N
|
|
109
|
+
NEXT_N=$((NUM + 1))
|
|
110
|
+
while tmux has-session -t "${COO_NAME}${NEXT_N}" 2>/dev/null; do
|
|
111
|
+
NEXT_N=$((NEXT_N + 1))
|
|
112
|
+
done
|
|
113
|
+
|
|
114
|
+
# Try to get the working directory of the existing session
|
|
115
|
+
EXISTING_DIR="$(tmux display-message -t "$SESSION" -p '#{pane_current_path}' 2>/dev/null || true)"
|
|
116
|
+
DIR_HINT=""
|
|
117
|
+
if [ -n "$EXISTING_DIR" ]; then
|
|
118
|
+
DIR_HINT=" ($(echo "$EXISTING_DIR" | sed "s|$HOME|~|"))"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
echo " ${SESSION} is already running${DIR_HINT}."
|
|
122
|
+
echo ""
|
|
123
|
+
echo " To reconnect: ${SESSION} go"
|
|
124
|
+
echo " To start a new project here: ${COO_NAME}${NEXT_N}"
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Create session and launch COO
|
|
129
|
+
tmux new-session -d -s "$SESSION" -c "$PWD"
|
|
130
|
+
tmux send-keys -t "$SESSION" "$COO_NAME" Enter
|
|
131
|
+
|
|
132
|
+
# Attach user to session
|
|
133
|
+
exec tmux attach -t "$SESSION"
|
package/dist/bin/install.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
// src/adapters/claude/installer.ts
|
|
4
4
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
|
|
5
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
5
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync, copyFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
6
6
|
import path4 from "path";
|
|
7
7
|
import os3 from "os";
|
|
8
|
+
import { execSync as execSync2 } from "child_process";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
9
10
|
|
|
10
11
|
// src/lib/agent-symlinks.ts
|
|
@@ -658,6 +659,44 @@ async function runInstaller(homeDir) {
|
|
|
658
659
|
exe-os installed successfully.
|
|
659
660
|
`);
|
|
660
661
|
}
|
|
662
|
+
function setupTmux(home) {
|
|
663
|
+
const homeDir = home ?? os3.homedir();
|
|
664
|
+
const exeDir = path4.join(homeDir, ".exe-os");
|
|
665
|
+
const exeTmuxConf = path4.join(exeDir, "tmux.conf");
|
|
666
|
+
const userTmuxConf = path4.join(homeDir, ".tmux.conf");
|
|
667
|
+
const backupPath = path4.join(homeDir, ".tmux.conf.backup");
|
|
668
|
+
const sourceLine = "source-file ~/.exe-os/tmux.conf";
|
|
669
|
+
const pkgRoot = resolvePackageRoot();
|
|
670
|
+
const assetPath = path4.join(pkgRoot, "dist", "assets", "tmux.conf");
|
|
671
|
+
if (!existsSync4(assetPath)) {
|
|
672
|
+
process.stderr.write(`exe-os: tmux.conf asset not found at ${assetPath} \u2014 skipping tmux setup
|
|
673
|
+
`);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
mkdirSync2(exeDir, { recursive: true });
|
|
677
|
+
copyFileSync(assetPath, exeTmuxConf);
|
|
678
|
+
if (existsSync4(userTmuxConf)) {
|
|
679
|
+
const existing = readFileSync3(userTmuxConf, "utf8");
|
|
680
|
+
if (!existing.includes(sourceLine)) {
|
|
681
|
+
if (!existsSync4(backupPath)) {
|
|
682
|
+
copyFileSync(userTmuxConf, backupPath);
|
|
683
|
+
process.stderr.write(`exe-os: backed up existing tmux config to ${backupPath}
|
|
684
|
+
`);
|
|
685
|
+
}
|
|
686
|
+
writeFileSync(userTmuxConf, `${sourceLine}
|
|
687
|
+
${existing}`);
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
writeFileSync(userTmuxConf, `# Exe OS tmux defaults \u2014 remove this line to use your own config
|
|
691
|
+
${sourceLine}
|
|
692
|
+
`);
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
execSync2(`tmux source-file ${exeTmuxConf} 2>/dev/null`);
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
process.stderr.write("exe-os: tmux config installed\n");
|
|
699
|
+
}
|
|
661
700
|
function summarizeSymlinkResults(results) {
|
|
662
701
|
if (results.length === 0) return "no employees in roster";
|
|
663
702
|
const created = results.filter((r) => r.action === "created").length;
|
|
@@ -672,17 +711,124 @@ function summarizeSymlinkResults(results) {
|
|
|
672
711
|
}
|
|
673
712
|
|
|
674
713
|
// src/bin/install.ts
|
|
675
|
-
import { existsSync as
|
|
676
|
-
import { spawn, execSync as
|
|
714
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, readdirSync as readdirSync2, openSync, closeSync } from "fs";
|
|
715
|
+
import { spawn, execSync as execSync3 } from "child_process";
|
|
716
|
+
import path6 from "path";
|
|
717
|
+
import { homedir as homedir2 } from "os";
|
|
718
|
+
|
|
719
|
+
// src/lib/session-wrappers.ts
|
|
720
|
+
import {
|
|
721
|
+
existsSync as existsSync5,
|
|
722
|
+
readFileSync as readFileSync4,
|
|
723
|
+
writeFileSync as writeFileSync2,
|
|
724
|
+
mkdirSync as mkdirSync3,
|
|
725
|
+
chmodSync,
|
|
726
|
+
readdirSync,
|
|
727
|
+
unlinkSync
|
|
728
|
+
} from "fs";
|
|
677
729
|
import path5 from "path";
|
|
678
730
|
import { homedir } from "os";
|
|
679
|
-
var
|
|
731
|
+
var MAX_N = 9;
|
|
732
|
+
function generateSessionWrappers(packageRoot, homeDir) {
|
|
733
|
+
const home = homeDir ?? homedir();
|
|
734
|
+
const binDir = path5.join(home, ".exe-os", "bin");
|
|
735
|
+
const rosterPath = path5.join(home, ".exe-os", "exe-employees.json");
|
|
736
|
+
mkdirSync3(binDir, { recursive: true });
|
|
737
|
+
const exeStartDst = path5.join(binDir, "exe-start");
|
|
738
|
+
const candidates = [
|
|
739
|
+
path5.join(packageRoot, "dist", "bin", "exe-start.sh"),
|
|
740
|
+
path5.join(packageRoot, "src", "bin", "exe-start.sh")
|
|
741
|
+
];
|
|
742
|
+
for (const src of candidates) {
|
|
743
|
+
if (existsSync5(src)) {
|
|
744
|
+
writeFileSync2(exeStartDst, readFileSync4(src));
|
|
745
|
+
chmodSync(exeStartDst, 493);
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
let employees = [];
|
|
750
|
+
try {
|
|
751
|
+
employees = JSON.parse(readFileSync4(rosterPath, "utf8"));
|
|
752
|
+
} catch {
|
|
753
|
+
return { created: 0, pathConfigured: false };
|
|
754
|
+
}
|
|
755
|
+
if (employees.length === 0) {
|
|
756
|
+
return { created: 0, pathConfigured: false };
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
for (const f of readdirSync(binDir)) {
|
|
760
|
+
if (f === "exe-start") continue;
|
|
761
|
+
const fPath = path5.join(binDir, f);
|
|
762
|
+
try {
|
|
763
|
+
const content = readFileSync4(fPath, "utf8");
|
|
764
|
+
if (content.includes("exe-start")) {
|
|
765
|
+
unlinkSync(fPath);
|
|
766
|
+
}
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
let created = 0;
|
|
773
|
+
const wrapperContent = `#!/bin/bash
|
|
774
|
+
exec "${exeStartDst}" "$0" "$@"
|
|
775
|
+
`;
|
|
776
|
+
for (const emp of employees) {
|
|
777
|
+
for (let n = 1; n <= MAX_N; n++) {
|
|
778
|
+
const wrapperPath = path5.join(binDir, `${emp.name}${n}`);
|
|
779
|
+
writeFileSync2(wrapperPath, wrapperContent);
|
|
780
|
+
chmodSync(wrapperPath, 493);
|
|
781
|
+
created++;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const pathConfigured = ensurePath(home, binDir);
|
|
785
|
+
return { created, pathConfigured };
|
|
786
|
+
}
|
|
787
|
+
function ensurePath(home, binDir) {
|
|
788
|
+
if (process.env.PATH?.split(":").includes(binDir)) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
const exportLine = `
|
|
792
|
+
# exe-os session commands
|
|
793
|
+
export PATH="${binDir}:$PATH"
|
|
794
|
+
`;
|
|
795
|
+
const shell = process.env.SHELL ?? "/bin/bash";
|
|
796
|
+
const profilePaths = [];
|
|
797
|
+
if (shell.includes("zsh")) {
|
|
798
|
+
profilePaths.push(path5.join(home, ".zshrc"));
|
|
799
|
+
} else if (shell.includes("bash")) {
|
|
800
|
+
profilePaths.push(path5.join(home, ".bashrc"));
|
|
801
|
+
profilePaths.push(path5.join(home, ".bash_profile"));
|
|
802
|
+
} else {
|
|
803
|
+
profilePaths.push(path5.join(home, ".profile"));
|
|
804
|
+
}
|
|
805
|
+
for (const profilePath of profilePaths) {
|
|
806
|
+
try {
|
|
807
|
+
let content = "";
|
|
808
|
+
try {
|
|
809
|
+
content = readFileSync4(profilePath, "utf8");
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
if (content.includes(".exe-os/bin")) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
writeFileSync2(profilePath, content + exportLine);
|
|
816
|
+
return true;
|
|
817
|
+
} catch {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/bin/install.ts
|
|
825
|
+
var EXE_DIR = path6.join(homedir2(), ".exe-os");
|
|
680
826
|
function restartDaemon() {
|
|
681
|
-
const pidPath =
|
|
682
|
-
const sockPath =
|
|
827
|
+
const pidPath = path6.join(EXE_DIR, "exed.pid");
|
|
828
|
+
const sockPath = path6.join(EXE_DIR, "exed.sock");
|
|
683
829
|
try {
|
|
684
|
-
if (
|
|
685
|
-
const pid = parseInt(
|
|
830
|
+
if (existsSync6(pidPath)) {
|
|
831
|
+
const pid = parseInt(readFileSync5(pidPath, "utf8").trim(), 10);
|
|
686
832
|
if (!isNaN(pid) && pid > 0) {
|
|
687
833
|
try {
|
|
688
834
|
process.kill(pid, "SIGKILL");
|
|
@@ -690,12 +836,12 @@ function restartDaemon() {
|
|
|
690
836
|
}
|
|
691
837
|
}
|
|
692
838
|
try {
|
|
693
|
-
|
|
839
|
+
unlinkSync2(pidPath);
|
|
694
840
|
} catch {
|
|
695
841
|
}
|
|
696
842
|
}
|
|
697
843
|
try {
|
|
698
|
-
const pids =
|
|
844
|
+
const pids = execSync3("pgrep -f 'exe-daemon.js' 2>/dev/null || true", { encoding: "utf8" }).trim().split("\n").filter(Boolean).map(Number).filter((n) => !isNaN(n) && n !== process.pid);
|
|
699
845
|
for (const pid of pids) {
|
|
700
846
|
try {
|
|
701
847
|
process.kill(pid, "SIGKILL");
|
|
@@ -709,7 +855,7 @@ function restartDaemon() {
|
|
|
709
855
|
} catch {
|
|
710
856
|
}
|
|
711
857
|
try {
|
|
712
|
-
const pids =
|
|
858
|
+
const pids = execSync3("pgrep -f 'backfill-vectors.js' 2>/dev/null || true", { encoding: "utf8" }).trim().split("\n").filter(Boolean).map(Number).filter((n) => !isNaN(n) && n !== process.pid);
|
|
713
859
|
for (const pid of pids) {
|
|
714
860
|
try {
|
|
715
861
|
process.kill(pid, "SIGKILL");
|
|
@@ -723,11 +869,11 @@ function restartDaemon() {
|
|
|
723
869
|
} catch {
|
|
724
870
|
}
|
|
725
871
|
try {
|
|
726
|
-
const wpDir =
|
|
727
|
-
if (
|
|
728
|
-
for (const f of
|
|
872
|
+
const wpDir = path6.join(EXE_DIR, "worker-pids");
|
|
873
|
+
if (existsSync6(wpDir)) {
|
|
874
|
+
for (const f of readdirSync2(wpDir)) {
|
|
729
875
|
try {
|
|
730
|
-
|
|
876
|
+
unlinkSync2(path6.join(wpDir, f));
|
|
731
877
|
} catch {
|
|
732
878
|
}
|
|
733
879
|
}
|
|
@@ -735,24 +881,24 @@ function restartDaemon() {
|
|
|
735
881
|
} catch {
|
|
736
882
|
}
|
|
737
883
|
try {
|
|
738
|
-
|
|
884
|
+
unlinkSync2(sockPath);
|
|
739
885
|
} catch {
|
|
740
886
|
}
|
|
741
887
|
try {
|
|
742
|
-
|
|
888
|
+
execSync3("sleep 0.5");
|
|
743
889
|
} catch {
|
|
744
890
|
}
|
|
745
891
|
} catch {
|
|
746
892
|
}
|
|
747
893
|
try {
|
|
748
894
|
const pkgRoot = resolvePackageRoot();
|
|
749
|
-
const daemonPath =
|
|
750
|
-
if (!
|
|
895
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
896
|
+
if (!existsSync6(daemonPath)) {
|
|
751
897
|
process.stderr.write(`exe-os: daemon not found at ${daemonPath} \u2014 skipping respawn
|
|
752
898
|
`);
|
|
753
899
|
return;
|
|
754
900
|
}
|
|
755
|
-
const logPath =
|
|
901
|
+
const logPath = path6.join(EXE_DIR, "exed.log");
|
|
756
902
|
let stderrFd = "ignore";
|
|
757
903
|
try {
|
|
758
904
|
stderrFd = openSync(logPath, "a");
|
|
@@ -797,6 +943,22 @@ if (args.includes("--commands-only")) {
|
|
|
797
943
|
} else if (args.includes("--global")) {
|
|
798
944
|
try {
|
|
799
945
|
await runInstaller();
|
|
946
|
+
setupTmux();
|
|
947
|
+
try {
|
|
948
|
+
const pkgRoot = resolvePackageRoot();
|
|
949
|
+
const wrapResult = generateSessionWrappers(pkgRoot);
|
|
950
|
+
if (wrapResult.created > 0) {
|
|
951
|
+
process.stderr.write(`exe-os: ${wrapResult.created} session wrapper(s) generated
|
|
952
|
+
`);
|
|
953
|
+
}
|
|
954
|
+
if (wrapResult.pathConfigured) {
|
|
955
|
+
process.stderr.write(`exe-os: added ~/.exe-os/bin to PATH (restart your shell to activate)
|
|
956
|
+
`);
|
|
957
|
+
}
|
|
958
|
+
} catch (err) {
|
|
959
|
+
process.stderr.write(`exe-os: session wrapper generation failed: ${err instanceof Error ? err.message : String(err)}
|
|
960
|
+
`);
|
|
961
|
+
}
|
|
800
962
|
restartDaemon();
|
|
801
963
|
} catch (err) {
|
|
802
964
|
console.error(
|
|
@@ -2852,7 +2852,9 @@ async function main() {
|
|
|
2852
2852
|
try {
|
|
2853
2853
|
const { assertMemoryLimit: assertMemoryLimit2 } = await Promise.resolve().then(() => (init_plan_limits(), plan_limits_exports));
|
|
2854
2854
|
await assertMemoryLimit2();
|
|
2855
|
-
} catch {
|
|
2855
|
+
} catch (err) {
|
|
2856
|
+
process.stderr.write(`[response-ingest-worker] LIMIT: ${err instanceof Error ? err.message : String(err)}
|
|
2857
|
+
`);
|
|
2856
2858
|
process.exit(0);
|
|
2857
2859
|
}
|
|
2858
2860
|
await writeMemory({
|
|
@@ -4660,7 +4660,9 @@ async function main() {
|
|
|
4660
4660
|
try {
|
|
4661
4661
|
const { assertMemoryLimit: assertMemoryLimit2 } = await Promise.resolve().then(() => (init_plan_limits(), plan_limits_exports));
|
|
4662
4662
|
await assertMemoryLimit2();
|
|
4663
|
-
} catch {
|
|
4663
|
+
} catch (err) {
|
|
4664
|
+
process.stderr.write(`[summary-worker] LIMIT: ${err instanceof Error ? err.message : String(err)}
|
|
4665
|
+
`);
|
|
4664
4666
|
process.exit(0);
|
|
4665
4667
|
}
|
|
4666
4668
|
await writeMemory({
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/lib/session-wrappers.ts
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
chmodSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
unlinkSync
|
|
10
|
+
} from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
var MAX_N = 9;
|
|
14
|
+
function generateSessionWrappers(packageRoot, homeDir) {
|
|
15
|
+
const home = homeDir ?? homedir();
|
|
16
|
+
const binDir = path.join(home, ".exe-os", "bin");
|
|
17
|
+
const rosterPath = path.join(home, ".exe-os", "exe-employees.json");
|
|
18
|
+
mkdirSync(binDir, { recursive: true });
|
|
19
|
+
const exeStartDst = path.join(binDir, "exe-start");
|
|
20
|
+
const candidates = [
|
|
21
|
+
path.join(packageRoot, "dist", "bin", "exe-start.sh"),
|
|
22
|
+
path.join(packageRoot, "src", "bin", "exe-start.sh")
|
|
23
|
+
];
|
|
24
|
+
for (const src of candidates) {
|
|
25
|
+
if (existsSync(src)) {
|
|
26
|
+
writeFileSync(exeStartDst, readFileSync(src));
|
|
27
|
+
chmodSync(exeStartDst, 493);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let employees = [];
|
|
32
|
+
try {
|
|
33
|
+
employees = JSON.parse(readFileSync(rosterPath, "utf8"));
|
|
34
|
+
} catch {
|
|
35
|
+
return { created: 0, pathConfigured: false };
|
|
36
|
+
}
|
|
37
|
+
if (employees.length === 0) {
|
|
38
|
+
return { created: 0, pathConfigured: false };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
for (const f of readdirSync(binDir)) {
|
|
42
|
+
if (f === "exe-start") continue;
|
|
43
|
+
const fPath = path.join(binDir, f);
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(fPath, "utf8");
|
|
46
|
+
if (content.includes("exe-start")) {
|
|
47
|
+
unlinkSync(fPath);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
let created = 0;
|
|
55
|
+
const wrapperContent = `#!/bin/bash
|
|
56
|
+
exec "${exeStartDst}" "$0" "$@"
|
|
57
|
+
`;
|
|
58
|
+
for (const emp of employees) {
|
|
59
|
+
for (let n = 1; n <= MAX_N; n++) {
|
|
60
|
+
const wrapperPath = path.join(binDir, `${emp.name}${n}`);
|
|
61
|
+
writeFileSync(wrapperPath, wrapperContent);
|
|
62
|
+
chmodSync(wrapperPath, 493);
|
|
63
|
+
created++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const pathConfigured = ensurePath(home, binDir);
|
|
67
|
+
return { created, pathConfigured };
|
|
68
|
+
}
|
|
69
|
+
function ensurePath(home, binDir) {
|
|
70
|
+
if (process.env.PATH?.split(":").includes(binDir)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const exportLine = `
|
|
74
|
+
# exe-os session commands
|
|
75
|
+
export PATH="${binDir}:$PATH"
|
|
76
|
+
`;
|
|
77
|
+
const shell = process.env.SHELL ?? "/bin/bash";
|
|
78
|
+
const profilePaths = [];
|
|
79
|
+
if (shell.includes("zsh")) {
|
|
80
|
+
profilePaths.push(path.join(home, ".zshrc"));
|
|
81
|
+
} else if (shell.includes("bash")) {
|
|
82
|
+
profilePaths.push(path.join(home, ".bashrc"));
|
|
83
|
+
profilePaths.push(path.join(home, ".bash_profile"));
|
|
84
|
+
} else {
|
|
85
|
+
profilePaths.push(path.join(home, ".profile"));
|
|
86
|
+
}
|
|
87
|
+
for (const profilePath of profilePaths) {
|
|
88
|
+
try {
|
|
89
|
+
let content = "";
|
|
90
|
+
try {
|
|
91
|
+
content = readFileSync(profilePath, "utf8");
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
if (content.includes(".exe-os/bin")) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
writeFileSync(profilePath, content + exportLine);
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
generateSessionWrappers
|
|
107
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.61",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "CC-BY-NC-4.0",
|
|
6
6
|
"type": "module",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"test": "vitest run",
|
|
66
66
|
"test:watch": "vitest",
|
|
67
67
|
"typecheck": "tsc --noEmit",
|
|
68
|
-
"build": "tsup",
|
|
69
|
-
"deploy": "node dist/bin/pre-build-guard.js 2>/dev/null; tsup && npm install -g . && node dist/bin/install.js --global && echo '[exe-os] Deploy complete. MCP servers will auto-reconnect on next tool call.'",
|
|
68
|
+
"build": "tsup && mkdir -p dist/assets && cp src/assets/tmux.conf dist/assets/ && cp src/bin/exe-start.sh dist/bin/exe-start.sh",
|
|
69
|
+
"deploy": "node dist/bin/pre-build-guard.js 2>/dev/null; tsup && mkdir -p dist/assets && cp src/assets/tmux.conf dist/assets/ && cp src/bin/exe-start.sh dist/bin/exe-start.sh && npm install -g . && node dist/bin/install.js --global && echo '[exe-os] Deploy complete. MCP servers will auto-reconnect on next tool call.'",
|
|
70
70
|
"postinstall": "node dist/bin/install.js --global 2>/dev/null || true",
|
|
71
71
|
"prepublishOnly": "npm run typecheck && npm run build && node dist/bin/customer-readiness.js && npx vitest run --maxWorkers=4 --exclude 'tests/tui/**' --exclude 'tests/lib/tmux-routing.test.ts' --exclude 'tests/lib/intercom-routing.test.ts' --exclude 'tests/gateway/**' --exclude 'tests/installer/setup-wizard.test.ts' --exclude 'tests/mcp/ingest-document.test.ts' --exclude 'tests/lib/hybrid-search.test.ts' --exclude 'tests/lib/worker-gate.test.ts'",
|
|
72
72
|
"benchmark:longmemeval": "npx tsx tests/benchmarks/longmemeval.ts"
|