open-agents-ai 0.185.73 → 0.185.74
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/index.js +369 -177
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2129,10 +2129,10 @@ function loadLog(workingDir) {
|
|
|
2129
2129
|
return { version: 1, entries: [] };
|
|
2130
2130
|
}
|
|
2131
2131
|
}
|
|
2132
|
-
function saveLog(workingDir,
|
|
2132
|
+
function saveLog(workingDir, log2) {
|
|
2133
2133
|
const dir = join4(workingDir, ".oa", "index");
|
|
2134
2134
|
mkdirSync4(dir, { recursive: true });
|
|
2135
|
-
writeFileSync4(logPath(workingDir), JSON.stringify(
|
|
2135
|
+
writeFileSync4(logPath(workingDir), JSON.stringify(log2, null, 2), "utf-8");
|
|
2136
2136
|
}
|
|
2137
2137
|
function setChangeLogSession(sessionId, taskGoal) {
|
|
2138
2138
|
_sessionId = sessionId;
|
|
@@ -2140,7 +2140,7 @@ function setChangeLogSession(sessionId, taskGoal) {
|
|
|
2140
2140
|
}
|
|
2141
2141
|
function recordChange(workingDir, opts) {
|
|
2142
2142
|
try {
|
|
2143
|
-
const
|
|
2143
|
+
const log2 = loadLog(workingDir);
|
|
2144
2144
|
const entry = {
|
|
2145
2145
|
id: `chg-${randomBytes2(4).toString("hex")}`,
|
|
2146
2146
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2155,11 +2155,11 @@ function recordChange(workingDir, opts) {
|
|
|
2155
2155
|
relatedNoteIds: opts.relatedNoteIds,
|
|
2156
2156
|
taskGoal: _taskGoal || void 0
|
|
2157
2157
|
};
|
|
2158
|
-
|
|
2159
|
-
if (
|
|
2160
|
-
|
|
2158
|
+
log2.entries.push(entry);
|
|
2159
|
+
if (log2.entries.length > 500) {
|
|
2160
|
+
log2.entries = log2.entries.slice(-500);
|
|
2161
2161
|
}
|
|
2162
|
-
saveLog(workingDir,
|
|
2162
|
+
saveLog(workingDir, log2);
|
|
2163
2163
|
return entry.id;
|
|
2164
2164
|
} catch {
|
|
2165
2165
|
return null;
|
|
@@ -2167,16 +2167,16 @@ function recordChange(workingDir, opts) {
|
|
|
2167
2167
|
}
|
|
2168
2168
|
function markSessionValidated(workingDir) {
|
|
2169
2169
|
try {
|
|
2170
|
-
const
|
|
2170
|
+
const log2 = loadLog(workingDir);
|
|
2171
2171
|
let count = 0;
|
|
2172
|
-
for (const entry of
|
|
2172
|
+
for (const entry of log2.entries) {
|
|
2173
2173
|
if (entry.sessionId === _sessionId && entry.outcome === "untested") {
|
|
2174
2174
|
entry.outcome = "success";
|
|
2175
2175
|
count++;
|
|
2176
2176
|
}
|
|
2177
2177
|
}
|
|
2178
2178
|
if (count > 0)
|
|
2179
|
-
saveLog(workingDir,
|
|
2179
|
+
saveLog(workingDir, log2);
|
|
2180
2180
|
return count;
|
|
2181
2181
|
} catch {
|
|
2182
2182
|
return 0;
|
|
@@ -2184,12 +2184,12 @@ function markSessionValidated(workingDir) {
|
|
|
2184
2184
|
}
|
|
2185
2185
|
function markReverted(workingDir, changeId) {
|
|
2186
2186
|
try {
|
|
2187
|
-
const
|
|
2188
|
-
const entry =
|
|
2187
|
+
const log2 = loadLog(workingDir);
|
|
2188
|
+
const entry = log2.entries.find((e) => e.id === changeId);
|
|
2189
2189
|
if (!entry)
|
|
2190
2190
|
return false;
|
|
2191
2191
|
entry.outcome = "reverted";
|
|
2192
|
-
saveLog(workingDir,
|
|
2192
|
+
saveLog(workingDir, log2);
|
|
2193
2193
|
return true;
|
|
2194
2194
|
} catch {
|
|
2195
2195
|
return false;
|
|
@@ -2197,24 +2197,24 @@ function markReverted(workingDir, changeId) {
|
|
|
2197
2197
|
}
|
|
2198
2198
|
function getFileChanges(workingDir, filePath, limit = 10) {
|
|
2199
2199
|
try {
|
|
2200
|
-
const
|
|
2201
|
-
return
|
|
2200
|
+
const log2 = loadLog(workingDir);
|
|
2201
|
+
return log2.entries.filter((e) => e.file === filePath).slice(-limit);
|
|
2202
2202
|
} catch {
|
|
2203
2203
|
return [];
|
|
2204
2204
|
}
|
|
2205
2205
|
}
|
|
2206
2206
|
function getSessionChanges(workingDir) {
|
|
2207
2207
|
try {
|
|
2208
|
-
const
|
|
2209
|
-
return
|
|
2208
|
+
const log2 = loadLog(workingDir);
|
|
2209
|
+
return log2.entries.filter((e) => e.sessionId === _sessionId);
|
|
2210
2210
|
} catch {
|
|
2211
2211
|
return [];
|
|
2212
2212
|
}
|
|
2213
2213
|
}
|
|
2214
2214
|
function getRecentChangesSummary(workingDir, limit = 10) {
|
|
2215
2215
|
try {
|
|
2216
|
-
const
|
|
2217
|
-
const recent =
|
|
2216
|
+
const log2 = loadLog(workingDir);
|
|
2217
|
+
const recent = log2.entries.slice(-limit);
|
|
2218
2218
|
if (recent.length === 0)
|
|
2219
2219
|
return "";
|
|
2220
2220
|
const lines = [`Recent changes (${recent.length}):`];
|
|
@@ -5261,9 +5261,9 @@ var init_git_info = __esm({
|
|
|
5261
5261
|
}
|
|
5262
5262
|
report.push("");
|
|
5263
5263
|
report.push("## Recent Commits");
|
|
5264
|
-
const
|
|
5265
|
-
if (
|
|
5266
|
-
for (const line of
|
|
5264
|
+
const log2 = this.git(repoDir, `log --oneline -${logCount} --no-decorate`);
|
|
5265
|
+
if (log2) {
|
|
5266
|
+
for (const line of log2.split("\n").filter((l) => l.trim())) {
|
|
5267
5267
|
report.push(` ${line}`);
|
|
5268
5268
|
}
|
|
5269
5269
|
} else {
|
|
@@ -41666,7 +41666,7 @@ function getWeightRepoInfo(tier) {
|
|
|
41666
41666
|
return WEIGHT_REPOS[tier];
|
|
41667
41667
|
}
|
|
41668
41668
|
async function installPersonaPlex(onInfo, weightTier) {
|
|
41669
|
-
const
|
|
41669
|
+
const log2 = onInfo ?? (() => {
|
|
41670
41670
|
});
|
|
41671
41671
|
mkdirSync15(PERSONAPLEX_DIR, { recursive: true });
|
|
41672
41672
|
let arch2 = "";
|
|
@@ -41676,21 +41676,21 @@ async function installPersonaPlex(onInfo, weightTier) {
|
|
|
41676
41676
|
}
|
|
41677
41677
|
const isAarch64 = arch2 === "aarch64" || arch2 === "arm64";
|
|
41678
41678
|
if (isAarch64)
|
|
41679
|
-
|
|
41679
|
+
log2(`Detected ARM64 platform (${arch2}) \u2014 Jetson/ARM install path`);
|
|
41680
41680
|
const venvDir = join54(PERSONAPLEX_DIR, "venv");
|
|
41681
41681
|
if (!existsSync37(venvDir)) {
|
|
41682
|
-
|
|
41682
|
+
log2("Creating Python virtual environment...");
|
|
41683
41683
|
try {
|
|
41684
41684
|
const ssp = isAarch64 ? " --system-site-packages" : "";
|
|
41685
41685
|
await execAsync(`python3 -m venv${ssp} "${venvDir}"`, { timeout: 6e4 });
|
|
41686
41686
|
} catch (err) {
|
|
41687
|
-
|
|
41687
|
+
log2(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
|
|
41688
41688
|
return false;
|
|
41689
41689
|
}
|
|
41690
41690
|
}
|
|
41691
41691
|
const pip = process.platform === "win32" ? join54(venvDir, "Scripts", "pip.exe") : join54(venvDir, "bin", "pip");
|
|
41692
41692
|
const python = process.platform === "win32" ? join54(venvDir, "Scripts", "python.exe") : join54(venvDir, "bin", "python3");
|
|
41693
|
-
|
|
41693
|
+
log2("Checking system dependencies (libopus)...");
|
|
41694
41694
|
try {
|
|
41695
41695
|
if (process.platform === "linux") {
|
|
41696
41696
|
execSync28("dpkg -l libopus-dev 2>/dev/null || sudo apt-get install -y libopus-dev", { timeout: 3e4, stdio: "pipe" });
|
|
@@ -41700,16 +41700,16 @@ async function installPersonaPlex(onInfo, weightTier) {
|
|
|
41700
41700
|
} catch {
|
|
41701
41701
|
}
|
|
41702
41702
|
if (isAarch64) {
|
|
41703
|
-
|
|
41703
|
+
log2("ARM64: Checking Rust toolchain for sphn build...");
|
|
41704
41704
|
try {
|
|
41705
41705
|
execSync28("rustc --version", { timeout: 5e3, stdio: "pipe" });
|
|
41706
41706
|
} catch {
|
|
41707
|
-
|
|
41707
|
+
log2("ARM64: Installing Rust toolchain (needed for sphn audio codec)...");
|
|
41708
41708
|
try {
|
|
41709
41709
|
await execAsync("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y", { timeout: 12e4 });
|
|
41710
41710
|
} catch (e) {
|
|
41711
|
-
|
|
41712
|
-
|
|
41711
|
+
log2(`Rust install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
41712
|
+
log2("Install Rust manually: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
|
|
41713
41713
|
return false;
|
|
41714
41714
|
}
|
|
41715
41715
|
}
|
|
@@ -41718,38 +41718,38 @@ async function installPersonaPlex(onInfo, weightTier) {
|
|
|
41718
41718
|
} catch {
|
|
41719
41719
|
}
|
|
41720
41720
|
}
|
|
41721
|
-
|
|
41721
|
+
log2("Installing PersonaPlex (moshi package)...");
|
|
41722
41722
|
const repoDir = join54(PERSONAPLEX_DIR, "personaplex-repo");
|
|
41723
41723
|
try {
|
|
41724
41724
|
if (!existsSync37(repoDir)) {
|
|
41725
41725
|
await execAsync(`git clone https://github.com/NVIDIA/personaplex.git "${repoDir}"`, { timeout: 12e4 });
|
|
41726
41726
|
}
|
|
41727
41727
|
if (isAarch64) {
|
|
41728
|
-
|
|
41728
|
+
log2("ARM64: Building sphn from source (Opus codec bindings)...");
|
|
41729
41729
|
try {
|
|
41730
41730
|
const rustEnv = `export PATH="$HOME/.cargo/bin:$PATH" &&`;
|
|
41731
41731
|
await execAsync(`${rustEnv} "${pip}" install --quiet --no-binary sphn sphn`, { timeout: 3e5 });
|
|
41732
|
-
|
|
41732
|
+
log2("ARM64: sphn built successfully");
|
|
41733
41733
|
} catch (e) {
|
|
41734
|
-
|
|
41735
|
-
|
|
41734
|
+
log2(`ARM64: sphn build failed \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
41735
|
+
log2("Ensure Rust, libopus-dev, and cmake are installed.");
|
|
41736
41736
|
return false;
|
|
41737
41737
|
}
|
|
41738
41738
|
}
|
|
41739
41739
|
if (isAarch64) {
|
|
41740
|
-
|
|
41740
|
+
log2("ARM64: Installing moshi (--no-deps to preserve JetPack torch)...");
|
|
41741
41741
|
await execAsync(`"${pip}" install --quiet --no-deps "${join54(repoDir, "moshi")}/."`, { timeout: 3e5 });
|
|
41742
|
-
|
|
41742
|
+
log2("ARM64: Installing remaining moshi dependencies...");
|
|
41743
41743
|
await execAsync(`"${pip}" install --quiet "numpy>=1.26,<2.2" "safetensors>=0.4.0,<0.5" "huggingface-hub>=0.24,<0.25" "einops==0.7" "sentencepiece==0.2" "sounddevice==0.5" "aiohttp>=3.10.5,<3.11"`, { timeout: 3e5 });
|
|
41744
41744
|
} else {
|
|
41745
41745
|
await execAsync(`"${pip}" install --quiet "${join54(repoDir, "moshi")}/."`, { timeout: 6e5 });
|
|
41746
41746
|
}
|
|
41747
41747
|
} catch (err) {
|
|
41748
|
-
|
|
41748
|
+
log2(`Moshi install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41749
41749
|
if (isAarch64) {
|
|
41750
|
-
|
|
41751
|
-
|
|
41752
|
-
|
|
41750
|
+
log2("ARM64: This often means the pip process was OOM-killed.");
|
|
41751
|
+
log2("Check: dmesg | grep -i 'oom\\|killed' | tail -5");
|
|
41752
|
+
log2("Ensure JetPack PyTorch is installed: pip3 show torch");
|
|
41753
41753
|
}
|
|
41754
41754
|
try {
|
|
41755
41755
|
await execAsync(`"${pip}" install --quiet torch torchaudio websockets soundfile huggingface_hub`, { timeout: 3e5, stdio: "pipe" });
|
|
@@ -41770,7 +41770,7 @@ async function installPersonaPlex(onInfo, weightTier) {
|
|
|
41770
41770
|
if (src.includes('int(request["seed"])')) {
|
|
41771
41771
|
src = src.replace('int(request["seed"])', 'int(request.query["seed"])');
|
|
41772
41772
|
writeFileSync16(serverFile, src);
|
|
41773
|
-
|
|
41773
|
+
log2("Applied seed parameter bug fix to moshi server.");
|
|
41774
41774
|
}
|
|
41775
41775
|
}
|
|
41776
41776
|
} catch {
|
|
@@ -41868,7 +41868,7 @@ $2if filename.endswith(".safetensors"):`);
|
|
|
41868
41868
|
${patchPoint}`);
|
|
41869
41869
|
}
|
|
41870
41870
|
writeFileSync16(loadersFile, src);
|
|
41871
|
-
|
|
41871
|
+
log2("Patched loaders.py with 2-bit TurboQuant native dequant support.");
|
|
41872
41872
|
}
|
|
41873
41873
|
}
|
|
41874
41874
|
} catch {
|
|
@@ -41882,31 +41882,31 @@ $2if filename.endswith(".safetensors"):`);
|
|
|
41882
41882
|
const hybridDest = join54(sitePackages2, "hybrid_agent.py");
|
|
41883
41883
|
const serverDest = join54(sitePackages2, "server.py");
|
|
41884
41884
|
if (!existsSync37(hybridDest) || !readFileSync28(hybridDest, "utf8").includes("OA_API_BASE")) {
|
|
41885
|
-
|
|
41885
|
+
log2("Deploying hybrid_agent.py (OA API integration)...");
|
|
41886
41886
|
try {
|
|
41887
41887
|
await execAsync(`curl -sL "https://raw.githubusercontent.com/robit-man/personaplex/main/personaplex-setup/moshi/moshi/hybrid_agent.py" -o "${hybridDest}"`, { timeout: 3e4 });
|
|
41888
41888
|
if (existsSync37(hybridDest) && readFileSync28(hybridDest, "utf8").includes("OA_API_BASE")) {
|
|
41889
|
-
|
|
41889
|
+
log2("hybrid_agent.py deployed (OA API + Ollama fallback).");
|
|
41890
41890
|
}
|
|
41891
41891
|
} catch {
|
|
41892
|
-
|
|
41892
|
+
log2("hybrid_agent.py download failed \u2014 hybrid mode will be disabled.");
|
|
41893
41893
|
}
|
|
41894
41894
|
}
|
|
41895
41895
|
if (!readFileSync28(serverDest, "utf8").includes("hybrid_agent")) {
|
|
41896
|
-
|
|
41896
|
+
log2("Deploying patched server.py (hybrid mode + API endpoints)...");
|
|
41897
41897
|
try {
|
|
41898
41898
|
await execAsync(`curl -sL "https://raw.githubusercontent.com/robit-man/personaplex/main/personaplex-setup/moshi/moshi/server.py" -o "${serverDest}"`, { timeout: 3e4 });
|
|
41899
41899
|
if (readFileSync28(serverDest, "utf8").includes("hybrid_agent")) {
|
|
41900
|
-
|
|
41900
|
+
log2("server.py patched with hybrid intercept + REST APIs.");
|
|
41901
41901
|
}
|
|
41902
41902
|
} catch {
|
|
41903
|
-
|
|
41903
|
+
log2("server.py download failed \u2014 will use upstream version.");
|
|
41904
41904
|
}
|
|
41905
41905
|
}
|
|
41906
41906
|
} catch {
|
|
41907
41907
|
}
|
|
41908
41908
|
if (isAarch64) {
|
|
41909
|
-
|
|
41909
|
+
log2("ARM64: Installing bitsandbytes for INT4 inference...");
|
|
41910
41910
|
try {
|
|
41911
41911
|
await execAsync(`"${pip}" install --quiet bitsandbytes`, { timeout: 12e4, stdio: "pipe" });
|
|
41912
41912
|
} catch {
|
|
@@ -41922,15 +41922,15 @@ $2if filename.endswith(".safetensors"):`);
|
|
|
41922
41922
|
}
|
|
41923
41923
|
const tier = weightTier ?? detectPersonaPlexCapability().weightTier;
|
|
41924
41924
|
const repoInfo = WEIGHT_REPOS[tier];
|
|
41925
|
-
|
|
41926
|
-
|
|
41925
|
+
log2(`Weight tier: ${tier} (${repoInfo.sizeGB}GB) \u2014 ${repoInfo.needsToken ? "requires HF_TOKEN" : "public, no token needed"}`);
|
|
41926
|
+
log2(`Downloading PersonaPlex weights (${repoInfo.sizeGB}GB)...`);
|
|
41927
41927
|
try {
|
|
41928
41928
|
const tokenArg = repoInfo.needsToken ? "" : "--token ''";
|
|
41929
41929
|
const dlCmd = `"${python}" -c "from huggingface_hub import hf_hub_download; f=hf_hub_download('${repoInfo.repo}', '${repoInfo.file}'${repoInfo.needsToken ? "" : ", token=False"}); print(f)"`;
|
|
41930
41930
|
const weightPath = (await execAsync(dlCmd, { timeout: 6e5 })).trim();
|
|
41931
|
-
|
|
41931
|
+
log2(`Weights downloaded: ${repoInfo.file}`);
|
|
41932
41932
|
if (tier !== "original") {
|
|
41933
|
-
|
|
41933
|
+
log2("Downloading Mimi codec + tokenizer (no token needed)...");
|
|
41934
41934
|
const supportFiles = ["tokenizer-e351c8d8-checkpoint125.safetensors", "tokenizer_spm_32k_3.model", "config.json"];
|
|
41935
41935
|
for (const sf of supportFiles) {
|
|
41936
41936
|
try {
|
|
@@ -41946,49 +41946,49 @@ $2if filename.endswith(".safetensors"):`);
|
|
|
41946
41946
|
}
|
|
41947
41947
|
}
|
|
41948
41948
|
}
|
|
41949
|
-
|
|
41949
|
+
log2("Codec + tokenizer downloaded.");
|
|
41950
41950
|
}
|
|
41951
41951
|
} catch (err) {
|
|
41952
41952
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41953
41953
|
if (repoInfo.needsToken && /401|403|gated|unauthorized/i.test(msg)) {
|
|
41954
|
-
|
|
41954
|
+
log2(`HF_TOKEN required for ${tier} weights. Set HF_TOKEN or accept license at https://huggingface.co/${repoInfo.repo}`);
|
|
41955
41955
|
if (tier === "original") {
|
|
41956
|
-
|
|
41956
|
+
log2("Auto-downgrading to INT4 weights (no token required)...");
|
|
41957
41957
|
const nf4 = WEIGHT_REPOS["nf4"];
|
|
41958
41958
|
try {
|
|
41959
41959
|
await execAsync(`"${python}" -c "from huggingface_hub import hf_hub_download; hf_hub_download('${nf4.repo}', '${nf4.file}', token=False)"`, {
|
|
41960
41960
|
timeout: 6e5
|
|
41961
41961
|
});
|
|
41962
41962
|
writeFileSync16(join54(PERSONAPLEX_DIR, "weight_tier"), "nf4");
|
|
41963
|
-
|
|
41963
|
+
log2(`Downloaded INT4 weights instead (${nf4.sizeGB}GB, public).`);
|
|
41964
41964
|
} catch {
|
|
41965
|
-
|
|
41965
|
+
log2("Weight download failed.");
|
|
41966
41966
|
return false;
|
|
41967
41967
|
}
|
|
41968
41968
|
}
|
|
41969
41969
|
} else {
|
|
41970
|
-
|
|
41971
|
-
|
|
41970
|
+
log2(`Weight download failed: ${msg}`);
|
|
41971
|
+
log2("Weights will download on first server launch.");
|
|
41972
41972
|
}
|
|
41973
41973
|
}
|
|
41974
41974
|
writeFileSync16(join54(PERSONAPLEX_DIR, "weight_tier"), tier);
|
|
41975
41975
|
writeFileSync16(join54(PERSONAPLEX_DIR, "model_ready"), (/* @__PURE__ */ new Date()).toISOString());
|
|
41976
|
-
|
|
41976
|
+
log2(`PersonaPlex installed (${tier} tier). Use /call to start voice session.`);
|
|
41977
41977
|
return true;
|
|
41978
41978
|
}
|
|
41979
41979
|
async function startPersonaPlexDaemon(onInfo) {
|
|
41980
|
-
const
|
|
41980
|
+
const log2 = onInfo ?? (() => {
|
|
41981
41981
|
});
|
|
41982
41982
|
const PORT = 8998;
|
|
41983
41983
|
if (isPersonaPlexRunning()) {
|
|
41984
41984
|
const url = getPersonaPlexWSUrl();
|
|
41985
41985
|
if (url) {
|
|
41986
|
-
|
|
41986
|
+
log2(`PersonaPlex already running at ${url}`);
|
|
41987
41987
|
return url;
|
|
41988
41988
|
}
|
|
41989
41989
|
}
|
|
41990
41990
|
if (!isPersonaPlexInstalled()) {
|
|
41991
|
-
|
|
41991
|
+
log2("PersonaPlex not installed. Run /voice personaplex to set up.");
|
|
41992
41992
|
return null;
|
|
41993
41993
|
}
|
|
41994
41994
|
mkdirSync15(PERSONAPLEX_DIR, { recursive: true });
|
|
@@ -42001,12 +42001,12 @@ async function startPersonaPlexDaemon(onInfo) {
|
|
|
42001
42001
|
if (tier !== "original") {
|
|
42002
42002
|
const cachedBf16 = join54(PERSONAPLEX_DIR, "model-bf16-cache.safetensors");
|
|
42003
42003
|
if (tier === "nf4-distilled") {
|
|
42004
|
-
|
|
42004
|
+
log2(`Weight tier: ${tier} \u2014 distilled NF4 (90% token match, ${repoInfo.sizeGB}GB)...`);
|
|
42005
42005
|
try {
|
|
42006
42006
|
const weightPath = execSync28(`"${venvPython2}" -c "from huggingface_hub import hf_hub_download; print(hf_hub_download('${repoInfo.repo}', '${repoInfo.file}', token=False))"`, { encoding: "utf8", timeout: 6e4, stdio: "pipe" }).trim();
|
|
42007
42007
|
if (existsSync37(weightPath)) {
|
|
42008
42008
|
if (!existsSync37(cachedBf16)) {
|
|
42009
|
-
|
|
42009
|
+
log2("Converting .pt checkpoint to safetensors (one-time)...");
|
|
42010
42010
|
execSync28(`"${venvPython2}" -c "
|
|
42011
42011
|
import torch; from safetensors.torch import save_file
|
|
42012
42012
|
state = torch.load('${weightPath}', map_location='cpu', weights_only=True)
|
|
@@ -42017,16 +42017,16 @@ print('Converted')
|
|
|
42017
42017
|
}
|
|
42018
42018
|
if (existsSync37(cachedBf16)) {
|
|
42019
42019
|
extraArgs.push("--moshi-weight", cachedBf16);
|
|
42020
|
-
|
|
42020
|
+
log2(`Using distilled weights: ${(statSync13(cachedBf16).size / 1024 ** 3).toFixed(1)}GB`);
|
|
42021
42021
|
} else {
|
|
42022
42022
|
extraArgs.push("--moshi-weight", weightPath);
|
|
42023
42023
|
}
|
|
42024
42024
|
}
|
|
42025
42025
|
} catch (e) {
|
|
42026
|
-
|
|
42026
|
+
log2(`Failed to load distilled weights \u2014 falling back to standard NF4`);
|
|
42027
42027
|
}
|
|
42028
42028
|
} else {
|
|
42029
|
-
|
|
42029
|
+
log2(`Weight tier: ${tier} (${repoInfo.sizeGB}GB) \u2014 dequantizing to bf16 cache...`);
|
|
42030
42030
|
const dequantScript = join54(PERSONAPLEX_DIR, "dequant-loader.py");
|
|
42031
42031
|
if (!existsSync37(dequantScript)) {
|
|
42032
42032
|
const shipped = getShippedVoicesDir();
|
|
@@ -42043,10 +42043,10 @@ print('Converted')
|
|
|
42043
42043
|
execSync28(`"${venvPython2}" "${dequantScript}" --input "${weightPath}" --output "${cachedBf16}"`, { timeout: 3e5, stdio: "pipe" });
|
|
42044
42044
|
if (existsSync37(cachedBf16)) {
|
|
42045
42045
|
extraArgs.push("--moshi-weight", cachedBf16);
|
|
42046
|
-
|
|
42046
|
+
log2(`Using dequantized cache: ${(statSync13(cachedBf16).size / 1024 ** 3).toFixed(1)}GB`);
|
|
42047
42047
|
}
|
|
42048
42048
|
} catch (e) {
|
|
42049
|
-
|
|
42049
|
+
log2(`Dequantization failed \u2014 server will try to load original weights`);
|
|
42050
42050
|
}
|
|
42051
42051
|
}
|
|
42052
42052
|
try {
|
|
@@ -42062,7 +42062,7 @@ print('Converted')
|
|
|
42062
42062
|
} catch {
|
|
42063
42063
|
}
|
|
42064
42064
|
} catch {
|
|
42065
|
-
|
|
42065
|
+
log2(`Weight file not found \u2014 server will download on first run`);
|
|
42066
42066
|
}
|
|
42067
42067
|
extraArgs.push("--hf-repo", repoInfo.repo);
|
|
42068
42068
|
}
|
|
@@ -42087,17 +42087,17 @@ print('Converted')
|
|
|
42087
42087
|
});
|
|
42088
42088
|
if (ollamaCheck.includes("models")) {
|
|
42089
42089
|
hybridEnabled = true;
|
|
42090
|
-
|
|
42090
|
+
log2(`Hybrid mode: PersonaPlex voice + ${ollamaModel} reasoning`);
|
|
42091
42091
|
}
|
|
42092
42092
|
} catch {
|
|
42093
|
-
|
|
42093
|
+
log2("Ollama not detected \u2014 running PersonaPlex standalone (no hybrid)");
|
|
42094
42094
|
}
|
|
42095
42095
|
const caps = detectPersonaPlexCapability();
|
|
42096
42096
|
const needsOffload = caps.vramGB > 0 && caps.vramGB < 24;
|
|
42097
42097
|
if (needsOffload) {
|
|
42098
|
-
|
|
42098
|
+
log2(`GPU has ${caps.vramGB.toFixed(0)}GB VRAM \u2014 enabling CPU offload (model needs ~19GB)`);
|
|
42099
42099
|
}
|
|
42100
|
-
|
|
42100
|
+
log2(`Starting PersonaPlex daemon (${tier} tier${hybridEnabled ? ", hybrid" : ""}${needsOffload ? ", cpu-offload" : ""})...`);
|
|
42101
42101
|
const serverArgs = [
|
|
42102
42102
|
"-m",
|
|
42103
42103
|
"moshi.server",
|
|
@@ -42153,7 +42153,7 @@ print('Converted')
|
|
|
42153
42153
|
if (child.pid)
|
|
42154
42154
|
process.kill(child.pid, 0);
|
|
42155
42155
|
} catch {
|
|
42156
|
-
|
|
42156
|
+
log2(`PersonaPlex daemon exited unexpectedly. Check ${fileLink2(LOG_FILE, "daemon.log")}`);
|
|
42157
42157
|
return null;
|
|
42158
42158
|
}
|
|
42159
42159
|
try {
|
|
@@ -42163,16 +42163,16 @@ print('Converted')
|
|
|
42163
42163
|
encoding: "utf8"
|
|
42164
42164
|
});
|
|
42165
42165
|
const url = `wss://127.0.0.1:${PORT}`;
|
|
42166
|
-
|
|
42166
|
+
log2(`PersonaPlex ready at ${url}`);
|
|
42167
42167
|
return url;
|
|
42168
42168
|
} catch {
|
|
42169
42169
|
}
|
|
42170
42170
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
42171
42171
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
42172
42172
|
if (elapsed % 10 === 0)
|
|
42173
|
-
|
|
42173
|
+
log2(`Still loading... (${elapsed}s)`);
|
|
42174
42174
|
}
|
|
42175
|
-
|
|
42175
|
+
log2(`PersonaPlex daemon failed to start within 120s. Check ${fileLink2(LOG_FILE, "daemon.log")}`);
|
|
42176
42176
|
return null;
|
|
42177
42177
|
}
|
|
42178
42178
|
function stopPersonaPlex() {
|
|
@@ -42229,30 +42229,30 @@ function listPersonaPlexVoices() {
|
|
|
42229
42229
|
return voices;
|
|
42230
42230
|
}
|
|
42231
42231
|
async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
|
|
42232
|
-
const
|
|
42232
|
+
const log2 = onInfo ?? (() => {
|
|
42233
42233
|
});
|
|
42234
42234
|
if (!isPersonaPlexInstalled()) {
|
|
42235
|
-
|
|
42235
|
+
log2("PersonaPlex not installed. Run /voice personaplex first.");
|
|
42236
42236
|
return null;
|
|
42237
42237
|
}
|
|
42238
42238
|
if (!existsSync37(inputWav)) {
|
|
42239
|
-
|
|
42239
|
+
log2(`Input WAV not found: ${inputWav}`);
|
|
42240
42240
|
return null;
|
|
42241
42241
|
}
|
|
42242
42242
|
mkdirSync15(CUSTOM_VOICES_DIR, { recursive: true });
|
|
42243
42243
|
const outputPt = join54(CUSTOM_VOICES_DIR, `${voiceName}.pt`);
|
|
42244
42244
|
if (existsSync37(outputPt)) {
|
|
42245
|
-
|
|
42245
|
+
log2(`Voice "${voiceName}" already exists. Delete ${outputPt} to re-clone.`);
|
|
42246
42246
|
return outputPt;
|
|
42247
42247
|
}
|
|
42248
42248
|
const venvPython2 = process.platform === "win32" ? join54(PERSONAPLEX_DIR, "venv", "Scripts", "python.exe") : join54(PERSONAPLEX_DIR, "venv", "bin", "python3");
|
|
42249
42249
|
const cloneScript = join54(PERSONAPLEX_DIR, "clone-voice.py");
|
|
42250
42250
|
if (!existsSync37(cloneScript)) {
|
|
42251
|
-
|
|
42251
|
+
log2("clone-voice.py not found. Reinstall PersonaPlex.");
|
|
42252
42252
|
return null;
|
|
42253
42253
|
}
|
|
42254
|
-
|
|
42255
|
-
|
|
42254
|
+
log2(`Cloning voice "${voiceName}" from ${inputWav}...`);
|
|
42255
|
+
log2("This requires loading the full 7B model \u2014 may take 30-60s...");
|
|
42256
42256
|
return new Promise((resolve36) => {
|
|
42257
42257
|
const child = spawn19(venvPython2, [
|
|
42258
42258
|
cloneScript,
|
|
@@ -42272,7 +42272,7 @@ async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
|
|
|
42272
42272
|
const line = d.toString();
|
|
42273
42273
|
output += line;
|
|
42274
42274
|
for (const l of line.split("\n").filter((s) => s.trim())) {
|
|
42275
|
-
|
|
42275
|
+
log2(l.trim());
|
|
42276
42276
|
}
|
|
42277
42277
|
});
|
|
42278
42278
|
child.stderr?.on("data", (d) => {
|
|
@@ -42280,10 +42280,10 @@ async function clonePersonaPlexVoice(inputWav, voiceName, onInfo) {
|
|
|
42280
42280
|
});
|
|
42281
42281
|
child.on("close", (code) => {
|
|
42282
42282
|
if (code === 0 && existsSync37(outputPt)) {
|
|
42283
|
-
|
|
42283
|
+
log2(`Voice "${voiceName}" cloned successfully.`);
|
|
42284
42284
|
resolve36(outputPt);
|
|
42285
42285
|
} else {
|
|
42286
|
-
|
|
42286
|
+
log2(`Voice cloning failed (exit ${code}).`);
|
|
42287
42287
|
resolve36(null);
|
|
42288
42288
|
}
|
|
42289
42289
|
});
|
|
@@ -42315,7 +42315,7 @@ function getShippedVoicesDir() {
|
|
|
42315
42315
|
return null;
|
|
42316
42316
|
}
|
|
42317
42317
|
function provisionShippedVoices(onInfo) {
|
|
42318
|
-
const
|
|
42318
|
+
const log2 = onInfo ?? (() => {
|
|
42319
42319
|
});
|
|
42320
42320
|
const shippedDir = getShippedVoicesDir();
|
|
42321
42321
|
if (!shippedDir)
|
|
@@ -42335,7 +42335,7 @@ function provisionShippedVoices(onInfo) {
|
|
|
42335
42335
|
const hfDst = join54(hfVoicesDir, f);
|
|
42336
42336
|
if (!existsSync37(hfDst)) {
|
|
42337
42337
|
copyFileSync2(join54(shippedDir, f), hfDst);
|
|
42338
|
-
|
|
42338
|
+
log2(`Deployed voice: ${f.replace(".pt", "")}`);
|
|
42339
42339
|
deployed++;
|
|
42340
42340
|
}
|
|
42341
42341
|
}
|
|
@@ -42370,7 +42370,7 @@ function getHFVoicesDir() {
|
|
|
42370
42370
|
return null;
|
|
42371
42371
|
}
|
|
42372
42372
|
function patchFrontendVoiceList(onInfo) {
|
|
42373
|
-
const
|
|
42373
|
+
const log2 = onInfo ?? (() => {
|
|
42374
42374
|
});
|
|
42375
42375
|
const hfBase = join54(homedir13(), ".cache", "huggingface", "hub", "models--nvidia--personaplex-7b-v1");
|
|
42376
42376
|
if (!existsSync37(hfBase))
|
|
@@ -42404,7 +42404,7 @@ function patchFrontendVoiceList(onInfo) {
|
|
|
42404
42404
|
const additions = customVoices.map((v) => `"${v}"`).join(", ");
|
|
42405
42405
|
js = js.replace(needle, `"VARM4.pt", ${additions}]`);
|
|
42406
42406
|
writeFileSync16(jsPath, js);
|
|
42407
|
-
|
|
42407
|
+
log2(`Added ${customVoices.length} custom voice(s) to frontend: ${customVoices.map((v) => v.replace(".pt", "")).join(", ")}`);
|
|
42408
42408
|
}
|
|
42409
42409
|
}
|
|
42410
42410
|
}
|
|
@@ -42412,36 +42412,36 @@ function patchFrontendVoiceList(onInfo) {
|
|
|
42412
42412
|
}
|
|
42413
42413
|
}
|
|
42414
42414
|
async function autoSetupPersonaPlex(onInfo) {
|
|
42415
|
-
const
|
|
42415
|
+
const log2 = onInfo ?? (() => {
|
|
42416
42416
|
});
|
|
42417
42417
|
const caps = detectPersonaPlexCapability();
|
|
42418
42418
|
if (!caps.supported) {
|
|
42419
|
-
|
|
42419
|
+
log2(`PersonaPlex not available: ${caps.reason}`);
|
|
42420
42420
|
return null;
|
|
42421
42421
|
}
|
|
42422
42422
|
const tierInfo = WEIGHT_REPOS[caps.weightTier];
|
|
42423
|
-
|
|
42423
|
+
log2(`GPU: ${caps.gpuName} (${caps.vramGB.toFixed(0)}GB) \u2192 ${caps.weightTier} weights (${tierInfo.sizeGB}GB${caps.needsHfToken ? "" : ", no HF token needed"})`);
|
|
42424
42424
|
if (!isPersonaPlexInstalled()) {
|
|
42425
|
-
|
|
42426
|
-
const ok = await installPersonaPlex(
|
|
42425
|
+
log2("Installing PersonaPlex (first time setup)...");
|
|
42426
|
+
const ok = await installPersonaPlex(log2, caps.weightTier);
|
|
42427
42427
|
if (!ok) {
|
|
42428
|
-
|
|
42428
|
+
log2("PersonaPlex installation failed.");
|
|
42429
42429
|
return null;
|
|
42430
42430
|
}
|
|
42431
42431
|
}
|
|
42432
|
-
const deployed = provisionShippedVoices(
|
|
42432
|
+
const deployed = provisionShippedVoices(log2);
|
|
42433
42433
|
if (deployed > 0) {
|
|
42434
|
-
|
|
42434
|
+
log2(`Provisioned ${deployed} shipped voice(s)`);
|
|
42435
42435
|
}
|
|
42436
|
-
patchFrontendVoiceList(
|
|
42436
|
+
patchFrontendVoiceList(log2);
|
|
42437
42437
|
if (isPersonaPlexRunning()) {
|
|
42438
42438
|
const url = getPersonaPlexWSUrl();
|
|
42439
42439
|
if (url) {
|
|
42440
|
-
|
|
42440
|
+
log2(`PersonaPlex already running at ${url}`);
|
|
42441
42441
|
return url;
|
|
42442
42442
|
}
|
|
42443
42443
|
}
|
|
42444
|
-
return await startPersonaPlexDaemon(
|
|
42444
|
+
return await startPersonaPlexDaemon(log2);
|
|
42445
42445
|
}
|
|
42446
42446
|
var WEIGHT_REPOS, PERSONAPLEX_DIR, PID_FILE, PORT_FILE, LOG_FILE, CUSTOM_VOICES_DIR;
|
|
42447
42447
|
var init_personaplex = __esm({
|
|
@@ -43624,20 +43624,20 @@ function hasVenvModule() {
|
|
|
43624
43624
|
return false;
|
|
43625
43625
|
}
|
|
43626
43626
|
}
|
|
43627
|
-
function ensureVenv(
|
|
43627
|
+
function ensureVenv(log2) {
|
|
43628
43628
|
const venvDir = getVenvDir();
|
|
43629
43629
|
const isWin2 = process.platform === "win32";
|
|
43630
43630
|
const pipPath = isWin2 ? join55(venvDir, "Scripts", "pip.exe") : join55(venvDir, "bin", "pip");
|
|
43631
43631
|
const pythonCmd = isWin2 ? "python" : "python3";
|
|
43632
43632
|
if (existsSync38(pipPath))
|
|
43633
43633
|
return venvDir;
|
|
43634
|
-
|
|
43634
|
+
log2("Creating Python venv for vision deps...");
|
|
43635
43635
|
if (!hasCmd(pythonCmd) && !hasCmd("python3")) {
|
|
43636
|
-
|
|
43636
|
+
log2(`${pythonCmd} not found \u2014 cannot create venv.`);
|
|
43637
43637
|
return null;
|
|
43638
43638
|
}
|
|
43639
43639
|
if (!isWin2 && !hasVenvModule()) {
|
|
43640
|
-
|
|
43640
|
+
log2("python3 venv module not available \u2014 venv creation skipped.");
|
|
43641
43641
|
return null;
|
|
43642
43642
|
}
|
|
43643
43643
|
try {
|
|
@@ -43648,10 +43648,10 @@ function ensureVenv(log) {
|
|
|
43648
43648
|
stdio: "pipe",
|
|
43649
43649
|
timeout: 6e4
|
|
43650
43650
|
});
|
|
43651
|
-
|
|
43651
|
+
log2("Python venv created at ~/.open-agents/venv");
|
|
43652
43652
|
return venvDir;
|
|
43653
43653
|
} catch (err) {
|
|
43654
|
-
|
|
43654
|
+
log2(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
|
|
43655
43655
|
return null;
|
|
43656
43656
|
}
|
|
43657
43657
|
}
|
|
@@ -43688,7 +43688,7 @@ function runWithSudo(cmd, password, timeoutMs = 12e4) {
|
|
|
43688
43688
|
function validateSudoPassword(password) {
|
|
43689
43689
|
return runWithSudo("true", password, 1e4) === "ok";
|
|
43690
43690
|
}
|
|
43691
|
-
async function acquireSudoPassword(getSudoPassword,
|
|
43691
|
+
async function acquireSudoPassword(getSudoPassword, log2, cachedPasswordRef) {
|
|
43692
43692
|
if (cachedPasswordRef.value && validateSudoPassword(cachedPasswordRef.value)) {
|
|
43693
43693
|
return cachedPasswordRef.value;
|
|
43694
43694
|
}
|
|
@@ -43697,7 +43697,7 @@ async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
|
|
|
43697
43697
|
}
|
|
43698
43698
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
43699
43699
|
if (attempt > 0)
|
|
43700
|
-
|
|
43700
|
+
log2("Authentication failed \u2014 please re-enter password.");
|
|
43701
43701
|
const pw = await getSudoPassword();
|
|
43702
43702
|
if (!pw)
|
|
43703
43703
|
return null;
|
|
@@ -43708,10 +43708,10 @@ async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
|
|
|
43708
43708
|
}
|
|
43709
43709
|
return null;
|
|
43710
43710
|
}
|
|
43711
|
-
async function sudoInstall(cmd, getSudoPassword,
|
|
43711
|
+
async function sudoInstall(cmd, getSudoPassword, log2, cachedPasswordRef, timeoutMs = 12e4) {
|
|
43712
43712
|
if (trySudoPasswordless(cmd, timeoutMs))
|
|
43713
43713
|
return true;
|
|
43714
|
-
const pw = await acquireSudoPassword(getSudoPassword,
|
|
43714
|
+
const pw = await acquireSudoPassword(getSudoPassword, log2, cachedPasswordRef);
|
|
43715
43715
|
if (pw === null)
|
|
43716
43716
|
return false;
|
|
43717
43717
|
if (pw === "")
|
|
@@ -43720,12 +43720,12 @@ async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeout
|
|
|
43720
43720
|
if (result === "ok")
|
|
43721
43721
|
return true;
|
|
43722
43722
|
if (result === "cmd_fail") {
|
|
43723
|
-
|
|
43723
|
+
log2(`Install command failed (not an auth issue) \u2014 package may not be available for this OS.`);
|
|
43724
43724
|
}
|
|
43725
43725
|
return false;
|
|
43726
43726
|
}
|
|
43727
43727
|
async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
43728
|
-
const
|
|
43728
|
+
const log2 = onInfo ?? (() => {
|
|
43729
43729
|
});
|
|
43730
43730
|
const cachedPasswordRef = { value: null };
|
|
43731
43731
|
const getPassword = getSudoPassword ?? (() => Promise.resolve(null));
|
|
@@ -43751,7 +43751,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43751
43751
|
if (missing.length === 0) {
|
|
43752
43752
|
} else if (!pm2) {
|
|
43753
43753
|
for (const d of missing)
|
|
43754
|
-
|
|
43754
|
+
log2(`No supported package manager (choco/winget/apt/brew) \u2014 ${d.label} unavailable. Install manually or install Chocolatey: https://chocolatey.org/install`);
|
|
43755
43755
|
} else {
|
|
43756
43756
|
const labels = missing.map((d) => d.label).join(", ");
|
|
43757
43757
|
let winNeedsElevation = false;
|
|
@@ -43760,19 +43760,19 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43760
43760
|
execSync29("net session", { stdio: "pipe", timeout: 3e3 });
|
|
43761
43761
|
} catch {
|
|
43762
43762
|
winNeedsElevation = true;
|
|
43763
|
-
|
|
43763
|
+
log2(`Installing ${labels} via ${pm2} (requires admin \u2014 UAC prompt will appear)...`);
|
|
43764
43764
|
}
|
|
43765
43765
|
if (!winNeedsElevation)
|
|
43766
|
-
|
|
43766
|
+
log2(`Installing ${labels} via ${pm2}...`);
|
|
43767
43767
|
} else {
|
|
43768
|
-
|
|
43768
|
+
log2(`Installing ${labels} via ${pm2}...`);
|
|
43769
43769
|
}
|
|
43770
43770
|
const needsSudo = pm2 !== "brew" && pm2 !== "choco" && pm2 !== "winget";
|
|
43771
43771
|
for (const d of missing) {
|
|
43772
43772
|
const pkgName = d.pkgs[pm2];
|
|
43773
43773
|
const pipPkg = d.pkgs.pip;
|
|
43774
43774
|
if (!pkgName && !pipPkg) {
|
|
43775
|
-
|
|
43775
|
+
log2(`${d.label} not available for ${pm2}.`);
|
|
43776
43776
|
continue;
|
|
43777
43777
|
}
|
|
43778
43778
|
let lastError = "";
|
|
@@ -43803,7 +43803,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43803
43803
|
}
|
|
43804
43804
|
try {
|
|
43805
43805
|
if (needsSudo) {
|
|
43806
|
-
await sudoInstall(installCmd, getPassword,
|
|
43806
|
+
await sudoInstall(installCmd, getPassword, log2, cachedPasswordRef, 18e4);
|
|
43807
43807
|
} else if (winNeedsElevation) {
|
|
43808
43808
|
execSync29(`powershell -NoProfile -Command "Start-Process -FilePath 'cmd.exe' -ArgumentList '/c ${installCmd.replace(/'/g, "''")}' -Verb RunAs -Wait"`, { stdio: "pipe", timeout: 18e4 });
|
|
43809
43809
|
} else {
|
|
@@ -43840,10 +43840,10 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43840
43840
|
}
|
|
43841
43841
|
}
|
|
43842
43842
|
if (hasCmd(d.binary)) {
|
|
43843
|
-
|
|
43843
|
+
log2(`${d.label} installed.`);
|
|
43844
43844
|
} else {
|
|
43845
43845
|
const reason = lastError ? ` Reason: ${lastError}` : " (binary not found in PATH after install \u2014 may need terminal restart or admin elevation)";
|
|
43846
|
-
|
|
43846
|
+
log2(`${d.label} could not be installed \u2014 features will be limited.${reason}`);
|
|
43847
43847
|
}
|
|
43848
43848
|
}
|
|
43849
43849
|
}
|
|
@@ -43856,10 +43856,10 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43856
43856
|
};
|
|
43857
43857
|
const pipCmd = pipCmds[pm];
|
|
43858
43858
|
if (pipCmd) {
|
|
43859
|
-
|
|
43860
|
-
const ok = await sudoInstall(pipCmd, getPassword,
|
|
43859
|
+
log2("Installing python3-pip...");
|
|
43860
|
+
const ok = await sudoInstall(pipCmd, getPassword, log2, cachedPasswordRef);
|
|
43861
43861
|
if (!ok) {
|
|
43862
|
-
|
|
43862
|
+
log2("python3-pip could not be installed \u2014 moondream-station may be unavailable.");
|
|
43863
43863
|
}
|
|
43864
43864
|
}
|
|
43865
43865
|
}
|
|
@@ -43879,10 +43879,10 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43879
43879
|
if (cmdFn) {
|
|
43880
43880
|
const cmd = cmdFn();
|
|
43881
43881
|
if (cmd) {
|
|
43882
|
-
|
|
43883
|
-
const ok = await sudoInstall(cmd, getPassword,
|
|
43882
|
+
log2("Installing python3-venv...");
|
|
43883
|
+
const ok = await sudoInstall(cmd, getPassword, log2, cachedPasswordRef);
|
|
43884
43884
|
if (!ok) {
|
|
43885
|
-
|
|
43885
|
+
log2("python3-venv could not be installed \u2014 moondream-station may be unavailable.");
|
|
43886
43886
|
}
|
|
43887
43887
|
}
|
|
43888
43888
|
}
|
|
@@ -43891,26 +43891,26 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43891
43891
|
const isWin2 = process.platform === "win32";
|
|
43892
43892
|
const venvBin = join55(venvDir, isWin2 ? "Scripts" : "bin");
|
|
43893
43893
|
const venvMoondream = join55(venvBin, isWin2 ? "moondream-station.exe" : "moondream-station");
|
|
43894
|
-
const venv = ensureVenv(
|
|
43894
|
+
const venv = ensureVenv(log2);
|
|
43895
43895
|
if (venv && !hasCmd("moondream-station") && !existsSync38(venvMoondream)) {
|
|
43896
43896
|
const venvPip2 = join55(venvBin, "pip");
|
|
43897
|
-
|
|
43897
|
+
log2("Installing moondream-station in ~/.open-agents/venv...");
|
|
43898
43898
|
try {
|
|
43899
43899
|
execSync29(`"${venvPip2}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
|
|
43900
43900
|
if (existsSync38(venvMoondream)) {
|
|
43901
|
-
|
|
43901
|
+
log2("moondream-station installed successfully.");
|
|
43902
43902
|
} else {
|
|
43903
43903
|
try {
|
|
43904
43904
|
const check = execSync29(`"${venvPip2}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
|
|
43905
43905
|
if (check.includes("moondream")) {
|
|
43906
|
-
|
|
43906
|
+
log2("moondream-station package installed.");
|
|
43907
43907
|
}
|
|
43908
43908
|
} catch {
|
|
43909
|
-
|
|
43909
|
+
log2("moondream-station install completed.");
|
|
43910
43910
|
}
|
|
43911
43911
|
}
|
|
43912
43912
|
} catch (err) {
|
|
43913
|
-
|
|
43913
|
+
log2(`moondream-station install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
43914
43914
|
}
|
|
43915
43915
|
}
|
|
43916
43916
|
if (venv) {
|
|
@@ -43924,21 +43924,21 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
43924
43924
|
}
|
|
43925
43925
|
if (!ocrStackInstalled) {
|
|
43926
43926
|
const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
|
|
43927
|
-
|
|
43927
|
+
log2("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
|
|
43928
43928
|
try {
|
|
43929
43929
|
execSync29(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
|
|
43930
43930
|
try {
|
|
43931
43931
|
execSync29(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
|
|
43932
|
-
|
|
43932
|
+
log2("OCR Python stack installed successfully.");
|
|
43933
43933
|
} catch {
|
|
43934
|
-
|
|
43934
|
+
log2("OCR Python stack install completed but import verification failed.");
|
|
43935
43935
|
}
|
|
43936
43936
|
} catch (err) {
|
|
43937
|
-
|
|
43937
|
+
log2(`OCR Python stack install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
43938
43938
|
}
|
|
43939
43939
|
}
|
|
43940
43940
|
} else {
|
|
43941
|
-
|
|
43941
|
+
log2("Python venv unavailable \u2014 advanced OCR pipeline will fall back to basic tesseract.");
|
|
43942
43942
|
}
|
|
43943
43943
|
}
|
|
43944
43944
|
function ensureCloudflaredBackground(onInfo) {
|
|
@@ -43948,14 +43948,14 @@ function ensureCloudflaredBackground(onInfo) {
|
|
|
43948
43948
|
_cloudflaredInstallPromise = Promise.resolve(true);
|
|
43949
43949
|
return;
|
|
43950
43950
|
}
|
|
43951
|
-
const
|
|
43951
|
+
const log2 = onInfo ?? (() => {
|
|
43952
43952
|
});
|
|
43953
|
-
|
|
43953
|
+
log2("Installing cloudflared for live voice sessions...");
|
|
43954
43954
|
_cloudflaredInstallPromise = (async () => {
|
|
43955
43955
|
const arch2 = process.arch;
|
|
43956
43956
|
const os = platform2();
|
|
43957
43957
|
if (os !== "win32" && !ensureCurl()) {
|
|
43958
|
-
|
|
43958
|
+
log2("curl not available \u2014 cannot install cloudflared.");
|
|
43959
43959
|
return false;
|
|
43960
43960
|
}
|
|
43961
43961
|
if (os === "linux") {
|
|
@@ -43967,7 +43967,7 @@ function ensureCloudflaredBackground(onInfo) {
|
|
|
43967
43967
|
process.env.PATH = `${homedir14()}/.local/bin:${process.env.PATH}`;
|
|
43968
43968
|
}
|
|
43969
43969
|
if (hasCmd("cloudflared")) {
|
|
43970
|
-
|
|
43970
|
+
log2("cloudflared installed.");
|
|
43971
43971
|
return true;
|
|
43972
43972
|
}
|
|
43973
43973
|
} catch {
|
|
@@ -43975,7 +43975,7 @@ function ensureCloudflaredBackground(onInfo) {
|
|
|
43975
43975
|
try {
|
|
43976
43976
|
execSync29(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null`, { stdio: "pipe", timeout: 6e4 });
|
|
43977
43977
|
if (hasCmd("cloudflared")) {
|
|
43978
|
-
|
|
43978
|
+
log2("cloudflared installed.");
|
|
43979
43979
|
return true;
|
|
43980
43980
|
}
|
|
43981
43981
|
} catch {
|
|
@@ -43984,13 +43984,13 @@ function ensureCloudflaredBackground(onInfo) {
|
|
|
43984
43984
|
try {
|
|
43985
43985
|
execSync29("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
|
|
43986
43986
|
if (hasCmd("cloudflared")) {
|
|
43987
|
-
|
|
43987
|
+
log2("cloudflared installed via Homebrew.");
|
|
43988
43988
|
return true;
|
|
43989
43989
|
}
|
|
43990
43990
|
} catch {
|
|
43991
43991
|
}
|
|
43992
43992
|
}
|
|
43993
|
-
|
|
43993
|
+
log2("cloudflared not available \u2014 live voice sessions disabled.");
|
|
43994
43994
|
return false;
|
|
43995
43995
|
})();
|
|
43996
43996
|
}
|
|
@@ -54096,10 +54096,10 @@ function getGitInfo(repoRoot) {
|
|
|
54096
54096
|
} catch {
|
|
54097
54097
|
}
|
|
54098
54098
|
try {
|
|
54099
|
-
const
|
|
54100
|
-
if (
|
|
54099
|
+
const log2 = execSync32("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
|
|
54100
|
+
if (log2)
|
|
54101
54101
|
lines.push(`Recent commits:
|
|
54102
|
-
${
|
|
54102
|
+
${log2}`);
|
|
54103
54103
|
} catch {
|
|
54104
54104
|
}
|
|
54105
54105
|
return lines.join("\n");
|
|
@@ -65534,7 +65534,24 @@ body {
|
|
|
65534
65534
|
<textarea id="system-prompt" placeholder="System prompt (optional)"></textarea>
|
|
65535
65535
|
</div>
|
|
65536
65536
|
|
|
65537
|
+
<div id="tabs" style="display:flex;gap:0;background:#1e1e22;border-bottom:1px solid #2a2a30;padding:0 16px;flex-shrink:0">
|
|
65538
|
+
<button class="tab active" onclick="switchTab('chat')" id="tab-chat" style="background:none;border:none;border-bottom:2px solid #b2920a;color:#b2920a;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">chat</button>
|
|
65539
|
+
<button class="tab" onclick="switchTab('agent')" id="tab-agent" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">agent</button>
|
|
65540
|
+
<button class="tab" onclick="switchTab('jobs')" id="tab-jobs" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">jobs</button>
|
|
65541
|
+
</div>
|
|
65537
65542
|
<div id="conversation"></div>
|
|
65543
|
+
<div id="agent-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
65544
|
+
<textarea id="agent-task" placeholder="Describe the task for the agent..." style="width:100%;min-height:80px;background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:8px 12px;color:#b0b0b0;font-family:inherit;font-size:0.82rem;resize:vertical;outline:none;margin-bottom:8px"></textarea>
|
|
65545
|
+
<div style="display:flex;gap:8px;margin-bottom:12px;align-items:center">
|
|
65546
|
+
<select id="agent-profile" style="background:#2a2a30;border:1px solid #3a3a42;color:#b0b0b0;padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem"><option value="">no profile</option></select>
|
|
65547
|
+
<button onclick="submitAgentTask()" id="agent-submit" style="background:#2a2a30;border:1px solid #3a3a42;color:#b2920a;padding:6px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer">run task</button>
|
|
65548
|
+
<button onclick="abortAgentTask()" id="agent-abort" style="display:none;background:#2a2a30;border:1px solid #ff4444;color:#ff4444;padding:6px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer">abort</button>
|
|
65549
|
+
</div>
|
|
65550
|
+
<div id="agent-events" style="font-size:0.78rem;line-height:1.5"></div>
|
|
65551
|
+
</div>
|
|
65552
|
+
<div id="jobs-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
65553
|
+
<div id="jobs-list" style="font-size:0.78rem"></div>
|
|
65554
|
+
</div>
|
|
65538
65555
|
|
|
65539
65556
|
<div id="footer">
|
|
65540
65557
|
<span id="system-prompt-toggle" onclick="toggleSystemPrompt()">sys</span>
|
|
@@ -65759,6 +65776,124 @@ function closeKeyModal() {
|
|
|
65759
65776
|
document.getElementById('key-modal').classList.remove('visible');
|
|
65760
65777
|
}
|
|
65761
65778
|
|
|
65779
|
+
// Tab switching
|
|
65780
|
+
function switchTab(tab) {
|
|
65781
|
+
document.getElementById('conversation').style.display = tab === 'chat' ? 'flex' : 'none';
|
|
65782
|
+
document.getElementById('agent-panel').style.display = tab === 'agent' ? 'block' : 'none';
|
|
65783
|
+
document.getElementById('jobs-panel').style.display = tab === 'jobs' ? 'block' : 'none';
|
|
65784
|
+
document.getElementById('footer').style.display = tab === 'chat' ? 'flex' : 'none';
|
|
65785
|
+
document.querySelectorAll('.tab').forEach(t => { t.style.borderBottomColor = 'transparent'; t.style.color = '#555'; });
|
|
65786
|
+
const active = document.getElementById('tab-' + tab);
|
|
65787
|
+
if (active) { active.style.borderBottomColor = '#b2920a'; active.style.color = '#b2920a'; }
|
|
65788
|
+
if (tab === 'jobs') loadJobs();
|
|
65789
|
+
if (tab === 'agent') loadProfiles();
|
|
65790
|
+
}
|
|
65791
|
+
|
|
65792
|
+
// Agent task
|
|
65793
|
+
let currentRunId = null;
|
|
65794
|
+
async function loadProfiles() {
|
|
65795
|
+
try {
|
|
65796
|
+
const r = await fetch('/v1/profiles', { headers: headers() });
|
|
65797
|
+
const d = await r.json();
|
|
65798
|
+
const sel = document.getElementById('agent-profile');
|
|
65799
|
+
sel.innerHTML = '<option value="">no profile</option>';
|
|
65800
|
+
for (const p of (d.profiles || [])) {
|
|
65801
|
+
const opt = document.createElement('option');
|
|
65802
|
+
opt.value = p.name; opt.textContent = p.name + (p.encrypted ? ' (encrypted)' : '');
|
|
65803
|
+
sel.appendChild(opt);
|
|
65804
|
+
}
|
|
65805
|
+
} catch {}
|
|
65806
|
+
}
|
|
65807
|
+
|
|
65808
|
+
async function submitAgentTask() {
|
|
65809
|
+
const task = document.getElementById('agent-task').value.trim();
|
|
65810
|
+
if (!task) return;
|
|
65811
|
+
const eventsDiv = document.getElementById('agent-events');
|
|
65812
|
+
eventsDiv.innerHTML = '';
|
|
65813
|
+
document.getElementById('agent-submit').style.display = 'none';
|
|
65814
|
+
document.getElementById('agent-abort').style.display = 'inline-block';
|
|
65815
|
+
|
|
65816
|
+
const profile = document.getElementById('agent-profile').value;
|
|
65817
|
+
try {
|
|
65818
|
+
const resp = await fetch('/v1/run', {
|
|
65819
|
+
method: 'POST',
|
|
65820
|
+
headers: headers(),
|
|
65821
|
+
body: JSON.stringify({
|
|
65822
|
+
task, model: modelSelect.value, stream: true,
|
|
65823
|
+
...(profile ? { profile } : {}),
|
|
65824
|
+
}),
|
|
65825
|
+
});
|
|
65826
|
+
|
|
65827
|
+
const reader = resp.body.getReader();
|
|
65828
|
+
const decoder = new TextDecoder();
|
|
65829
|
+
let buffer = '';
|
|
65830
|
+
|
|
65831
|
+
while (true) {
|
|
65832
|
+
const { done, value } = await reader.read();
|
|
65833
|
+
if (done) break;
|
|
65834
|
+
buffer += decoder.decode(value, { stream: true });
|
|
65835
|
+
const lines = buffer.split('\\n');
|
|
65836
|
+
buffer = lines.pop() || '';
|
|
65837
|
+
|
|
65838
|
+
for (const line of lines) {
|
|
65839
|
+
if (!line.startsWith('data: ')) continue;
|
|
65840
|
+
const data = line.slice(6);
|
|
65841
|
+
if (data === '[DONE]') continue;
|
|
65842
|
+
try {
|
|
65843
|
+
const evt = JSON.parse(data);
|
|
65844
|
+
const div = document.createElement('div');
|
|
65845
|
+
div.style.padding = '2px 0';
|
|
65846
|
+
if (evt.type === 'run_started') {
|
|
65847
|
+
currentRunId = evt.run_id;
|
|
65848
|
+
div.innerHTML = '<span style="color:#b2920a">Task started</span> \u2014 run_id: ' + evt.run_id;
|
|
65849
|
+
} else if (evt.type === 'run_completed') {
|
|
65850
|
+
div.innerHTML = '<span style="color:' + (evt.exit_code === 0 ? '#4ec94e' : '#ff4444') + '">Task ' + (evt.exit_code === 0 ? 'completed' : 'failed') + '</span> \u2014 exit: ' + evt.exit_code;
|
|
65851
|
+
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
65852
|
+
document.getElementById('agent-abort').style.display = 'none';
|
|
65853
|
+
currentRunId = null;
|
|
65854
|
+
} else if (evt.type === 'stdout') {
|
|
65855
|
+
div.style.color = '#888';
|
|
65856
|
+
div.style.fontFamily = 'inherit';
|
|
65857
|
+
div.textContent = evt.data?.slice?.(0, 200) || '';
|
|
65858
|
+
}
|
|
65859
|
+
eventsDiv.appendChild(div);
|
|
65860
|
+
eventsDiv.scrollTop = eventsDiv.scrollHeight;
|
|
65861
|
+
} catch {}
|
|
65862
|
+
}
|
|
65863
|
+
}
|
|
65864
|
+
} catch (err) {
|
|
65865
|
+
eventsDiv.innerHTML += '<div style="color:#ff4444">Error: ' + escHtml(err.message) + '</div>';
|
|
65866
|
+
}
|
|
65867
|
+
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
65868
|
+
document.getElementById('agent-abort').style.display = 'none';
|
|
65869
|
+
}
|
|
65870
|
+
|
|
65871
|
+
async function abortAgentTask() {
|
|
65872
|
+
if (!currentRunId) return;
|
|
65873
|
+
try { await fetch('/v1/runs/' + currentRunId, { method: 'DELETE', headers: headers() }); } catch {}
|
|
65874
|
+
}
|
|
65875
|
+
|
|
65876
|
+
async function loadJobs() {
|
|
65877
|
+
const list = document.getElementById('jobs-list');
|
|
65878
|
+
try {
|
|
65879
|
+
const r = await fetch('/v1/runs', { headers: headers() });
|
|
65880
|
+
const d = await r.json();
|
|
65881
|
+
if (!d.runs?.length) { list.innerHTML = '<div style="color:#555">No jobs yet</div>'; return; }
|
|
65882
|
+
let html = '<table style="width:100%;border-collapse:collapse">';
|
|
65883
|
+
html += '<tr style="color:#b2920a;font-size:0.65rem"><th style="text-align:left;padding:4px">ID</th><th>Status</th><th>Task</th><th>Duration</th></tr>';
|
|
65884
|
+
for (const j of d.runs.slice(0, 20)) {
|
|
65885
|
+
const color = j.status === 'completed' ? '#4ec94e' : j.status === 'running' ? '#b2920a' : '#ff4444';
|
|
65886
|
+
const dur = j.durationMs ? (j.durationMs / 1000).toFixed(1) + 's' : '\u2014';
|
|
65887
|
+
html += '<tr style="border-top:1px solid #2a2a30"><td style="padding:4px;color:#888">' + (j.id||'').slice(0,12) + '</td>';
|
|
65888
|
+
html += '<td style="color:' + color + '">' + (j.status||'?') + '</td>';
|
|
65889
|
+
html += '<td style="color:#b0b0b0;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml((j.task||'').slice(0,60)) + '</td>';
|
|
65890
|
+
html += '<td style="color:#555">' + dur + '</td></tr>';
|
|
65891
|
+
}
|
|
65892
|
+
html += '</table>';
|
|
65893
|
+
list.innerHTML = html;
|
|
65894
|
+
} catch { list.innerHTML = '<div style="color:#ff4444">Failed to load jobs</div>'; }
|
|
65895
|
+
}
|
|
65896
|
+
|
|
65762
65897
|
// Init
|
|
65763
65898
|
checkHealth();
|
|
65764
65899
|
loadModels();
|
|
@@ -65774,6 +65909,38 @@ var init_web_ui = __esm({
|
|
|
65774
65909
|
}
|
|
65775
65910
|
});
|
|
65776
65911
|
|
|
65912
|
+
// packages/cli/dist/api/logger.js
|
|
65913
|
+
function log(level, fields) {
|
|
65914
|
+
if (LEVEL_NUM[level] > LEVEL_NUM[configuredLevel])
|
|
65915
|
+
return;
|
|
65916
|
+
if (useJson) {
|
|
65917
|
+
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, ...fields };
|
|
65918
|
+
process.stderr.write(JSON.stringify(entry) + "\n");
|
|
65919
|
+
} else {
|
|
65920
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
65921
|
+
const parts = Object.entries(fields).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
65922
|
+
process.stderr.write(`[${ts}] ${level.toUpperCase()} ${parts}
|
|
65923
|
+
`);
|
|
65924
|
+
}
|
|
65925
|
+
}
|
|
65926
|
+
function logRequest(fields) {
|
|
65927
|
+
log("info", fields);
|
|
65928
|
+
}
|
|
65929
|
+
var LEVEL_NUM, configuredLevel, useJson;
|
|
65930
|
+
var init_logger = __esm({
|
|
65931
|
+
"packages/cli/dist/api/logger.js"() {
|
|
65932
|
+
"use strict";
|
|
65933
|
+
LEVEL_NUM = { error: 0, warn: 1, info: 2, debug: 3 };
|
|
65934
|
+
configuredLevel = (() => {
|
|
65935
|
+
const env = (process.env["OA_LOG_LEVEL"] || "info").toLowerCase();
|
|
65936
|
+
if (env in LEVEL_NUM)
|
|
65937
|
+
return env;
|
|
65938
|
+
return "info";
|
|
65939
|
+
})();
|
|
65940
|
+
useJson = (process.env["OA_LOG_FORMAT"] || "json").toLowerCase() !== "text";
|
|
65941
|
+
}
|
|
65942
|
+
});
|
|
65943
|
+
|
|
65777
65944
|
// packages/cli/dist/api/profiles.js
|
|
65778
65945
|
import { existsSync as existsSync53, readFileSync as readFileSync42, writeFileSync as writeFileSync25, mkdirSync as mkdirSync27, readdirSync as readdirSync20, unlinkSync as unlinkSync12 } from "node:fs";
|
|
65779
65946
|
import { join as join70 } from "node:path";
|
|
@@ -66995,11 +67162,6 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
66995
67162
|
const startMs = performance.now();
|
|
66996
67163
|
const requestId = req.headers["x-request-id"] || randomUUID4();
|
|
66997
67164
|
res.setHeader("X-Request-ID", requestId);
|
|
66998
|
-
if (verbose) {
|
|
66999
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
67000
|
-
process.stderr.write(`[${ts}] ${requestId.slice(0, 8)} ${method} ${pathname}
|
|
67001
|
-
`);
|
|
67002
|
-
}
|
|
67003
67165
|
if (method === "OPTIONS") {
|
|
67004
67166
|
if (!corsHeaders(req, res))
|
|
67005
67167
|
return;
|
|
@@ -67214,6 +67376,16 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
67214
67376
|
});
|
|
67215
67377
|
} finally {
|
|
67216
67378
|
recordMetric(method, pathname, status);
|
|
67379
|
+
const latencyMs = Math.round(performance.now() - startMs);
|
|
67380
|
+
logRequest({
|
|
67381
|
+
requestId,
|
|
67382
|
+
method,
|
|
67383
|
+
path: pathname,
|
|
67384
|
+
status,
|
|
67385
|
+
latencyMs,
|
|
67386
|
+
user: req._authUser ?? "anonymous",
|
|
67387
|
+
scope: req._authScope ?? "none"
|
|
67388
|
+
});
|
|
67217
67389
|
recordAudit({
|
|
67218
67390
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67219
67391
|
requestId,
|
|
@@ -67228,7 +67400,7 @@ async function handleRequest(req, res, ollamaUrl, verbose) {
|
|
|
67228
67400
|
}
|
|
67229
67401
|
}
|
|
67230
67402
|
function startApiServer(options = {}) {
|
|
67231
|
-
const
|
|
67403
|
+
const log2 = options.quiet ? (_msg) => {
|
|
67232
67404
|
} : (msg) => process.stderr.write(msg);
|
|
67233
67405
|
let host = "127.0.0.1";
|
|
67234
67406
|
let port = 11435;
|
|
@@ -67253,7 +67425,24 @@ function startApiServer(options = {}) {
|
|
|
67253
67425
|
const ollamaUrl = options.ollamaUrl ?? config.backendUrl;
|
|
67254
67426
|
const cwd4 = process.cwd();
|
|
67255
67427
|
initAuditLog(join71(cwd4, ".oa"));
|
|
67256
|
-
const
|
|
67428
|
+
const tlsCert = process.env["OA_TLS_CERT"];
|
|
67429
|
+
const tlsKey = process.env["OA_TLS_KEY"];
|
|
67430
|
+
const useTls = !!(tlsCert && tlsKey);
|
|
67431
|
+
let tlsOpts = null;
|
|
67432
|
+
if (useTls) {
|
|
67433
|
+
try {
|
|
67434
|
+
tlsOpts = {
|
|
67435
|
+
cert: readFileSync43(resolve31(tlsCert)),
|
|
67436
|
+
key: readFileSync43(resolve31(tlsKey))
|
|
67437
|
+
};
|
|
67438
|
+
} catch (e) {
|
|
67439
|
+
log2(`
|
|
67440
|
+
ERROR: TLS cert/key could not be loaded: ${e.message}
|
|
67441
|
+
`);
|
|
67442
|
+
process.exit(1);
|
|
67443
|
+
}
|
|
67444
|
+
}
|
|
67445
|
+
const handler = (req, res) => {
|
|
67257
67446
|
handleRequest(req, res, ollamaUrl, verbose).catch((err) => {
|
|
67258
67447
|
metrics.totalErrors++;
|
|
67259
67448
|
try {
|
|
@@ -67264,12 +67453,13 @@ function startApiServer(options = {}) {
|
|
|
67264
67453
|
} catch {
|
|
67265
67454
|
}
|
|
67266
67455
|
});
|
|
67267
|
-
}
|
|
67456
|
+
};
|
|
67457
|
+
const server = useTls ? https.createServer(tlsOpts, handler) : http.createServer(handler);
|
|
67268
67458
|
let retried = false;
|
|
67269
67459
|
server.on("error", (err) => {
|
|
67270
67460
|
if (err.code === "EADDRINUSE" && !retried) {
|
|
67271
67461
|
retried = true;
|
|
67272
|
-
|
|
67462
|
+
log2(` Port ${port} in use \u2014 reclaiming...
|
|
67273
67463
|
`);
|
|
67274
67464
|
try {
|
|
67275
67465
|
const { execSync: es } = __require("node:child_process");
|
|
@@ -67295,10 +67485,10 @@ function startApiServer(options = {}) {
|
|
|
67295
67485
|
server.listen(port, host);
|
|
67296
67486
|
}, 2e3);
|
|
67297
67487
|
} else if (err.code === "EADDRINUSE" && retried) {
|
|
67298
|
-
|
|
67488
|
+
log2(` Port ${port} still in use after reclaim attempt \u2014 API server skipped (non-fatal).
|
|
67299
67489
|
`);
|
|
67300
67490
|
} else {
|
|
67301
|
-
|
|
67491
|
+
log2(` API server error (non-fatal): ${err.message}
|
|
67302
67492
|
`);
|
|
67303
67493
|
}
|
|
67304
67494
|
});
|
|
@@ -67319,43 +67509,44 @@ function startApiServer(options = {}) {
|
|
|
67319
67509
|
});
|
|
67320
67510
|
server.listen(port, host, () => {
|
|
67321
67511
|
const version = getVersion3();
|
|
67322
|
-
|
|
67512
|
+
log2(`
|
|
67323
67513
|
open-agents API server v${version}
|
|
67324
67514
|
`);
|
|
67325
|
-
|
|
67515
|
+
const proto = useTls ? "https" : "http";
|
|
67516
|
+
log2(` Listening on ${proto}://${host}:${port}
|
|
67326
67517
|
`);
|
|
67327
|
-
|
|
67518
|
+
log2(` Primary: ${config.backendUrl} (${config.backendType || "ollama"})
|
|
67328
67519
|
`);
|
|
67329
67520
|
if (process.env["OA_API_KEYS"]) {
|
|
67330
67521
|
const keyCount = process.env["OA_API_KEYS"].split(",").length;
|
|
67331
|
-
|
|
67522
|
+
log2(` Auth: ${keyCount} scoped key(s) (read/run/admin)
|
|
67332
67523
|
`);
|
|
67333
67524
|
} else if (process.env["OA_API_KEY"]) {
|
|
67334
|
-
|
|
67525
|
+
log2(` Auth: single Bearer token (admin scope)
|
|
67335
67526
|
`);
|
|
67336
67527
|
} else {
|
|
67337
|
-
|
|
67528
|
+
log2(` Auth: disabled (set OA_API_KEY or OA_API_KEYS to enable)
|
|
67338
67529
|
`);
|
|
67339
67530
|
}
|
|
67340
|
-
|
|
67531
|
+
log2(` Discovering sponsors in background...
|
|
67341
67532
|
|
|
67342
67533
|
`);
|
|
67343
67534
|
refreshEndpointRegistry().then(() => {
|
|
67344
67535
|
if (endpointRegistry.length > 1) {
|
|
67345
|
-
|
|
67536
|
+
log2(` Sponsors: ${endpointRegistry.length - 1} endpoint(s) discovered
|
|
67346
67537
|
`);
|
|
67347
67538
|
for (const ep of endpointRegistry.slice(1)) {
|
|
67348
|
-
|
|
67539
|
+
log2(` ${ep.label}: ${ep.url} (${ep.type})${ep.authKey ? " [auth]" : ""}
|
|
67349
67540
|
`);
|
|
67350
67541
|
}
|
|
67351
|
-
|
|
67542
|
+
log2(`
|
|
67352
67543
|
`);
|
|
67353
67544
|
}
|
|
67354
67545
|
}).catch(() => {
|
|
67355
67546
|
});
|
|
67356
67547
|
});
|
|
67357
67548
|
const shutdown = () => {
|
|
67358
|
-
|
|
67549
|
+
log2("\n Shutting down API server...\n");
|
|
67359
67550
|
for (const [id, child] of runningProcesses) {
|
|
67360
67551
|
if (!child.killed) {
|
|
67361
67552
|
child.kill("SIGTERM");
|
|
@@ -67363,7 +67554,7 @@ function startApiServer(options = {}) {
|
|
|
67363
67554
|
runningProcesses.delete(id);
|
|
67364
67555
|
}
|
|
67365
67556
|
server.close(() => {
|
|
67366
|
-
|
|
67557
|
+
log2(" Server stopped.\n");
|
|
67367
67558
|
process.exit(0);
|
|
67368
67559
|
});
|
|
67369
67560
|
setTimeout(() => process.exit(1), 5e3).unref();
|
|
@@ -67389,6 +67580,7 @@ var init_serve = __esm({
|
|
|
67389
67580
|
init_config();
|
|
67390
67581
|
init_audit_log();
|
|
67391
67582
|
init_web_ui();
|
|
67583
|
+
init_logger();
|
|
67392
67584
|
init_oa_directory();
|
|
67393
67585
|
init_render();
|
|
67394
67586
|
init_profiles();
|
package/package.json
CHANGED