Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
bcc7177
fix: pin reedline to 0.47.0 (#3398)
amitksingh1490 May 28, 2026
9e16491
chore(deps): update rust crate reedline to v0.48.0 (#3406)
renovate[bot] May 28, 2026
fd9b64c
Revert "chore(deps): update rust crate reedline to v0.48.0" (#3409)
amitksingh1490 May 28, 2026
337f2fa
fix(openai_responses): handle codex response completed/incomplete eve…
amitksingh1490 May 28, 2026
e037aa7
chore(deps): update rust crate posthog-rs to v0.7.2 (#3410)
renovate[bot] May 28, 2026
eda1e35
chore(deps): update dependency @ai-sdk/google-vertex to v4.0.140 (#3412)
renovate[bot] May 28, 2026
5bbd1b5
chore(deps): update dependency ai to v6.0.192 (#3413)
renovate[bot] May 29, 2026
58edf67
fix(provider): add model entries to provider.json and vertex.json (#3…
amitksingh1490 May 29, 2026
c2f0916
chore(deps): update rust crate posthog-rs to v0.7.3 (#3415)
renovate[bot] May 29, 2026
118a582
chore(deps): update rust crate aws-sdk-bedrockruntime to v1.132.0 (#3…
renovate[bot] May 29, 2026
b3454cb
fix: apply Opus 4.7 API contract to Claude Opus 4.8 (#3418)
amitksingh1490 May 29, 2026
22edd31
refactor(editor): replace reedline with rustyline completer (#3399)
amitksingh1490 May 29, 2026
c2437e9
fix(minimax): MiniMax M3 model support (#3434)
ImBIOS Jun 2, 2026
7e5aa7f
fix(gemini): strip propertyNames from tool schemas (#3426)
pascalwhoop Jun 2, 2026
29c7596
fix(http): map invalid response status to openai error (#3439)
amitksingh1490 Jun 3, 2026
709850e
chore(deps): update aws-sdk-rust monorepo (#3420)
renovate[bot] Jun 5, 2026
f82b86a
chore(deps): update dependency @ai-sdk/google-vertex to v4.0.142 (#3440)
renovate[bot] Jun 5, 2026
7ccf600
chore(deps): update dependency ai to v6.0.197 (#3444)
renovate[bot] Jun 5, 2026
f996b47
chore(deps): update dependency tsx to v4.22.4 (#3427)
renovate[bot] Jun 5, 2026
c804101
chore(deps): update rust crate chrono to v0.4.45 (#3445)
renovate[bot] Jun 5, 2026
8d00f8a
chore(deps): update rust crate diesel to v2.3.10 (#3446)
renovate[bot] Jun 5, 2026
317656d
chore(deps): update rust crate unicode-segmentation to v1.13.3 (#3431)
renovate[bot] Jun 5, 2026
525aa6f
chore(deps): update rust crate uuid to v1.23.2 (#3421)
renovate[bot] Jun 5, 2026
36cad65
chore(deps): update dependency @types/node to v24.13.0 (#3447)
renovate[bot] Jun 5, 2026
ccdf57a
chore(deps): update rust to 1.96 (#3419)
renovate[bot] Jun 5, 2026
aba495b
chore(deps): update rust crate ignore to v0.4.26 (#3453)
renovate[bot] Jun 5, 2026
c2c8ea5
chore(deps): update rust crate google-cloud-auth to v1.12.0 (#3449)
renovate[bot] Jun 5, 2026
fbd16e7
chore(deps): update rust crate serial_test to v3.5.0 (#3423)
renovate[bot] Jun 5, 2026
6b65b2a
fix(deps): update rust crate posthog-rs to 0.9.0 (#3451)
renovate[bot] Jun 5, 2026
bdeb1bc
fix(deps): update rust crate posthog-rs to 0.10.0 (#3455)
renovate[bot] Jun 5, 2026
38eca6a
fix(provider): Ambient as a built-in verified-inference provider (#3389)
ambient-gregory Jun 6, 2026
573f097
chore(deps): update dependency @types/node to v24.13.1 (#3458)
renovate[bot] Jun 6, 2026
40e3ba1
build(deps): bump brace-expansion from 5.0.5 to 5.0.6 (#3359)
dependabot[bot] Jun 6, 2026
339cfe6
fix(openai_responses): preserve 503 retryable errors in stream (#3460)
amitksingh1490 Jun 6, 2026
4a359a5
fix(select): ignore key Release events so pickers do not close instan…
resrever Jun 6, 2026
d364ece
fix(editor): strip ANSI from rustyline prompt raw() so the cursor tra…
resrever Jun 6, 2026
a53a8aa
chore(deps): update tokio-prost monorepo to v0.14.4 (#3467)
renovate[bot] Jun 7, 2026
1e1e814
chore(deps): update dependency ai to v6.0.198 (#3470)
renovate[bot] Jun 9, 2026
f3c17fa
chore(deps): update rust crate http to v1.4.2 (#3471)
renovate[bot] Jun 9, 2026
91f78ac
chore(deps): update rust crate uuid to v1.23.3 (#3473)
renovate[bot] Jun 9, 2026
f86077c
chore(deps): update dependency @ai-sdk/google-vertex to v4.0.143 (#3475)
renovate[bot] Jun 9, 2026
88b024d
chore(deps): update dependency ai to v6.0.199 (#3476)
renovate[bot] Jun 10, 2026
c3924e2
fix(anthropic): support for claude mythos and fable models (#3474)
ssddOnTop Jun 10, 2026
d6c069d
chore(deps): update rust crate regex to v1.12.4 (#3478)
renovate[bot] Jun 10, 2026
0ccb929
chore(deps): update dependency ai to v6.0.200 (#3481)
renovate[bot] Jun 11, 2026
e921ef5
chore(deps): update dependency @types/node to v24.13.2 (#3483)
renovate[bot] Jun 11, 2026
10a3865
chore(deps): update dependency ai to v6.0.201 (#3484)
renovate[bot] Jun 11, 2026
04c80b5
chore(deps): update rust crate insta to v1.48.0 (#3486)
renovate[bot] Jun 12, 2026
fc8999d
chore(deps): update dependency @ai-sdk/google-vertex to v4.0.144 (#3488)
renovate[bot] Jun 12, 2026
328a43d
chore(deps): update dependency ai to v6.0.202 (#3489)
renovate[bot] Jun 12, 2026
a2dc3af
chore(deps): update rust crate rmcp to v1 [security] (#3277)
renovate[bot] Jun 12, 2026
6097f8a
build(deps): bump openssl from 0.10.78 to 0.10.80 (#3364)
dependabot[bot] Jun 12, 2026
3699303
fix(config): config auto_install_vscode_extension option (#3485)
amitksingh1490 Jun 12, 2026
6901d64
chore(deps): update rust crate aws-smithy-types to v1.5.0 (#3490)
renovate[bot] Jun 12, 2026
1aab3ae
chore(deps): update rust crate async-openai to 0.41.0 (#3078)
renovate[bot] Jun 12, 2026
cb202d7
feat(provider): add claude-fable-5 to vertex_ai_anthropic models (#3480)
akhil29 Jun 12, 2026
a1ad76d
chore(deps): update rust crate google-cloud-auth to v1.13.0 (#3491)
renovate[bot] Jun 12, 2026
d26a266
fix(deps): update rust crate posthog-rs to 0.11.0 (#3487)
renovate[bot] Jun 12, 2026
db61122
fix(forge_select): enter alternate screen to keep prompt visible (#3492)
amitksingh1490 Jun 12, 2026
cef7c7a
fix(zsh): pad _forge_reset to avoid zle clearing output (#3494)
amitksingh1490 Jun 12, 2026
da5fe41
chore(deps): update dependency @ai-sdk/google-vertex to v4.0.145 (#3495)
renovate[bot] Jun 12, 2026
cd1fc3f
fix(deps): update rust crate posthog-rs to 0.12.0 (#3498)
renovate[bot] Jun 13, 2026
b5a91a3
chore(deps): update dependency ai to v6.0.204 (#3496)
renovate[bot] Jun 13, 2026
05e448b
chore(deps): update rust crate aws-sdk-bedrockruntime to v1.134.0 (#3…
renovate[bot] Jun 13, 2026
3063380
fix(provider): add missing fireworks models in provider.json (#3504)
amitksingh1490 Jun 13, 2026
cd597e0
fix(provider): add z.ai glm-5.2 model to provider.json (#3505)
amitksingh1490 Jun 13, 2026
1ca2237
chore(deps): update dependency ai to v6.0.205 (#3509)
renovate[bot] Jun 14, 2026
34cfeb6
fix: show only direct conversation initiated by the user via the `:co…
tusharmath Jun 14, 2026
f3beee1
feat: add parent_id, source, FTS5, subagent hiding, and loop commands
Jun 14, 2026
fd4a28d
Merge upstream/main into feat/session-viewer
Jun 14, 2026
7065903
fix(ui): apply user_initiated_conversations filter to SelectCommand::…
Jun 15, 2026
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
685 changes: 357 additions & 328 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ machineid-rs = "1.2.4"
mockito = "1.7.2"
nom = "8.0.0"
nu-ansi-term = "0.50.1"
posthog-rs = "0.7.0"
posthog-rs = "0.12.0"
pretty_assertions = "1.4.1"
proc-macro2 = "1.0"
quote = "1.0"
reedline = "0.48.0"
rustyline = "18.0.0"
regex = "1.12.3"
reqwest = { version = "0.12.23", features = [
Expand Down Expand Up @@ -121,9 +120,8 @@ whoami = "2.1.0"
fnv_rs = "0.4.3"
merge = { version = "0.2", features = ["derive"] }
hex = "0.4.3"
rmcp = { version = "0.10.0", features = [
rmcp = { version = "1.0.0", features = [
"client",
"transport-sse-client-reqwest",
"transport-child-process",
"transport-streamable-http-client-reqwest",
"auth",
Expand All @@ -135,7 +133,7 @@ gray_matter = "0.3.2"
num-format = "0.4"
humantime = "2.1.0"
dashmap = "7.0.0-rc2"
async-openai = { version = "0.34.0", default-features = false, features = ["response-types"] } # Using only types, not the API client - reduces dependencies
async-openai = { version = "0.41.0", default-features = false, features = ["response-types"] } # Using only types, not the API client - reduces dependencies
gix = "0.84"
google-cloud-auth = "1.8.0" # Google Cloud authentication with automatic token refresh

Expand Down
19 changes: 19 additions & 0 deletions crates/forge_api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ pub trait API: Sync + Send {
/// Returns an error if the operation fails
async fn delete_conversation(&self, conversation_id: &ConversationId) -> Result<()>;

/// Lists all subagent conversations for a given parent conversation
async fn get_subagents(
&self,
parent_id: &ConversationId,
) -> Result<Vec<Conversation>>;

/// Lists all top-level (parent) conversations, excluding subagents
async fn get_parent_conversations(
&self,
limit: Option<usize>,
) -> Result<Vec<Conversation>>;

/// Lists conversations by source (e.g., "interactive", "headless", "forge-p")
async fn get_conversations_by_source(
&self,
source: &str,
limit: Option<usize>,
) -> Result<Vec<Conversation>>;

/// Renames a conversation by setting its title
///
/// # Arguments
Expand Down
34 changes: 34 additions & 0 deletions crates/forge_api/src/forge_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,40 @@ impl<
self.services.delete_conversation(conversation_id).await
}

async fn get_subagents(
&self,
parent_id: &ConversationId,
) -> Result<Vec<Conversation>> {
Ok(self
.services
.get_conversations_by_parent(parent_id)
.await?
.unwrap_or_default())
}

async fn get_parent_conversations(
&self,
limit: Option<usize>,
) -> Result<Vec<Conversation>> {
Ok(self
.services
.get_parent_conversations(limit)
.await?
.unwrap_or_default())
}

async fn get_conversations_by_source(
&self,
source: &str,
limit: Option<usize>,
) -> Result<Vec<Conversation>> {
Ok(self
.services
.get_conversations_by_source(source, limit)
.await?
.unwrap_or_default())
}

async fn rename_conversation(
&self,
conversation_id: &ConversationId,
Expand Down
9 changes: 8 additions & 1 deletion crates/forge_app/src/agent_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> AgentEx
task: String,
ctx: &ToolCallContext,
conversation_id: Option<ConversationId>,
parent_id: Option<ConversationId>,
) -> anyhow::Result<ToolOutput> {
ctx.send_tool_input(
TitleFormat::debug(format!(
Expand All @@ -66,9 +67,15 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> AgentEx
// Create context with agent initiator since it's spawned by a parent agent
// This is crucial for GitHub Copilot billing optimization
let context = forge_domain::Context::default().initiator("agent".to_string());
let conversation = Conversation::generate()
let mut conversation = Conversation::generate()
.title(task.clone())
.context(context.clone());
if let Some(parent) = parent_id {
conversation.parent_id = Some(parent);
}
if let Some(source) = ctx.source() {
conversation.source = Some(source.to_string());
}
self.services
.conversation_service()
.upsert_conversation(conversation.clone())
Expand Down
7 changes: 7 additions & 0 deletions crates/forge_app/src/dto/anthropic/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ impl From<Model> for forge_domain::Model {
|| value.id.contains("claude-sonnet")
|| value.id.contains("claude-opus")
|| value.id.contains("claude-haiku")
|| value.id.contains("claude-mythos")
|| value.id.contains("claude-fable")
{
vec![
forge_domain::InputModality::Text,
Expand Down Expand Up @@ -77,6 +79,11 @@ impl From<Model> for forge_domain::Model {
/// header
/// - Legacy models may have different context lengths
fn get_context_length(model_id: &str) -> Option<u64> {
// Claude Mythos / Fable models (1M context)
if model_id.starts_with("claude-mythos") || model_id.starts_with("claude-fable") {
return Some(1_000_000);
}
Comment on lines +82 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add regression tests for the new Mythos/Fable branches.

Line 83 and Line 84 introduce a new 1M-context path, and Line 37-Line 38 add new modality matching, but there is no explicit test for either branch. Please add focused tests for claude-mythos* and claude-fable* in get_context_length and Model -> forge_domain::Model conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/forge_app/src/dto/anthropic/response.rs` around lines 82 - 85, The new
code branches for claude-mythos and claude-fable models (returning 1M context)
and the new modality matching logic lack test coverage. Add focused regression
tests for the get_context_length function that verify both claude-mythos* and
claude-fable* model IDs correctly return a context length of 1,000,000, and also
add tests for the Model to forge_domain::Model conversion to ensure the new
modality matching handles these models properly without breaking existing
behavior.


// Current models (200K context)
if model_id.starts_with("claude-sonnet-4-5-")
|| model_id.starts_with("claude-haiku-4-5-")
Expand Down
52 changes: 46 additions & 6 deletions crates/forge_app/src/dto/openai/transformers/minimax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,45 @@ use crate::dto::openai::Request;

/// Transformer that applies minimax-specific parameter adjustments
///
/// Minimax M2 models require specific temperature, top_p, and top_k values
/// Minimax models require specific temperature, top_p, and top_k values
/// for optimal performance:
/// - Temperature: 1.0
/// - Top P: 0.95
/// - Top K: 40 (for m2.1), 20 (for other m2 models)
/// - Top K: 40 (for m2.1), 20 (for all other models including M2, M2.5, M2.7,
/// M3)
///
/// These parameters are based on official MiniMax evaluation methodology
/// (see VideoMMMU, Video-MME benchmarks in M3 blog post).
pub struct SetMinimaxParams;

impl Transformer for SetMinimaxParams {
type Value = Request;

fn transform(&mut self, mut request: Self::Value) -> Self::Value {
// Check if model is a minimax-m2 model
let model_id = request
.model
.as_ref()
.map(|m| m.as_str().to_lowercase())
.unwrap_or_default();

if !model_id.contains("minimax-m2") {
// Match MiniMax model patterns (minimax-m2, minimax-m3, etc.)
let is_minimax = model_id.contains("minimax-m2") || model_id.contains("minimax-m3");

if !is_minimax {
return request;
}

// Set temperature to 1.0 for minimax-m2 models
// Set temperature to 1.0 for minimax models
request.temperature = Some(1.0);

// Set top_p to 0.95 for minimax-m2 models
// Set top_p to 0.95 for minimax models
request.top_p = Some(0.95);

// Set top_k based on model variant
if model_id.contains("minimax-m2.1") {
request.top_k = Some(40);
} else {
// M2, M2.5, M2.7, M3 all use top_k = 20
request.top_k = Some(20);
}

Expand Down Expand Up @@ -145,4 +152,37 @@ mod tests {
assert_eq!(actual.top_p, fixture.top_p);
assert_eq!(actual.top_k, fixture.top_k);
}

#[test]
fn test_minimax_m3_sets_parameters() {
let fixture = create_request_fixture("minimax-m3");
let mut transformer = SetMinimaxParams;
let actual = transformer.transform(fixture);

assert_eq!(actual.temperature, Some(1.0));
assert_eq!(actual.top_p, Some(0.95));
assert_eq!(actual.top_k, Some(20)); // M3 uses same config as M2.7
}

#[test]
fn test_minimax_m3_case_insensitive() {
let fixture = create_request_fixture("MiniMax-M3");
let mut transformer = SetMinimaxParams;
let actual = transformer.transform(fixture);

assert_eq!(actual.temperature, Some(1.0));
assert_eq!(actual.top_p, Some(0.95));
assert_eq!(actual.top_k, Some(20)); // M3 uses same config as M2.7
}

#[test]
fn test_minimax_m3_bedrock_provider_id() {
let fixture = create_request_fixture("minimax.minimax-m3");
let mut transformer = SetMinimaxParams;
let actual = transformer.transform(fixture);

assert_eq!(actual.temperature, Some(1.0));
assert_eq!(actual.top_p, Some(0.95));
assert_eq!(actual.top_k, Some(20)); // M3 uses same config as M2.7
}
}
2 changes: 1 addition & 1 deletion crates/forge_app/src/fmt/todo_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn format_todo_line(todo: &Todo, line_style: TodoLineStyle) -> String {
TodoStatus::Completed => "󰄵",
TodoStatus::InProgress => "󰄗",
TodoStatus::Pending => "󰄱",
TodoStatus::Cancelled => "󰅙",
TodoStatus::Cancelled => "",
};

let content = match todo.status {
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod operation;
mod orch;
#[cfg(test)]
mod orch_spec;
mod retry;
pub mod retry;
mod search_dedup;
mod services;
mod set_conversation_id;
Expand Down
10 changes: 8 additions & 2 deletions crates/forge_app/src/orch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,14 @@ impl<S: AgentService + EnvironmentInfra<Config = forge_config::ForgeConfig>> Orc

// Retrieve the number of requests allowed per tick.
let max_requests_per_turn = self.agent.max_requests_per_turn;
let tool_context =
ToolCallContext::new(self.conversation.metrics.clone()).sender(self.sender.clone());
let tool_context = {
let mut ctx = ToolCallContext::new(self.conversation.metrics.clone())
.sender(self.sender.clone());
ctx.set_conversation_id(Some(self.conversation.id));
ctx.set_parent_id(self.conversation.parent_id);
ctx.set_source(self.conversation.source.clone());
ctx
};

while !should_yield {
// Set context for the current loop iteration
Expand Down
47 changes: 47 additions & 0 deletions crates/forge_app/src/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,25 @@ pub trait ConversationService: Send + Sync {

/// Permanently deletes a conversation
async fn delete_conversation(&self, conversation_id: &ConversationId) -> anyhow::Result<()>;

/// Find all subagent conversations for a given parent
async fn get_conversations_by_parent(
&self,
parent_id: &ConversationId,
) -> anyhow::Result<Option<Vec<Conversation>>>;

/// Find all top-level conversations (those without a parent)
async fn get_parent_conversations(
&self,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<Conversation>>>;

/// Find conversations by source (e.g., "interactive", "headless", "forge-p")
async fn get_conversations_by_source(
&self,
source: &str,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<Conversation>>>;
}

#[async_trait::async_trait]
Expand Down Expand Up @@ -634,6 +653,34 @@ impl<I: Services> ConversationService for I {
.delete_conversation(conversation_id)
.await
}

async fn get_conversations_by_parent(
&self,
parent_id: &ConversationId,
) -> anyhow::Result<Option<Vec<Conversation>>> {
self.conversation_service()
.get_conversations_by_parent(parent_id)
.await
}

async fn get_parent_conversations(
&self,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<Conversation>>> {
self.conversation_service()
.get_parent_conversations(limit)
.await
}

async fn get_conversations_by_source(
&self,
source: &str,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<Conversation>>> {
self.conversation_service()
.get_conversations_by_source(source, limit)
.await
}
}
#[async_trait::async_trait]
impl<I: Services> ProviderService for I {
Expand Down
8 changes: 6 additions & 2 deletions crates/forge_app/src/tool_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> ToolReg
let executor = self.agent_executor.clone();
let session_id = task_input.session_id.clone();
let agent_id = task_input.agent_id.clone();
let parent_id = context.conversation_id();
// Parse session_id into ConversationId if present
let conversation_id = session_id
.map(|id| forge_domain::ConversationId::parse(&id))
Expand All @@ -120,9 +121,10 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> ToolReg
let outputs = join_all(task_input.tasks.into_iter().map(|task| {
let agent_id = agent_id.clone();
let executor = executor.clone();
let parent_id = parent_id;
async move {
executor
.execute(AgentId::new(&agent_id), task, context, conversation_id)
.execute(AgentId::new(&agent_id), task, context, conversation_id, parent_id)
.await
}
}))
Expand Down Expand Up @@ -169,13 +171,15 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> ToolReg
let agent_input = AgentInput::try_from(&input)?;
let executor = self.agent_executor.clone();
let agent_name = input.name.as_str().to_string();
let parent_id = context.conversation_id();
// NOTE: Agents should not timeout
let outputs = join_all(agent_input.tasks.into_iter().map(|task| {
let agent_name = agent_name.clone();
let executor = executor.clone();
let parent_id = parent_id;
async move {
executor
.execute(AgentId::new(&agent_name), task, context, None)
.execute(AgentId::new(&agent_name), task, context, None, parent_id)
.await
}
}))
Expand Down
Loading
Loading