Compare commits
7 Commits
v0.13.3-de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43db9cdb50 | ||
|
|
03ed3d3b2c | ||
|
|
a111de1af8 | ||
|
|
8a3b162be9 | ||
|
|
c62cb3ce4a | ||
|
|
d9811e735d | ||
|
|
1ce58b9dd9 |
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"7zip-bin": "^5.2.0",
|
||||
@@ -12068,7 +12068,7 @@
|
||||
},
|
||||
"packages/electron-app": {
|
||||
"name": "@neuralnomads/codenomad-electron-app",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codenomad/ui": "file:../ui",
|
||||
@@ -12105,7 +12105,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.5.0",
|
||||
@@ -12147,7 +12147,7 @@
|
||||
},
|
||||
"packages/tauri-app": {
|
||||
"name": "@codenomad/tauri-app",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.9.4"
|
||||
@@ -12155,7 +12155,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@codenomad/ui",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@git-diff-view/solid": "^0.0.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"private": true,
|
||||
"description": "CodeNomad monorepo workspace",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"minServerVersion": "0.13.3",
|
||||
"minServerVersion": "0.14.0",
|
||||
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad-electron-app",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"description": "CodeNomad - AI coding assistant",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
|
||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.5.0",
|
||||
"@fastify/reply-from": "^9.8.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"description": "CodeNomad Server",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
|
||||
@@ -25,6 +25,9 @@ import { resolveNetworkAddresses, resolveRemoteAddresses } from "./server/networ
|
||||
import { startDevReleaseMonitor } from "./releases/dev-release-monitor"
|
||||
import { SpeechService } from "./speech/service"
|
||||
import { SideCarManager } from "./sidecars/manager"
|
||||
import { ClientConnectionManager } from "./clients/connection-manager"
|
||||
import { PluginChannelManager } from "./plugins/channel"
|
||||
import { VoiceModeManager } from "./plugins/voice-mode"
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
@@ -378,6 +381,14 @@ async function main() {
|
||||
|
||||
const remoteAccessEnabled = options.host === "0.0.0.0" || !isLoopbackHost(options.host)
|
||||
|
||||
const clientConnectionManager = new ClientConnectionManager(logger.child({ component: "client-connections" }))
|
||||
const pluginChannel = new PluginChannelManager(logger.child({ component: "plugin-channel" }))
|
||||
const voiceModeManager = new VoiceModeManager({
|
||||
connections: clientConnectionManager,
|
||||
channel: pluginChannel,
|
||||
logger: logger.child({ component: "voice-mode" }),
|
||||
})
|
||||
|
||||
const httpsPortExplicit = programHasArg(process.argv.slice(2), "--https-port") || Boolean(process.env.CLI_HTTPS_PORT)
|
||||
const httpPortExplicit = programHasArg(process.argv.slice(2), "--http-port") || Boolean(process.env.CLI_HTTP_PORT)
|
||||
|
||||
@@ -408,6 +419,9 @@ async function main() {
|
||||
speechService,
|
||||
sidecarManager,
|
||||
authManager,
|
||||
clientConnectionManager,
|
||||
pluginChannel,
|
||||
voiceModeManager,
|
||||
uiStaticDir: uiResolution.uiStaticDir ?? DEFAULT_UI_STATIC_DIR,
|
||||
uiDevServerUrl: uiResolution.uiDevServerUrl,
|
||||
logger,
|
||||
@@ -430,6 +444,9 @@ async function main() {
|
||||
speechService,
|
||||
sidecarManager,
|
||||
authManager,
|
||||
clientConnectionManager,
|
||||
pluginChannel,
|
||||
voiceModeManager,
|
||||
uiStaticDir: uiResolution.uiStaticDir ?? DEFAULT_UI_STATIC_DIR,
|
||||
uiDevServerUrl: undefined,
|
||||
logger,
|
||||
@@ -534,6 +551,12 @@ async function main() {
|
||||
logger.error({ err: error }, "SideCar manager shutdown failed")
|
||||
}
|
||||
|
||||
try {
|
||||
clientConnectionManager.shutdown()
|
||||
} catch (error) {
|
||||
logger.warn({ err: error }, "Client connection manager shutdown failed")
|
||||
}
|
||||
|
||||
try {
|
||||
await workspaceManager.shutdown()
|
||||
logger.info("Workspace manager shutdown complete")
|
||||
|
||||
@@ -19,13 +19,13 @@ export class VoiceModeManager {
|
||||
})
|
||||
}
|
||||
|
||||
setEnabled(instanceId: string, connection: ClientConnectionRef, enabled: boolean): void {
|
||||
setEnabled(instanceId: string, connection: ClientConnectionRef, enabled: boolean): boolean {
|
||||
if (enabled && !this.options.connections.isConnected(connection)) {
|
||||
this.options.logger.debug(
|
||||
{ instanceId, clientId: connection.clientId, connectionId: connection.connectionId },
|
||||
"Ignoring voice mode enable for disconnected client connection",
|
||||
)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
const key = getConnectionKey(connection)
|
||||
@@ -44,6 +44,7 @@ export class VoiceModeManager {
|
||||
|
||||
this.options.logger.debug({ instanceId, clientId: connection.clientId, connectionId: connection.connectionId, enabled }, "Voice mode updated for client connection")
|
||||
this.publishIfChanged(instanceId)
|
||||
return true
|
||||
}
|
||||
|
||||
syncInstance(instanceId: string): void {
|
||||
@@ -76,7 +77,10 @@ export class VoiceModeManager {
|
||||
this.aggregateByInstance.delete(instanceId)
|
||||
}
|
||||
|
||||
this.options.logger.debug({ instanceId, enabled }, "Broadcasting aggregate voice mode")
|
||||
this.options.logger.debug(
|
||||
{ instanceId, enabled },
|
||||
"Broadcasting aggregate voice mode",
|
||||
)
|
||||
this.options.channel.send(instanceId, buildVoiceModeEvent(enabled))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ interface HttpServerDeps {
|
||||
speechService: SpeechService
|
||||
sidecarManager: SideCarManager
|
||||
authManager: AuthManager
|
||||
clientConnectionManager: ClientConnectionManager
|
||||
pluginChannel: PluginChannelManager
|
||||
voiceModeManager: VoiceModeManager
|
||||
uiStaticDir: string
|
||||
uiDevServerUrl?: string
|
||||
logger: Logger
|
||||
@@ -182,13 +185,6 @@ export function createHttpServer(deps: HttpServerDeps) {
|
||||
eventBus: deps.eventBus,
|
||||
logger: deps.logger.child({ component: "background-processes" }),
|
||||
})
|
||||
const clientConnectionManager = new ClientConnectionManager(deps.logger.child({ component: "client-connections" }))
|
||||
const pluginChannel = new PluginChannelManager(deps.logger.child({ component: "plugin-channel" }))
|
||||
const voiceModeManager = new VoiceModeManager({
|
||||
connections: clientConnectionManager,
|
||||
channel: pluginChannel,
|
||||
logger: deps.logger.child({ component: "voice-mode" }),
|
||||
})
|
||||
|
||||
registerAuthRoutes(app, { authManager: deps.authManager })
|
||||
|
||||
@@ -268,7 +264,7 @@ export function createHttpServer(deps: HttpServerDeps) {
|
||||
eventBus: deps.eventBus,
|
||||
registerClient: registerSseClient,
|
||||
logger: sseLogger,
|
||||
connectionManager: clientConnectionManager,
|
||||
connectionManager: deps.clientConnectionManager,
|
||||
})
|
||||
registerWorktreeRoutes(app, { workspaceManager: deps.workspaceManager })
|
||||
registerStorageRoutes(app, {
|
||||
@@ -289,8 +285,8 @@ export function createHttpServer(deps: HttpServerDeps) {
|
||||
workspaceManager: deps.workspaceManager,
|
||||
eventBus: deps.eventBus,
|
||||
logger: proxyLogger,
|
||||
channel: pluginChannel,
|
||||
voiceModeManager,
|
||||
channel: deps.pluginChannel,
|
||||
voiceModeManager: deps.voiceModeManager,
|
||||
})
|
||||
registerBackgroundProcessRoutes(app, { backgroundProcessManager })
|
||||
registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger })
|
||||
@@ -356,7 +352,6 @@ export function createHttpServer(deps: HttpServerDeps) {
|
||||
},
|
||||
stop: () => {
|
||||
closeSseClients()
|
||||
clientConnectionManager.shutdown()
|
||||
return app.close()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -66,11 +66,17 @@ export function registerPluginRoutes(app: FastifyInstance, deps: RouteDeps) {
|
||||
}
|
||||
|
||||
const payload = VoiceModeStateSchema.parse(request.body ?? {})
|
||||
deps.voiceModeManager.setEnabled(
|
||||
const applied = deps.voiceModeManager.setEnabled(
|
||||
request.params.id,
|
||||
{ clientId: payload.clientId, connectionId: payload.connectionId },
|
||||
payload.enabled,
|
||||
)
|
||||
|
||||
if (payload.enabled && !applied) {
|
||||
reply.code(409).send({ error: "Client connection not active for voice mode enable" })
|
||||
return
|
||||
}
|
||||
|
||||
return { enabled: payload.enabled }
|
||||
})
|
||||
|
||||
|
||||
2
packages/tauri-app/Cargo.lock
generated
2
packages/tauri-app/Cargo.lock
generated
@@ -458,7 +458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codenomad-tauri"
|
||||
version = "0.13.3"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs 5.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@codenomad/tauri-app",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "codenomad-tauri"
|
||||
version = "0.13.3"
|
||||
version = "0.14.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@@ -28,4 +28,4 @@ url = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59", features = ["Win32_UI_Shell"] }
|
||||
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_Security", "Win32_System_JobObjects"] }
|
||||
|
||||
@@ -5,9 +5,13 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::VecDeque;
|
||||
use std::env;
|
||||
#[cfg(windows)]
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
#[cfg(windows)]
|
||||
use std::mem::{size_of, zeroed};
|
||||
use std::net::TcpStream;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
@@ -19,12 +23,95 @@ use std::thread;
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tauri::{webview::cookie::Cookie, AppHandle, Emitter, Manager, Url};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::System::JobObjects::{
|
||||
AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
|
||||
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
|
||||
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[derive(Debug)]
|
||||
struct WindowsJobObject {
|
||||
// The desktop wrapper may observe only a short-lived Node wrapper PID while the real
|
||||
// server and workspace descendants continue running below it. KILL_ON_JOB_CLOSE gives
|
||||
// Tauri an OS-owned handle for the whole subtree instead of relying on a single PID.
|
||||
handle: HANDLE,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl WindowsJobObject {
|
||||
fn create() -> anyhow::Result<Self> {
|
||||
let handle = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) };
|
||||
if handle.is_null() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"CreateJobObjectW failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
));
|
||||
}
|
||||
|
||||
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
|
||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
|
||||
let ok = unsafe {
|
||||
SetInformationJobObject(
|
||||
handle,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut info as *mut _ as *mut c_void,
|
||||
size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
|
||||
)
|
||||
};
|
||||
if ok == 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
unsafe {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
return Err(anyhow::anyhow!("SetInformationJobObject failed: {}", err));
|
||||
}
|
||||
|
||||
Ok(Self { handle })
|
||||
}
|
||||
|
||||
fn assign_child(&self, child: &Child) -> anyhow::Result<()> {
|
||||
let process_handle = child.as_raw_handle() as HANDLE;
|
||||
let ok = unsafe { AssignProcessToJobObject(self.handle, process_handle) };
|
||||
if ok == 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"AssignProcessToJobObject failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for WindowsJobObject {
|
||||
fn drop(&mut self) {
|
||||
if !self.handle.is_null() {
|
||||
unsafe {
|
||||
CloseHandle(self.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl Send for WindowsJobObject {}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl Sync for WindowsJobObject {}
|
||||
|
||||
fn log_line(message: &str) {
|
||||
println!("[tauri-cli] {message}");
|
||||
}
|
||||
@@ -363,6 +450,8 @@ impl Default for CliStatus {
|
||||
pub struct CliProcessManager {
|
||||
status: Arc<Mutex<CliStatus>>,
|
||||
child: Arc<Mutex<Option<Child>>>,
|
||||
#[cfg(windows)]
|
||||
job: Arc<Mutex<Option<WindowsJobObject>>>,
|
||||
ready: Arc<AtomicBool>,
|
||||
bootstrap_token: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
@@ -372,6 +461,8 @@ impl CliProcessManager {
|
||||
Self {
|
||||
status: Arc::new(Mutex::new(CliStatus::default())),
|
||||
child: Arc::new(Mutex::new(None)),
|
||||
#[cfg(windows)]
|
||||
job: Arc::new(Mutex::new(None)),
|
||||
ready: Arc::new(AtomicBool::new(false)),
|
||||
bootstrap_token: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
@@ -394,6 +485,8 @@ impl CliProcessManager {
|
||||
|
||||
let status_arc = self.status.clone();
|
||||
let child_arc = self.child.clone();
|
||||
#[cfg(windows)]
|
||||
let job_arc = self.job.clone();
|
||||
let ready_flag = self.ready.clone();
|
||||
let token_arc = self.bootstrap_token.clone();
|
||||
thread::spawn(move || {
|
||||
@@ -401,6 +494,8 @@ impl CliProcessManager {
|
||||
app.clone(),
|
||||
status_arc.clone(),
|
||||
child_arc,
|
||||
#[cfg(windows)]
|
||||
job_arc,
|
||||
ready_flag,
|
||||
token_arc,
|
||||
dev,
|
||||
@@ -420,11 +515,12 @@ impl CliProcessManager {
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> anyhow::Result<()> {
|
||||
#[cfg(windows)]
|
||||
let _job = self.job.lock().take();
|
||||
|
||||
let mut child_opt = self.child.lock();
|
||||
if let Some(mut child) = child_opt.take() {
|
||||
log_line(&format!("stopping CLI pid={}", child.id()));
|
||||
#[cfg(windows)]
|
||||
let mut forced_tree_shutdown = false;
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
let pid = child.id() as i32;
|
||||
@@ -446,18 +542,16 @@ impl CliProcessManager {
|
||||
Ok(Some(_)) => break,
|
||||
Ok(None) => {
|
||||
#[cfg(windows)]
|
||||
if !forced_tree_shutdown
|
||||
&& start.elapsed() > Duration::from_millis(CLI_WINDOWS_FORCE_GRACE_MS)
|
||||
{
|
||||
if start.elapsed() > Duration::from_millis(CLI_WINDOWS_FORCE_GRACE_MS) {
|
||||
log_line(&format!(
|
||||
"regular Windows shutdown still running after {}ms; escalating pid={}",
|
||||
CLI_WINDOWS_FORCE_GRACE_MS,
|
||||
child.id()
|
||||
));
|
||||
forced_tree_shutdown = true;
|
||||
if !kill_process_tree_windows(child.id(), true) {
|
||||
let _ = child.kill();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if start.elapsed() > Duration::from_secs(CLI_STOP_GRACE_SECS) {
|
||||
@@ -476,11 +570,7 @@ impl CliProcessManager {
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if !forced_tree_shutdown
|
||||
&& !kill_process_tree_windows(child.id(), true)
|
||||
{
|
||||
let _ = child.kill();
|
||||
} else if forced_tree_shutdown {
|
||||
if !kill_process_tree_windows(child.id(), true) {
|
||||
let _ = child.kill();
|
||||
}
|
||||
}
|
||||
@@ -491,6 +581,9 @@ impl CliProcessManager {
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#[cfg(windows)]
|
||||
log_line("tracked CLI process already exited; dropping Windows job object to reap descendants");
|
||||
}
|
||||
|
||||
let mut status = self.status.lock();
|
||||
@@ -511,6 +604,7 @@ impl CliProcessManager {
|
||||
app: AppHandle,
|
||||
status: Arc<Mutex<CliStatus>>,
|
||||
child_holder: Arc<Mutex<Option<Child>>>,
|
||||
#[cfg(windows)] job_holder: Arc<Mutex<Option<WindowsJobObject>>>,
|
||||
ready: Arc<AtomicBool>,
|
||||
bootstrap_token: Arc<Mutex<Option<String>>>,
|
||||
dev: bool,
|
||||
@@ -592,6 +686,22 @@ impl CliProcessManager {
|
||||
|
||||
let pid = child.id();
|
||||
log_line(&format!("spawned pid={pid}"));
|
||||
#[cfg(windows)]
|
||||
match WindowsJobObject::create().and_then(|job| {
|
||||
job.assign_child(&child)?;
|
||||
Ok(job)
|
||||
}) {
|
||||
Ok(job) => {
|
||||
log_line(&format!("attached pid={pid} to Windows job object"));
|
||||
*job_holder.lock() = Some(job);
|
||||
}
|
||||
Err(err) => {
|
||||
log_line(&format!(
|
||||
"failed to attach pid={pid} to Windows job object; falling back to taskkill-only cleanup: {err}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut locked = status.lock();
|
||||
locked.pid = Some(pid);
|
||||
@@ -665,6 +775,8 @@ impl CliProcessManager {
|
||||
let status_clone = status.clone();
|
||||
let ready_clone = ready.clone();
|
||||
let child_holder_clone = child_holder.clone();
|
||||
#[cfg(windows)]
|
||||
let job_holder_clone = job_holder.clone();
|
||||
thread::spawn(move || {
|
||||
let timeout = Duration::from_secs(60);
|
||||
thread::sleep(timeout);
|
||||
@@ -719,6 +831,10 @@ impl CliProcessManager {
|
||||
// Drop the handle after the process exits so other callers
|
||||
// don't attempt to stop/kill a finished process.
|
||||
*guard = None;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = job_holder_clone.lock().take();
|
||||
}
|
||||
Some(status)
|
||||
}
|
||||
None => None,
|
||||
@@ -776,7 +892,8 @@ impl CliProcessManager {
|
||||
auth_cookie_name: &str,
|
||||
) {
|
||||
let mut buffer = String::new();
|
||||
let local_url_regex = Regex::new(r"^Local\s+Connection\s+URL\s*:\s*(https?://\S+)\s*$").ok();
|
||||
let local_url_regex =
|
||||
Regex::new(r"^Local\s+Connection\s+URL\s*:\s*(https?://\S+)\s*$").ok();
|
||||
let token_prefix = "CODENOMAD_BOOTSTRAP_TOKEN:";
|
||||
|
||||
loop {
|
||||
@@ -818,7 +935,6 @@ impl CliProcessManager {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
@@ -1022,15 +1138,23 @@ fn resolve_tsx(_app: &AppHandle) -> Option<String> {
|
||||
let cwd = std::env::current_dir().ok();
|
||||
let workspace = workspace_root();
|
||||
let mut candidates = vec![
|
||||
cwd.as_ref().map(|p| p.join("node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref().map(|p| p.join("node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref().map(|p| p.join("node_modules/tsx/dist/cli.js")),
|
||||
cwd.as_ref().map(|p| p.join("../node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref().map(|p| p.join("../node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref().map(|p| p.join("../node_modules/tsx/dist/cli.js")),
|
||||
cwd.as_ref().map(|p| p.join("../../node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref().map(|p| p.join("../../node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref().map(|p| p.join("../../node_modules/tsx/dist/cli.js")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../node_modules/tsx/dist/cli.js")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../../node_modules/tsx/dist/cli.mjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../../node_modules/tsx/dist/cli.cjs")),
|
||||
cwd.as_ref()
|
||||
.map(|p| p.join("../../node_modules/tsx/dist/cli.js")),
|
||||
workspace
|
||||
.as_ref()
|
||||
.map(|p| p.join("node_modules/tsx/dist/cli.mjs")),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "CodeNomad",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"identifier": "ai.neuralnomads.codenomad.client",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev:bootstrap",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@codenomad/ui",
|
||||
"version": "0.13.3",
|
||||
"version": "0.14.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
Reference in New Issue
Block a user