Compare commits
15 Commits
speech-inp
...
v0.12.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6961efde0b | ||
|
|
b3e0233f4b | ||
|
|
fcebcb0174 | ||
|
|
eaab5e2e9f | ||
|
|
b12825f923 | ||
|
|
8245f474b8 | ||
|
|
3a15b311a8 | ||
|
|
6cb6c0af32 | ||
|
|
7f631611fd | ||
|
|
9d91ecc649 | ||
|
|
87afb06d34 | ||
|
|
4402d9afb0 | ||
|
|
7c3f808d69 | ||
|
|
a59e929b12 | ||
|
|
8ff4019839 |
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"7zip-bin": "^5.2.0",
|
"7zip-bin": "^5.2.0",
|
||||||
@@ -12002,7 +12002,7 @@
|
|||||||
},
|
},
|
||||||
"packages/electron-app": {
|
"packages/electron-app": {
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codenomad/ui": "file:../ui",
|
"@codenomad/ui": "file:../ui",
|
||||||
@@ -12039,7 +12039,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
@@ -12080,7 +12080,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tauri-app": {
|
"packages/tauri-app": {
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.9.4"
|
"@tauri-apps/cli": "^2.9.4"
|
||||||
@@ -12088,7 +12088,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git-diff-view/solid": "^0.0.8",
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "CodeNomad monorepo workspace",
|
"description": "CodeNomad monorepo workspace",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"minServerVersion": "0.11.4",
|
"minServerVersion": "0.12.3",
|
||||||
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"description": "CodeNomad - AI coding assistant",
|
"description": "CodeNomad - AI coding assistant",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.2.14"
|
"@opencode-ai/plugin": "1.2.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
"@fastify/reply-from": "^9.8.0",
|
"@fastify/reply-from": "^9.8.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"description": "CodeNomad Server",
|
"description": "CodeNomad Server",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
3
packages/tauri-app/Cargo.lock
generated
3
packages/tauri-app/Cargo.lock
generated
@@ -626,7 +626,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codenomad-tauri"
|
name = "codenomad-tauri"
|
||||||
version = "0.1.0"
|
version = "0.12.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
@@ -646,6 +646,7 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"url",
|
"url",
|
||||||
"which",
|
"which",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const serverDevInstallCommand =
|
|||||||
"npm install --workspace @neuralnomads/codenomad --include-workspace-root=false --install-strategy=nested --fund=false --audit=false"
|
"npm install --workspace @neuralnomads/codenomad --include-workspace-root=false --install-strategy=nested --fund=false --audit=false"
|
||||||
const uiDevInstallCommand =
|
const uiDevInstallCommand =
|
||||||
"npm install --workspace @codenomad/ui --include-workspace-root=false --install-strategy=nested --fund=false --audit=false"
|
"npm install --workspace @codenomad/ui --include-workspace-root=false --install-strategy=nested --fund=false --audit=false"
|
||||||
|
const serverPrepareUiCommand = "npm run prepare-ui --workspace @neuralnomads/codenomad"
|
||||||
|
|
||||||
const envWithRootBin = {
|
const envWithRootBin = {
|
||||||
...process.env,
|
...process.env,
|
||||||
@@ -91,6 +92,15 @@ function ensureUiBuild() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncServerUiBundle() {
|
||||||
|
console.log("[prebuild] syncing server public UI bundle...")
|
||||||
|
execSync(serverPrepareUiCommand, {
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
stdio: "inherit",
|
||||||
|
env: envWithRootBin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function ensureServerDevDependencies() {
|
function ensureServerDevDependencies() {
|
||||||
if (fs.existsSync(braceExpansionPath)) {
|
if (fs.existsSync(braceExpansionPath)) {
|
||||||
return
|
return
|
||||||
@@ -246,6 +256,7 @@ function copyUiLoadingAssets() {
|
|||||||
ensureServerDependencies()
|
ensureServerDependencies()
|
||||||
ensureServerBuild()
|
ensureServerBuild()
|
||||||
ensureUiBuild()
|
ensureUiBuild()
|
||||||
|
syncServerUiBundle()
|
||||||
copyServerArtifacts()
|
copyServerArtifacts()
|
||||||
stripNodeModuleBins()
|
stripNodeModuleBins()
|
||||||
copyUiLoadingAssets()
|
copyUiLoadingAssets()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "codenomad-tauri"
|
name = "codenomad-tauri"
|
||||||
version = "0.1.0"
|
version = "0.12.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@@ -25,3 +25,6 @@ tauri-plugin-opener = "2"
|
|||||||
url = "2"
|
url = "2"
|
||||||
tauri-plugin-keepawake = "0.1.1"
|
tauri-plugin-keepawake = "0.1.1"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
windows-sys = { version = "0.59", features = ["Win32_UI_Shell"] }
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use std::ffi::OsStr;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{BufRead, BufReader, Read, Write};
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
@@ -17,10 +19,24 @@ use std::thread;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tauri::{webview::cookie::Cookie, AppHandle, Emitter, Manager, Url};
|
use tauri::{webview::cookie::Cookie, AppHandle, Emitter, Manager, Url};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
|
|
||||||
fn log_line(message: &str) {
|
fn log_line(message: &str) {
|
||||||
println!("[tauri-cli] {message}");
|
println!("[tauri-cli] {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn configure_spawn(command: &mut Command) {
|
||||||
|
command.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn configure_spawn(_command: &mut Command) {}
|
||||||
|
|
||||||
fn workspace_root() -> Option<PathBuf> {
|
fn workspace_root() -> Option<PathBuf> {
|
||||||
std::env::current_dir().ok().and_then(|mut dir| {
|
std::env::current_dir().ok().and_then(|mut dir| {
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
@@ -36,6 +52,46 @@ const SESSION_COOKIE_NAME: &str = "codenomad_session";
|
|||||||
|
|
||||||
const CLI_STOP_GRACE_SECS: u64 = 30;
|
const CLI_STOP_GRACE_SECS: u64 = 30;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn configure_posix_process_group(command: &mut Command) {
|
||||||
|
// Ensure the CLI runs in its own process group so we can terminate wrapper
|
||||||
|
// processes (login shell/tsx) without leaving the server orphaned.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(|| {
|
||||||
|
if libc::setpgid(0, 0) != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn kill_process_tree_windows(pid: u32, force: bool) -> bool {
|
||||||
|
let mut args = vec!["/PID".to_string(), pid.to_string(), "/T".to_string()];
|
||||||
|
if force {
|
||||||
|
args.push("/F".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut command = Command::new("taskkill");
|
||||||
|
command.args(&args);
|
||||||
|
configure_spawn(&mut command);
|
||||||
|
|
||||||
|
match command.output() {
|
||||||
|
Ok(output) => {
|
||||||
|
if output.status.success() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the PID is already gone, treat it as success.
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr).to_lowercase();
|
||||||
|
let combined = format!("{stdout}\n{stderr}");
|
||||||
|
combined.contains("not found") || combined.contains("no running instance")
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
fn navigate_main(app: &AppHandle, url: &str) {
|
fn navigate_main(app: &AppHandle, url: &str) {
|
||||||
if let Some(win) = app.webview_windows().get("main") {
|
if let Some(win) = app.webview_windows().get("main") {
|
||||||
let mut display = url.to_string();
|
let mut display = url.to_string();
|
||||||
@@ -348,11 +404,19 @@ impl CliProcessManager {
|
|||||||
log_line(&format!("stopping CLI pid={}", child.id()));
|
log_line(&format!("stopping CLI pid={}", child.id()));
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::kill(child.id() as i32, libc::SIGTERM);
|
let pid = child.id() as i32;
|
||||||
|
// Prefer signaling the process group to avoid orphaning children
|
||||||
|
// when the CLI was launched via a wrapper shell.
|
||||||
|
let group_res = libc::kill(-pid, libc::SIGTERM);
|
||||||
|
if group_res != 0 {
|
||||||
|
let _ = libc::kill(pid, libc::SIGTERM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let _ = child.kill();
|
if !kill_process_tree_windows(child.id(), false) {
|
||||||
|
let _ = child.kill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -368,11 +432,17 @@ impl CliProcessManager {
|
|||||||
));
|
));
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
unsafe {
|
unsafe {
|
||||||
libc::kill(child.id() as i32, libc::SIGKILL);
|
let pid = child.id() as i32;
|
||||||
|
let group_res = libc::kill(-pid, libc::SIGKILL);
|
||||||
|
if group_res != 0 {
|
||||||
|
let _ = libc::kill(pid, libc::SIGKILL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let _ = child.kill();
|
if !kill_process_tree_windows(child.id(), true) {
|
||||||
|
let _ = child.kill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -450,9 +520,12 @@ impl CliProcessManager {
|
|||||||
.env("ELECTRON_RUN_AS_NODE", "1")
|
.env("ELECTRON_RUN_AS_NODE", "1")
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped());
|
.stderr(Stdio::piped());
|
||||||
|
configure_spawn(&mut c);
|
||||||
if let Some(ref cwd) = cwd {
|
if let Some(ref cwd) = cwd {
|
||||||
c.current_dir(cwd);
|
c.current_dir(cwd);
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
configure_posix_process_group(&mut c);
|
||||||
c.spawn()?
|
c.spawn()?
|
||||||
}
|
}
|
||||||
ShellCommandType::Direct(cmd) => {
|
ShellCommandType::Direct(cmd) => {
|
||||||
@@ -462,9 +535,12 @@ impl CliProcessManager {
|
|||||||
.env("ELECTRON_RUN_AS_NODE", "1")
|
.env("ELECTRON_RUN_AS_NODE", "1")
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped());
|
.stderr(Stdio::piped());
|
||||||
|
configure_spawn(&mut c);
|
||||||
if let Some(ref cwd) = cwd {
|
if let Some(ref cwd) = cwd {
|
||||||
c.current_dir(cwd);
|
c.current_dir(cwd);
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
configure_posix_process_group(&mut c);
|
||||||
c.spawn()?
|
c.spawn()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -537,7 +613,24 @@ impl CliProcessManager {
|
|||||||
locked.error = Some("CLI did not start in time".to_string());
|
locked.error = Some("CLI did not start in time".to_string());
|
||||||
log_line("timeout waiting for CLI readiness");
|
log_line("timeout waiting for CLI readiness");
|
||||||
if let Some(child) = child_holder_clone.lock().as_mut() {
|
if let Some(child) = child_holder_clone.lock().as_mut() {
|
||||||
let _ = child.kill();
|
#[cfg(unix)]
|
||||||
|
unsafe {
|
||||||
|
let pid = child.id() as i32;
|
||||||
|
let group_res = libc::kill(-pid, libc::SIGKILL);
|
||||||
|
if group_res != 0 {
|
||||||
|
let _ = libc::kill(pid, libc::SIGKILL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if !kill_process_tree_windows(child.id(), true) {
|
||||||
|
let _ = child.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
{
|
||||||
|
let _ = child.kill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _ = app_clone.emit("cli:error", json!({"message": "CLI did not start in time"}));
|
let _ = app_clone.emit("cli:error", json!({"message": "CLI did not start in time"}));
|
||||||
Self::emit_status(&app_clone, &locked);
|
Self::emit_status(&app_clone, &locked);
|
||||||
|
|||||||
@@ -12,8 +12,20 @@ use tauri::{AppHandle, Emitter, Manager, Runtime, Wry};
|
|||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::iter;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||||
|
|
||||||
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub manager: CliProcessManager,
|
pub manager: CliProcessManager,
|
||||||
@@ -101,6 +113,22 @@ fn emit_folder_drop_event(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn set_windows_app_user_model_id() {
|
||||||
|
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
||||||
|
.encode_wide()
|
||||||
|
.chain(iter::once(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let result = unsafe { SetCurrentProcessExplicitAppUserModelID(app_id.as_ptr()) };
|
||||||
|
if result < 0 {
|
||||||
|
eprintln!("[tauri] failed to set AppUserModelID: {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn set_windows_app_user_model_id() {}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let navigation_guard: TauriPlugin<Wry, ()> = PluginBuilder::new("external-link-guard")
|
let navigation_guard: TauriPlugin<Wry, ()> = PluginBuilder::new("external-link-guard")
|
||||||
.on_navigation(|webview, url| intercept_navigation(webview, url))
|
.on_navigation(|webview, url| intercept_navigation(webview, url))
|
||||||
@@ -116,6 +144,7 @@ fn main() {
|
|||||||
manager: CliProcessManager::new(),
|
manager: CliProcessManager::new(),
|
||||||
})
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
set_windows_app_user_model_id();
|
||||||
build_menu(&app.handle())?;
|
build_menu(&app.handle())?;
|
||||||
let dev_mode = is_dev_mode();
|
let dev_mode = is_dev_mode();
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "CodeNomad",
|
"productName": "CodeNomad",
|
||||||
"version": "0.1.0",
|
"version": "0.12.3",
|
||||||
"identifier": "ai.opencode.client",
|
"identifier": "ai.neuralnomads.codenomad.client",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev:bootstrap",
|
"beforeDevCommand": "npm run dev:bootstrap",
|
||||||
"beforeBuildCommand": "npm run bundle:server",
|
"beforeBuildCommand": "npm run bundle:server",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.12.2",
|
"version": "0.12.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getQuestionCallId, getQuestionMessageId } from "../../types/question"
|
|||||||
import type { Message, MessageInfo, ClientPart } from "../../types/message"
|
import type { Message, MessageInfo, ClientPart } from "../../types/message"
|
||||||
import type { Session } from "../../types/session"
|
import type { Session } from "../../types/session"
|
||||||
import { messageStoreBus } from "./bus"
|
import { messageStoreBus } from "./bus"
|
||||||
import type { MessageStatus, SessionRevertState } from "./types"
|
import type { MessageStatus, ReplaceMessageIdOptions, SessionRevertState } from "./types"
|
||||||
|
|
||||||
interface SessionMetadata {
|
interface SessionMetadata {
|
||||||
id: string
|
id: string
|
||||||
@@ -121,10 +121,10 @@ export function applyPartDeltaV2(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceMessageIdV2(instanceId: string, oldId: string, newId: string): void {
|
export function replaceMessageIdV2(instanceId: string, oldId: string, newId: string, options?: Omit<ReplaceMessageIdOptions, "oldId" | "newId">): void {
|
||||||
if (!oldId || !newId || oldId === newId) return
|
if (!oldId || !newId || oldId === newId) return
|
||||||
const store = messageStoreBus.getOrCreate(instanceId)
|
const store = messageStoreBus.getOrCreate(instanceId)
|
||||||
store.replaceMessageId({ oldId, newId })
|
store.replaceMessageId({ oldId, newId, ...(options ?? {}) })
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPermissionMessageId(permission: PermissionRequestLike): string | undefined {
|
function extractPermissionMessageId(permission: PermissionRequestLike): string | undefined {
|
||||||
|
|||||||
@@ -792,6 +792,8 @@ export function createInstanceMessageStore(instanceId: string, hooks?: MessageSt
|
|||||||
id: options.newId,
|
id: options.newId,
|
||||||
isEphemeral: false,
|
isEphemeral: false,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
|
partIds: options.clearParts ? [] : existing.partIds,
|
||||||
|
parts: options.clearParts ? {} : existing.parts,
|
||||||
}
|
}
|
||||||
|
|
||||||
setState("messages", options.newId, cloned)
|
setState("messages", options.newId, cloned)
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export interface PartUpdateInput {
|
|||||||
export interface ReplaceMessageIdOptions {
|
export interface ReplaceMessageIdOptions {
|
||||||
oldId: string
|
oldId: string
|
||||||
newId: string
|
newId: string
|
||||||
|
clearParts?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrollCacheKey {
|
export interface ScrollCacheKey {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ async function sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messageId = createId("msg")
|
const messageId = createId("msg")
|
||||||
const textPartId = createId("part")
|
const textPartId = createId("prt")
|
||||||
|
|
||||||
const resolvedPrompt = resolvePastedPlaceholders(prompt, attachments)
|
const resolvedPrompt = resolvePastedPlaceholders(prompt, attachments)
|
||||||
|
|
||||||
@@ -110,7 +110,6 @@ async function sendMessage(
|
|||||||
|
|
||||||
const requestParts: any[] = [
|
const requestParts: any[] = [
|
||||||
{
|
{
|
||||||
id: textPartId,
|
|
||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: resolvedPrompt,
|
text: resolvedPrompt,
|
||||||
},
|
},
|
||||||
@@ -120,9 +119,8 @@ async function sendMessage(
|
|||||||
for (const att of attachments) {
|
for (const att of attachments) {
|
||||||
const source = att.source
|
const source = att.source
|
||||||
if (source.type === "file") {
|
if (source.type === "file") {
|
||||||
const partId = createId("part")
|
const partId = createId("prt")
|
||||||
requestParts.push({
|
requestParts.push({
|
||||||
id: partId,
|
|
||||||
type: "file" as const,
|
type: "file" as const,
|
||||||
url: att.url,
|
url: att.url,
|
||||||
mime: source.mime,
|
mime: source.mime,
|
||||||
@@ -148,9 +146,8 @@ async function sendMessage(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const partId = createId("part")
|
const partId = createId("prt")
|
||||||
requestParts.push({
|
requestParts.push({
|
||||||
id: partId,
|
|
||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: value,
|
text: value,
|
||||||
})
|
})
|
||||||
@@ -184,7 +181,6 @@ async function sendMessage(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
messageID: messageId,
|
|
||||||
parts: requestParts,
|
parts: requestParts,
|
||||||
...(session.agent && { agent: session.agent }),
|
...(session.agent && { agent: session.agent }),
|
||||||
...(session.model.providerId &&
|
...(session.model.providerId &&
|
||||||
|
|||||||
@@ -240,19 +240,22 @@ function resolveMessageRole(info?: MessageInfo | null): MessageRole {
|
|||||||
return info?.role === "user" ? "user" : "assistant"
|
return info?.role === "user" ? "user" : "assistant"
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPendingMessageId(
|
function findPendingSyntheticMessageId(
|
||||||
store: InstanceMessageStore,
|
store: InstanceMessageStore,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
role: MessageRole,
|
role: MessageRole,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const messageIds = store.getSessionMessageIds(sessionId)
|
const messageIds = store.getSessionMessageIds(sessionId)
|
||||||
const lastId = messageIds[messageIds.length - 1]
|
for (const messageId of messageIds) {
|
||||||
if (!lastId) return undefined
|
const record = store.getMessage(messageId)
|
||||||
const record = store.getMessage(lastId)
|
if (!record) continue
|
||||||
if (!record) return undefined
|
if (record.sessionId !== sessionId) continue
|
||||||
if (record.sessionId !== sessionId) return undefined
|
if (record.role !== role) continue
|
||||||
if (record.role !== role) return undefined
|
if (record.status !== "sending") continue
|
||||||
return record.status === "sending" ? record.id : undefined
|
if (!record.isEphemeral) continue
|
||||||
|
return record.id
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | MessagePartUpdatedEvent): void {
|
function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | MessagePartUpdatedEvent): void {
|
||||||
@@ -282,9 +285,9 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
|||||||
|
|
||||||
let record = store.getMessage(messageId)
|
let record = store.getMessage(messageId)
|
||||||
if (!record) {
|
if (!record) {
|
||||||
const pendingId = findPendingMessageId(store, sessionId, role)
|
const pendingId = findPendingSyntheticMessageId(store, sessionId, role)
|
||||||
if (pendingId && pendingId !== messageId) {
|
if (pendingId && pendingId !== messageId) {
|
||||||
replaceMessageIdV2(instanceId, pendingId, messageId)
|
replaceMessageIdV2(instanceId, pendingId, messageId, { clearParts: role === "user" })
|
||||||
record = store.getMessage(messageId)
|
record = store.getMessage(messageId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,9 +348,9 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
|||||||
|
|
||||||
let record = store.getMessage(messageId)
|
let record = store.getMessage(messageId)
|
||||||
if (!record) {
|
if (!record) {
|
||||||
const pendingId = findPendingMessageId(store, sessionId, role)
|
const pendingId = findPendingSyntheticMessageId(store, sessionId, role)
|
||||||
if (pendingId && pendingId !== messageId) {
|
if (pendingId && pendingId !== messageId) {
|
||||||
replaceMessageIdV2(instanceId, pendingId, messageId)
|
replaceMessageIdV2(instanceId, pendingId, messageId, { clearParts: role === "user" })
|
||||||
record = store.getMessage(messageId)
|
record = store.getMessage(messageId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user