diff --git a/Cargo.lock b/Cargo.lock index 46bfdbc..50767eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1128,7 +1128,7 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] name = "cursor-byok-desktop" -version = "0.1.0-beta.10" +version = "0.1.1" dependencies = [ "axum", "cursor-server", diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json index 62a39b4..ac90bed 100644 --- a/apps/desktop/package-lock.json +++ b/apps/desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "cursor-byok-desktop", - "version": "0.1.0-beta.10", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cursor-byok-desktop", - "version": "0.1.0-beta.10", + "version": "0.1.1", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.8.0", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 80eae06..db78f10 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "cursor-byok-desktop", - "version": "0.1.0-beta.10", + "version": "0.1.1", "description": "Cursor BYOK desktop management application", "type": "module", "scripts": { diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 6f0895f..e270b17 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cursor-byok-desktop" -version = "0.1.0-beta.10" +version = "0.1.1" edition = "2021" publish = false diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 9f1a6ca..0212a54 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Cursor BYOK", - "version": "0.1.0-beta.10", + "version": "0.1.1", "identifier": "dev.cursorbyok.desktop", "build": { "beforeDevCommand": "npm run dev", diff --git a/server/src/cursor/tools/codec/request.rs b/server/src/cursor/tools/codec/request.rs index e8e5c1f..a37127d 100644 --- a/server/src/cursor/tools/codec/request.rs +++ b/server/src/cursor/tools/codec/request.rs @@ -34,27 +34,33 @@ pub fn request(id: u32, call: &ToolCall, context: &ExecContext) -> Result Message::ShellStreamArgs(pb::ShellArgs { - command: string("command")?, - working_directory: optional_string("working_directory").unwrap_or_default(), - timeout: shell_timeout(call)?, - tool_call_id: call.call_id.clone(), - file_output_threshold_bytes: Some(40_000), - timeout_behavior: pb::TimeoutBehavior::Background as i32, - hard_timeout: Some(86_400_000), - description: optional_string("description"), - output_notification: shell_notification(call)?, - smart_mode_approval: smart_mode_approval( - call, - "request_smart_mode_approval", - "smart_mode_block_reason", - )?, - requested_sandbox_policy: shell_sandbox_policy(call), - close_stdin: true, - conversation_id: Some(context.conversation_id.clone()), - admin_command_denylist: context.admin_command_denylist.clone(), - ..Default::default() - }), + "shell" => { + let command = string("command")?; + let (simple_commands, parsing_result) = shell_command_metadata(&command); + Message::ShellStreamArgs(pb::ShellArgs { + command, + working_directory: optional_string("working_directory").unwrap_or_default(), + timeout: shell_timeout(call)?, + tool_call_id: call.call_id.clone(), + simple_commands, + parsing_result, + file_output_threshold_bytes: Some(40_000), + timeout_behavior: pb::TimeoutBehavior::Background as i32, + hard_timeout: Some(86_400_000), + description: optional_string("description"), + output_notification: shell_notification(call)?, + smart_mode_approval: smart_mode_approval( + call, + "request_smart_mode_approval", + "smart_mode_block_reason", + )?, + requested_sandbox_policy: shell_sandbox_policy(call), + close_stdin: true, + conversation_id: Some(context.conversation_id.clone()), + admin_command_denylist: context.admin_command_denylist.clone(), + ..Default::default() + }) + } "read" => Message::ReadArgs(pb::ReadArgs { path: string("path")?, tool_call_id: call.call_id.clone(), @@ -368,10 +374,7 @@ pub fn abort(id: u32) -> pb::AgentServerMessage { fn shell_sandbox_policy(call: &ToolCall) -> Option { let permissions = call.arguments.get("required_permissions")?.as_array()?; - let perms: Vec<&str> = permissions - .iter() - .filter_map(Value::as_str) - .collect(); + let perms: Vec<&str> = permissions.iter().filter_map(Value::as_str).collect(); if perms.contains(&"all") { Some(pb::SandboxPolicy { r#type: pb::sandbox_policy::Type::InsecureNone as i32, @@ -389,6 +392,33 @@ fn shell_sandbox_policy(call: &ToolCall) -> Option { } } +fn shell_command_metadata(command: &str) -> (Vec, Option) { + let command = command.trim(); + let mut parts = command.split_whitespace(); + let Some(name) = parts.next() else { + return (Vec::new(), None); + }; + let args = parts + .map( + |value| pb::shell_command_parsing_result::ExecutableCommandArg { + r#type: "word".into(), + value: value.into(), + }, + ) + .collect(); + ( + vec![command.into()], + Some(pb::ShellCommandParsingResult { + executable_commands: vec![pb::shell_command_parsing_result::ExecutableCommand { + name: name.into(), + args, + full_text: command.into(), + }], + ..Default::default() + }), + ) +} + fn shell_timeout(call: &ToolCall) -> Result { let value = call .arguments diff --git a/server/tests/tool_loop.rs b/server/tests/tool_loop.rs index e85b622..da5c19a 100644 --- a/server/tests/tool_loop.rs +++ b/server/tests/tool_loop.rs @@ -398,6 +398,21 @@ async fn shell_uses_background_timeout_and_preserves_stream_identity() { assert!(args.close_stdin); assert_eq!(args.conversation_id.as_deref(), Some("conversation")); assert_eq!(args.file_output_threshold_bytes, Some(40_000)); + assert_eq!(args.simple_commands, ["python3 -m http.server 8000"]); + let parsing = args.parsing_result.as_ref().unwrap(); + assert!(!parsing.parsing_failed); + assert_eq!(parsing.executable_commands.len(), 1); + let executable = &parsing.executable_commands[0]; + assert_eq!(executable.name, "python3"); + assert_eq!(executable.full_text, "python3 -m http.server 8000"); + assert_eq!( + executable + .args + .iter() + .map(|argument| (argument.r#type.as_str(), argument.value.as_str())) + .collect::>(), + [("word", "-m"), ("word", "http.server"), ("word", "8000")] + ); let rendered = cursor_server::cursor::interaction::render_tool_call(&shell, false).unwrap(); let Some(pb::tool_call::Tool::ShellToolCall(rendered)) = rendered.tool else {