Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/cortex-cli/src/exec_cmd/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,32 @@ impl ExecCli {
eprintln!("\x1b[1;33m[WARN]\x1b[0m {}", w.message);
}
}
_ => {}
EventMsg::StreamError(e) => {
error_occurred = true;
error_message = Some(e.message.clone());
if is_text {
eprintln!("\x1b[1;31m[STREAM ERROR]\x1b[0m {}", e.message);
}
break;
}
EventMsg::ApplyPatchApprovalRequest(p) => {
if is_text && self.verbose {
eprintln!("\x1b[1;33m[PATCH]\x1b[0m Patch approval requested: {}", p.id);
}
}
EventMsg::ElicitationRequest(e) => {
if is_text && self.verbose {
eprintln!(
"\x1b[1;33m[ELICITATION]\x1b[0m Elicitation requested: {}",
e.request_id
);
}
}
other => {
if self.verbose {
eprintln!("Unhandled event: {:?}", other);
}
}
}
}

Expand Down
29 changes: 27 additions & 2 deletions src/cortex-engine/src/context/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ impl Conversation {
self.updated_at = std::time::Instant::now();
}

/// Recompute turn count.
pub fn recompute_turns(&mut self) {
self.turn_count = self
.messages
.iter()
.filter(|m| m.role == MessageRole::User)
.count() as u32;
}

/// Get the last message.
pub fn last_message(&self) -> Option<&Message> {
self.messages.last()
Expand Down Expand Up @@ -160,6 +169,9 @@ impl Conversation {
if index < self.messages.len() {
let msg = self.messages.remove(index);
self.token_count = self.token_count.saturating_sub(estimate_tokens(&msg));
if msg.role == MessageRole::User {
self.turn_count = self.turn_count.saturating_sub(1);
}
Some(msg)
} else {
None
Expand All @@ -171,6 +183,10 @@ impl Conversation {
let tokens = estimate_tokens(&message);
self.token_count += tokens;

if message.role == MessageRole::User {
self.turn_count += 1;
}

let index = index.min(self.messages.len());
self.messages.insert(index, message);
self.updated_at = std::time::Instant::now();
Expand All @@ -179,9 +195,14 @@ impl Conversation {
/// Truncate to N messages.
pub fn truncate(&mut self, n: usize) {
if n < self.messages.len() {
let removed: u32 = self.messages[n..].iter().map(estimate_tokens).sum();
let removed_tokens: u32 = self.messages[n..].iter().map(estimate_tokens).sum();
let removed_turns: u32 = self.messages[n..]
.iter()
.filter(|m| m.role == MessageRole::User)
.count() as u32;
self.messages.truncate(n);
self.token_count = self.token_count.saturating_sub(removed);
self.token_count = self.token_count.saturating_sub(removed_tokens);
self.turn_count = self.turn_count.saturating_sub(removed_turns);
self.updated_at = std::time::Instant::now();
}
}
Expand All @@ -191,6 +212,9 @@ impl Conversation {
while self.token_count > max_tokens && !self.messages.is_empty() {
if let Some(msg) = self.messages.first() {
let tokens = estimate_tokens(msg);
if msg.role == MessageRole::User {
self.turn_count = self.turn_count.saturating_sub(1);
}
self.messages.remove(0);
self.token_count = self.token_count.saturating_sub(tokens);
}
Expand Down Expand Up @@ -436,3 +460,4 @@ mod tests {
assert_eq!(fork.len(), 2);
}
}

2 changes: 1 addition & 1 deletion src/cortex-engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl ContextManager {
if self.config.auto_compact {
let usage = self.token_budget.current_usage();
if usage > self.config.compaction_threshold {
self.compact().await?;
self.compaction.compact(&mut conv)?;
}
}

Expand Down
32 changes: 25 additions & 7 deletions src/cortex-engine/src/permission/skill_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,33 @@ impl SkillPermissionChecker {
///
/// This checks the permission to load and execute a skill.
pub async fn can_load_skill(&self, skill_name: &str) -> Result<bool> {
let response = self
.permission_manager
.check_skill_permission(skill_name)
.await;
let response = self.permission_manager.check_skill_permission(skill_name).await;

// If parent inheritance is enabled and current manager doesn't allow,
// check parent
let final_response = if response == PermissionResponse::Ask
&& self.inherit_parent_permissions
&& self.parent_manager.is_some()
{
if let Some(ref parent) = self.parent_manager {
let parent_response = parent.check_skill_permission(skill_name).await;
if parent_response == PermissionResponse::Allow {
parent_response
} else {
response
}
} else {
response
}
} else {
response
};

let decision = SkillPermissionDecision::new(
skill_name,
None,
response,
match response {
final_response,
match final_response {
PermissionResponse::Allow => "Skill loading allowed",
PermissionResponse::Deny => "Skill loading denied",
PermissionResponse::Ask => "Skill loading requires user confirmation",
Expand All @@ -194,7 +211,7 @@ impl SkillPermissionChecker {

self.log_decision(decision).await;

match response {
match final_response {
PermissionResponse::Allow => {
info!(skill = skill_name, "Skill loading permitted");
Ok(true)
Expand Down Expand Up @@ -535,3 +552,4 @@ mod tests {
assert!(!decisions.is_empty());
}
}

40 changes: 39 additions & 1 deletion src/cortex-engine/src/session/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,45 @@ impl Session {
Op::DisableMcpServer { name } => {
info!("Disabling MCP server: {}...", name);
}
_ => {}
Op::PatchApproval { id, decision } => {
info!("Handling patch approval: {} - {:?}", id, decision);
// TODO: Forward to patch manager
}
Op::ResolveElicitation {
server_name,
request_id,
decision,
} => {
info!(
"Resolving elicitation: {}/{} - {:?}",
server_name, request_id, decision
);
// TODO: Forward to elicitation manager
}
Op::ListMcpTools => {
info!("Listing MCP tools...");
}
Op::ListCustomPrompts => {
info!("Listing custom prompts...");
}
Op::GetSessionTimeline => {
info!("Fetching session timeline...");
}
Op::Review { review_request } => {
info!("Requesting code review: {:?}", review_request);
}
Op::RunUserShellCommand { command } => {
info!("Running user shell command: {}", command);
}
Op::AddToHistory { text } => {
info!("Adding to history: {}", text);
}
Op::GetHistoryEntryRequest { offset, log_id } => {
info!("Fetching history entry: {}/{}", offset, log_id);
}
other => {
tracing::warn!("Unhandled operation: {:?}", other);
}
}
Ok(())
}
Expand Down
8 changes: 6 additions & 2 deletions src/cortex-lsp/src/downloader/servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ pub fn gopls() -> DownloadableServer {
name: "gopls".to_string(),
github_repo: "golang/tools".to_string(),
binary_pattern: "gopls{ext}".to_string(),
asset_pattern: "gopls.{os}-{arch}*".to_string(),
asset_pattern: "gopls-{os}-{arch}*".to_string(),
is_archive: false,
archive_binary_path: None,
install_method: None,
install_method: Some(InstallMethod::CustomUrl {
url_pattern: "https://proxy.golang.org/golang.org/x/tools/gopls/@v/{version}.zip".to_string(),
is_archive: true,
archive_binary_path: Some("gopls".to_string()),
}),
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/cortex-network-proxy/src/ip_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ pub async fn host_resolves_to_non_public(host: &str, _port: u16) -> bool {
false
}
Err(_) => {
// If we can't resolve, be safe and allow (the connection will fail anyway)
false
// If we can't resolve, be safe and block (the connection would likely fail anyway,
// but fail-closed is better for security)
true
}
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/cortex-tui-capture/src/exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,9 @@ impl MarkdownExporter {
output.push_str("<details>\n<summary>View Frame</summary>\n\n");
}

output.push_str("```\n");
output.push_str("````\n");
output.push_str(&frame.ascii_content);
output.push_str("\n```\n\n");
output.push_str("\n````\n\n");

if self.collapse_frames {
output.push_str("</details>\n\n");
Expand Down Expand Up @@ -467,9 +467,9 @@ impl MarkdownExporter {
let _ = writeln!(output, "## Frame {}\n", frame.frame_number);
}

output.push_str("```\n");
output.push_str("````\n");
output.push_str(&frame.ascii_content);
output.push_str("\n```\n\n");
output.push_str("\n````\n\n");
}

output
Expand Down Expand Up @@ -604,3 +604,4 @@ mod tests {
);
}
}