From bcc717774b81b96d4d73b81fc19c851d2405fbf7 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Thu, 28 May 2026 19:52:39 +0530 Subject: [PATCH 01/70] fix: pin reedline to 0.47.0 (#3398) Co-authored-by: ForgeCode Co-authored-by: laststylebender <43403528+laststylebender14@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- rust-toolchain.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6da9d9a7f4..89b1ce0e6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6656,9 +6656,9 @@ dependencies = [ [[package]] name = "reedline" -version = "0.48.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201e8e0160cbe7bb5eb2caccf281e178e77fac95115ab31a2c29edc5593603c8" +checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" dependencies = [ "chrono", "crossterm 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index ade18a2165..74ce489076 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ posthog-rs = "0.7.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" -reedline = "0.48.0" +reedline = "=0.47.0" rustyline = "18.0.0" regex = "1.12.3" reqwest = { version = "0.12.23", features = [ diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7ff6933be4..c1f37b40ff 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.95" +channel = "1.94" profile = "default" From 9e16491043b9fb5096eacd64afb8a278b777dff9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 14:35:51 +0000 Subject: [PATCH 02/70] chore(deps): update rust crate reedline to v0.48.0 (#3406) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89b1ce0e6d..1a9c21a068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3442,7 +3442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.15.5", + "hashbrown 0.17.0", ] [[package]] @@ -6656,9 +6656,9 @@ dependencies = [ [[package]] name = "reedline" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" +checksum = "201e8e0160cbe7bb5eb2caccf281e178e77fac95115ab31a2c29edc5593603c8" dependencies = [ "chrono", "crossterm 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index 74ce489076..c616856ef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ posthog-rs = "0.7.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" -reedline = "=0.47.0" +reedline = "=0.48.0" rustyline = "18.0.0" regex = "1.12.3" reqwest = { version = "0.12.23", features = [ From fd9b64cba97e270510fbf504c38fe8c103418c70 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Thu, 28 May 2026 20:09:32 +0530 Subject: [PATCH 03/70] Revert "chore(deps): update rust crate reedline to v0.48.0" (#3409) --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a9c21a068..89b1ce0e6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3442,7 +3442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.17.0", + "hashbrown 0.15.5", ] [[package]] @@ -6656,9 +6656,9 @@ dependencies = [ [[package]] name = "reedline" -version = "0.48.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201e8e0160cbe7bb5eb2caccf281e178e77fac95115ab31a2c29edc5593603c8" +checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" dependencies = [ "chrono", "crossterm 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index c616856ef2..74ce489076 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ posthog-rs = "0.7.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" -reedline = "=0.48.0" +reedline = "=0.47.0" rustyline = "18.0.0" regex = "1.12.3" reqwest = { version = "0.12.23", features = [ From 337f2fa22e49a2fbb8f5e6fdbf18939961b86870 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Thu, 28 May 2026 20:17:08 +0530 Subject: [PATCH 04/70] fix(openai_responses): handle codex response completed/incomplete events (#3405) --- .../provider/openai_responses/repository.rs | 20 ++++ .../src/provider/openai_responses/response.rs | 102 +++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/crates/forge_repo/src/provider/openai_responses/repository.rs b/crates/forge_repo/src/provider/openai_responses/repository.rs index ac28697650..d709972170 100644 --- a/crates/forge_repo/src/provider/openai_responses/repository.rs +++ b/crates/forge_repo/src/provider/openai_responses/repository.rs @@ -231,6 +231,16 @@ impl OpenAIResponsesProvider { .usage(usage), )))) } + Ok(super::response::ResponsesStreamEvent::ResponseCompleted { + response, + }) => Some(Ok(super::response::StreamItem::Message(Box::new( + super::response::into_response_completed_message(response), + )))), + Ok(super::response::ResponsesStreamEvent::ResponseIncomplete { + response, + }) => Some(Err(super::response::into_response_incomplete_error( + response.incomplete_details.map(|d| d.reason), + ))), Ok(super::response::ResponsesStreamEvent::Unknown(_)) => None, Ok(super::response::ResponsesStreamEvent::Response(inner)) => { Some(Ok(super::response::StreamItem::Event(inner))) @@ -310,6 +320,16 @@ impl OpenAIResponsesProvider { .usage(usage), )))) } + Ok(super::response::ResponsesStreamEvent::ResponseCompleted { + response, + }) => Some(Ok(super::response::StreamItem::Message(Box::new( + super::response::into_response_completed_message(response), + )))), + Ok(super::response::ResponsesStreamEvent::ResponseIncomplete { + response, + }) => Some(Err(super::response::into_response_incomplete_error( + response.incomplete_details.map(|d| d.reason), + ))), Ok(super::response::ResponsesStreamEvent::Unknown(_)) => None, Ok(super::response::ResponsesStreamEvent::Response(inner)) => { Some(Ok(super::response::StreamItem::Event(inner))) diff --git a/crates/forge_repo/src/provider/openai_responses/response.rs b/crates/forge_repo/src/provider/openai_responses/response.rs index 201df2ce57..2e58f824c5 100644 --- a/crates/forge_repo/src/provider/openai_responses/response.rs +++ b/crates/forge_repo/src/provider/openai_responses/response.rs @@ -43,6 +43,20 @@ pub(super) enum ResponsesStreamEvent { cost: f64, }, + /// Codex backend `response.completed` event. The Codex backend omits + /// required `oai::Response` fields (e.g. `output`) on this event, so it + /// cannot be parsed via the generic `oai::ResponseStreamEvent`. We + /// deserialize only `end_turn` (backend-only continue-turn signal); other + /// data (output items, usage) arrives via earlier streaming events. + #[serde(rename = "response.completed")] + ResponseCompleted { response: ResponseCompletedPayload }, + + /// Codex backend `response.incomplete` event. Mapped to a hard error so + /// the orchestrator stops the turn instead of looping on a truncated + /// assistant message. + #[serde(rename = "response.incomplete")] + ResponseIncomplete { response: ResponseIncompletePayload }, + /// Any standard OpenAI Responses API streaming event. #[serde(untagged)] Response(Box), @@ -82,10 +96,31 @@ where pub(super) enum StreamItem { /// A standard OpenAI Responses API streaming event. Event(Box), - /// A pre-resolved message (e.g. cost from a proxy ping event). + /// A pre-resolved message (e.g. cost from a proxy ping event, or a + /// Codex `response.completed` event already converted to its terminal + /// `ChatCompletionMessage`). Message(Box), } +/// Payload of the Codex `response.completed` event. The Codex backend omits +/// required `oai::Response` fields (e.g. `output`), so we deserialize only +/// `end_turn` (backend-only continue-turn signal). +#[derive(Debug, Deserialize)] +pub(super) struct ResponseCompletedPayload { + #[serde(default)] + pub end_turn: Option, + #[serde(default)] + pub usage: Option, +} + +/// Payload of the Codex `response.incomplete` event. Carries the +/// `incomplete_details.reason` used to produce a useful error message. +#[derive(Debug, Deserialize)] +pub(super) struct ResponseIncompletePayload { + #[serde(default)] + pub incomplete_details: Option, +} + /// Converts OpenAI Responses API usage into the domain Usage type. /// Usage is sent once in the `response.completed` event (not split across /// events). @@ -269,6 +304,32 @@ fn retain_encrypted_reasoning_details( } } +/// Builds the terminal `ChatCompletionMessage` for a `response.completed` +/// event. Deduplicates content/reasoning/tool_calls that were already streamed +/// via deltas and applies the Codex `end_turn` override when present. +pub(super) fn into_response_completed_message( + payload: ResponseCompletedPayload, +) -> ChatCompletionMessage { + let mut message = ChatCompletionMessage::default(); + if let Some(usage) = payload.usage { + message = message.usage(usage.into_domain()); + } + if payload.end_turn == Some(false) { + // Server explicitly asks to continue the turn; leave finish_reason + // unset so the orchestrator loop does not terminate. + message + } else { + message.finish_reason_opt(Some(FinishReason::Stop)) + } +} + +/// Maps a `response.incomplete` event into a hard error so the orchestrator +/// stops the turn instead of looping on a truncated assistant message. +pub(super) fn into_response_incomplete_error(reason: Option) -> anyhow::Error { + let reason = reason.unwrap_or_else(|| "unknown".to_string()); + anyhow::anyhow!("Upstream response incomplete: {reason}") +} + fn into_response_failed_error(failed: oai::ResponseFailedEvent) -> anyhow::Error { let Some(error) = failed.response.error else { return anyhow::anyhow!("Upstream response failed: no error object returned"); @@ -478,6 +539,7 @@ impl IntoDomain for BoxStream { #[cfg(test)] mod tests { use async_openai::types::responses as oai; + use pretty_assertions::assert_eq; // Type alias for ResponseStream in tests since it's not provided by // response-types @@ -1713,6 +1775,44 @@ mod tests { } } + #[test] + fn test_responses_stream_event_deserializes_codex_response_completed_without_output() { + let fixture = serde_json::json!({ + "type": "response.completed", + "response": { + "id": "resp_1", + "created_at": 1773422509, + "model": "gpt-5.3-codex-spark", + "object": "response", + "status": "completed", + "end_turn": false, + "usage": { + "input_tokens": 14900, + "output_tokens": 381, + "total_tokens": 15281, + "input_tokens_details": { "cached_tokens": 14720 }, + "output_tokens_details": { "reasoning_tokens": 317 } + } + } + }); + let actual: ResponsesStreamEvent = serde_json::from_value(fixture).unwrap(); + let expected = Usage { + prompt_tokens: TokenCount::Actual(14900), + completion_tokens: TokenCount::Actual(381), + total_tokens: TokenCount::Actual(15281), + cached_tokens: TokenCount::Actual(14720), + cost: None, + }; + + match actual { + ResponsesStreamEvent::ResponseCompleted { response } => { + assert_eq!(response.end_turn, Some(false)); + assert_eq!(response.usage.unwrap().into_domain(), expected); + } + other => panic!("Expected ResponseCompleted, got {:?}", other), + } + } + /// Simulates the Spark model's streaming pattern: function call arguments /// are sent only in the `done` event (no deltas). The stream emits: /// 1. output_item.added (function_call with empty arguments) From e037aa7c5d5078387dde53b411a2e9ed61ecc664 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 15:05:33 +0000 Subject: [PATCH 05/70] chore(deps): update rust crate posthog-rs to v0.7.2 (#3410) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89b1ce0e6d..3aecfec5a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,7 +1039,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -3442,7 +3442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -4564,7 +4564,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -4582,7 +4582,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -5720,7 +5720,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.22.1", + "base64 0.21.7", "chrono", "getrandom 0.2.17", "http 1.4.1", @@ -6190,9 +6190,9 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb400667902066c26d87df6c8573755c28f182b48a27d946ef585f39cb0e5c9d" +checksum = "ab7554650c7834f9ab1d286e5e7805471219e839ba1f741b4c423819ec0c80f2" dependencies = [ "chrono", "derive_builder", @@ -6433,7 +6433,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -6471,7 +6471,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -9278,7 +9278,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From eda1e35becedd8b702808ee7d14c708c02b43b4c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 21:20:57 +0000 Subject: [PATCH 06/70] chore(deps): update dependency @ai-sdk/google-vertex to v4.0.140 (#3412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdfaf520f5..2dc66bd3d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,9 +31,9 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "3.0.80", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.80.tgz", - "integrity": "sha512-lT8flzmQe7brMXcj+HxIRqC5/P8N2spHj88n7fdY84K8Ay5TI5hbeic3P2T668d9UmKZtIUcefLwgGW4xzfVkA==", + "version": "3.0.81", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.81.tgz", + "integrity": "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -80,12 +80,12 @@ } }, "node_modules/@ai-sdk/google-vertex": { - "version": "4.0.139", - "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.139.tgz", - "integrity": "sha512-9J1nJQQ9oxd1lyne288PKirZ4ZPyy9ttM0zLbrrPM1tVeaIK70X2yOv8u62hvGw2gvGVA5mi7Q2GSLmAE0LAfw==", + "version": "4.0.140", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.140.tgz", + "integrity": "sha512-Fz3STER9hcrY0uJ6wMg8tSasS5+FbLnBU9N89cw9KBkSUtq+Hefeert7y/UA7RUgPS+d6Z+kzmkqbdAU5dn+oA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/anthropic": "3.0.80", + "@ai-sdk/anthropic": "3.0.81", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", From 5bbd1b53942f3ecf98287997a528903549f52b50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 03:14:34 +0000 Subject: [PATCH 07/70] chore(deps): update dependency ai to v6.0.192 (#3413) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2dc66bd3d9..71800200d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.120", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.120.tgz", - "integrity": "sha512-MYKAeD2q7/sa1ZdqtL2tw0Me0B8Tok6Q/fhkJDhJl39dG8u+VBlWO9yk9lcdm784bM418o1EKObo4aOxs6+18Q==", + "version": "3.0.121", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.121.tgz", + "integrity": "sha512-uY248djJRxa5W68MHiyqO8WLdOeKQoRClGg7PVX/VPhVW8SJNM7/l5DcrA5WAM3YfQrLyNkgZa2VOu8T0t8LUw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -707,12 +707,12 @@ } }, "node_modules/ai": { - "version": "6.0.191", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.191.tgz", - "integrity": "sha512-zAxvjKebQE7YkSyyNIl0OM7i6/zygnKeF+yNUjD4nWOelYrG+LpDd6RnH6mjySI4zUpZ7o4wbnmAy8jc6u98vQ==", + "version": "6.0.193", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.193.tgz", + "integrity": "sha512-VQOTOse8+X8kMtg61DNSXlYJzwOW4NjMLDJNk/qxClWsFe4oiyFJDHGGG1oezfGcFzuYuQe/8Z7r4kwiZWh2YQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.120", + "@ai-sdk/gateway": "3.0.121", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" From 58edf670b660d6878d28099d247edee32842c0a3 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 29 May 2026 08:49:30 +0530 Subject: [PATCH 08/70] fix(provider): add model entries to provider.json and vertex.json (#3414) --- crates/forge_repo/src/provider/provider.json | 160 +++++++++++++++++++ vertex.json | 10 ++ 2 files changed, 170 insertions(+) diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index 64af4400ed..d492d38696 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -634,6 +634,16 @@ "response_type": "Anthropic", "url": "https://api.anthropic.com/v1/messages", "models": [ + { + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "claude-opus-4-7", "name": "Claude Opus 4.7", @@ -1096,6 +1106,16 @@ "response_type": "Google", "url": "https://{{#if (eq LOCATION \"global\")}}aiplatform.googleapis.com{{else}}{{LOCATION}}-aiplatform.googleapis.com{{/if}}/v1beta1/projects/{{PROJECT_ID}}/locations/{{LOCATION}}/publishers/google", "models": [ + { + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "description": "", + "context_length": 1048576, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "gemini-3.1-flash-lite-preview", "name": "Gemini 3.1 Flash Lite Preview", @@ -1296,6 +1316,66 @@ "response_type": "Bedrock", "url": "https://bedrock-runtime.{{AWS_REGION}}.amazonaws.com", "models": [ + { + "id": "jp.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (JP)", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "au.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (AU)", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "global.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (Global)", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "eu.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (EU)", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "us.anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8 (US)", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "anthropic.claude-opus-4-8", + "name": "Claude Opus 4.8", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "google.gemma-3-27b-it", "name": "Google Gemma 3 27B Instruct", @@ -2164,6 +2244,16 @@ "response_type": "Anthropic", "url": "{{#if (eq LOCATION \"global\")}}https://aiplatform.googleapis.com/v1/projects/{{PROJECT_ID}}/locations/{{LOCATION}}/publishers/anthropic/models/{{else}}https://{{LOCATION}}-aiplatform.googleapis.com/v1/projects/{{PROJECT_ID}}/locations/{{LOCATION}}/publishers/anthropic/models/{{/if}}", "models": [ + { + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", @@ -2324,6 +2414,16 @@ "response_type": "Google", "url": "https://generativelanguage.googleapis.com/v1beta", "models": [ + { + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "description": "", + "context_length": 1048576, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "gemini-3.1-pro-preview", "name": "Gemini 3.1 Pro Preview", @@ -2626,6 +2726,46 @@ "response_type": "OpenCode", "url": "https://opencode.ai/zen", "models": [ + { + "id": "grok-build-0.1", + "name": "Grok Build 0.1", + "description": "", + "context_length": 256000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "description": "", + "context_length": 1048576, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "grok-code", + "name": "Grok Code Fast 1", + "description": "", + "context_length": 256000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "claude-opus-4-6", "name": "Claude Opus 4.6", @@ -2999,6 +3139,16 @@ "response_type": "OpenCode", "url": "https://opencode.ai/zen/go", "models": [ + { + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, { "id": "mimo-v2.5-pro", "name": "MiMo V2.5 Pro", @@ -3210,6 +3360,16 @@ "supports_reasoning": true, "input_modalities": ["text"] }, + { + "id": "qwen3.7-max", + "name": "Qwen3.7 Max", + "description": "", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, { "id": "glm-4.7", "name": "GLM 4.7", diff --git a/vertex.json b/vertex.json index 614cff2236..cf8548a44c 100644 --- a/vertex.json +++ b/vertex.json @@ -1,4 +1,14 @@ [ + { + "id": "google/gemini-3.5-flash", + "name": "Gemini 3.5 Flash", + "description": "", + "context_length": 1048576, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "mistral/mistral-large-2411", "name": "Mistral Large 2411", From c2f0916b80ce804ed864806f51637e010a7dc3f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 03:23:25 +0000 Subject: [PATCH 09/70] chore(deps): update rust crate posthog-rs to v0.7.3 (#3415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3aecfec5a2..a01b540499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,7 +1039,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -4564,7 +4564,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -4582,7 +4582,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -6190,9 +6190,9 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7554650c7834f9ab1d286e5e7805471219e839ba1f741b4c423819ec0c80f2" +checksum = "37fe987ce1bbf655e4a753d5a72d4729fe69fbfbf8d9ca6d1afd452d7117ea54" dependencies = [ "chrono", "derive_builder", @@ -6433,7 +6433,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -6471,7 +6471,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.59.0", ] @@ -9278,7 +9278,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] From 118a582cb547823b31dbb503511a26a7c6bf40ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 05:18:40 +0000 Subject: [PATCH 10/70] chore(deps): update rust crate aws-sdk-bedrockruntime to v1.132.0 (#3416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a01b540499..8e3e90ba12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,10 +334,11 @@ dependencies = [ [[package]] name = "aws-sdk-bedrockruntime" -version = "1.131.0" +version = "1.132.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e494025b4c578bfefd025aada69c51ab1db6b7589f61cb78ae681f3115269209" +checksum = "41a2940faeb61f4f579a434bc3a546e9ab49a89596e94527d329281ef55fd44d" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -1039,7 +1040,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1792,7 +1793,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1965,7 +1966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4878,7 +4879,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5617,7 +5618,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7083,7 +7084,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7163,7 +7164,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8126,7 +8127,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8189,7 +8190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9278,7 +9279,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From b3454cbf49e85bf51454bd38a4b9571384fc6496 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 29 May 2026 13:53:50 +0530 Subject: [PATCH 11/70] fix: apply Opus 4.7 API contract to Claude Opus 4.8 (#3418) Co-authored-by: ForgeCode --- .../transformers/model_specific_reasoning.rs | 71 ++++++++++++++++++- crates/forge_repo/src/provider/anthropic.rs | 15 ++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/crates/forge_app/src/transformers/model_specific_reasoning.rs b/crates/forge_app/src/transformers/model_specific_reasoning.rs index 64a192afc6..8a98030adb 100644 --- a/crates/forge_app/src/transformers/model_specific_reasoning.rs +++ b/crates/forge_app/src/transformers/model_specific_reasoning.rs @@ -27,7 +27,14 @@ impl ModelSpecificReasoning { fn family(&self) -> AnthropicModelFamily { let id = self.model_id.to_lowercase(); - if id.contains("opus-4-7") || id.contains("47-opus") { + if id.contains("opus-4-8") + || id.contains("48-opus") + || id.contains("opus-4-7") + || id.contains("47-opus") + { + // Opus 4.8 shares Opus 4.7's API contract: adaptive thinking only + // (legacy `budget_tokens` returns 400) and non-default sampling + // params (`temperature`/`top_p`/`top_k`) return 400. AnthropicModelFamily::AdaptiveOnly } else if id.contains("opus-4-6") || id.contains("46-opus") @@ -82,7 +89,7 @@ impl Transformer for ModelSpecificReasoning { warn!( model = %self.model_id, dropped_max_tokens = max_tokens, - "Dropping `reasoning.max_tokens` for Opus 4.7: extended thinking budgets are unsupported. Use `reasoning.effort` to control thinking depth instead." + "Dropping `reasoning.max_tokens` for Opus 4.7/4.8: extended thinking budgets are unsupported. Use `reasoning.effort` to control thinking depth instead." ); } context.temperature = None; @@ -108,7 +115,7 @@ impl Transformer for ModelSpecificReasoning { { warn!( model = %self.model_id, - "Dropping `reasoning.effort`: the effort parameter is only supported on Opus 4.5, Opus 4.6, Sonnet 4.6, and Opus 4.7." + "Dropping `reasoning.effort`: the effort parameter is only supported on Opus 4.5, Opus 4.6, Sonnet 4.6, Opus 4.7, and Opus 4.8." ); reasoning.effort = None; } @@ -155,6 +162,64 @@ mod tests { assert_eq!(actual, expected); } + #[test] + fn test_opus_4_8_drops_max_tokens_and_sampling_params() { + // Opus 4.8 shares Opus 4.7's API contract: legacy `budget_tokens` and + // non-default sampling params both return 400, so they must be dropped. + let fixture = fixture_context_with_sampling().reasoning(ReasoningConfig { + enabled: Some(true), + max_tokens: Some(8000), + effort: Some(Effort::XHigh), + exclude: Some(true), + }); + + let actual = ModelSpecificReasoning::new("claude-opus-4-8").transform(fixture); + + let expected = Context::default().reasoning(ReasoningConfig { + enabled: Some(true), + max_tokens: None, + effort: Some(Effort::XHigh), + exclude: Some(true), + }); + + assert_eq!(actual, expected); + } + + #[test] + fn test_opus_4_8_strips_sampling_even_without_reasoning() { + let fixture = fixture_context_with_sampling(); + + let actual = ModelSpecificReasoning::new("claude-opus-4-8").transform(fixture); + + let expected = Context::default(); + + assert_eq!(actual, expected); + } + + #[test] + fn test_opus_4_8_bedrock_prefix_still_matches() { + // Bedrock region prefixes (`us.anthropic.claude-...`) must still be + // classified as AdaptiveOnly so sampling params are stripped and + // `max_tokens` is dropped. + let fixture = fixture_context_with_sampling().reasoning(ReasoningConfig { + enabled: Some(true), + max_tokens: Some(8000), + effort: Some(Effort::XHigh), + exclude: None, + }); + + let actual = ModelSpecificReasoning::new("us.anthropic.claude-opus-4-8").transform(fixture); + + let expected = Context::default().reasoning(ReasoningConfig { + enabled: Some(true), + max_tokens: None, + effort: Some(Effort::XHigh), + exclude: None, + }); + + assert_eq!(actual, expected); + } + #[test] fn test_opus_4_7_strips_sampling_even_without_reasoning() { let fixture = fixture_context_with_sampling(); diff --git a/crates/forge_repo/src/provider/anthropic.rs b/crates/forge_repo/src/provider/anthropic.rs index c9449d1bca..d8c004b5b2 100644 --- a/crates/forge_repo/src/provider/anthropic.rs +++ b/crates/forge_repo/src/provider/anthropic.rs @@ -92,13 +92,16 @@ impl Anthropic { } /// Returns false when the model auto-enables interleaved thinking through -/// adaptive thinking (Opus 4.7, Opus 4.6, Sonnet 4.6). When the model is -/// unknown (e.g., listing endpoints), the flag is included because it is +/// adaptive thinking (Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6). When the model +/// is unknown (e.g., listing endpoints), the flag is included because it is /// harmless on non-chat endpoints and necessary on older chat models. fn interleaved_thinking_required(model: Option<&ModelId>) -> bool { let Some(model) = model else { return true }; let id = model.as_str().to_lowercase(); - !(id.contains("opus-4-7") || id.contains("opus-4-6") || id.contains("sonnet-4-6")) + !(id.contains("opus-4-8") + || id.contains("opus-4-7") + || id.contains("opus-4-6") + || id.contains("sonnet-4-6")) } impl Anthropic { @@ -801,8 +804,8 @@ mod tests { #[test] fn test_get_headers_drops_interleaved_thinking_for_4_6_plus_models() { - // Adaptive thinking auto-enables interleaved thinking on Opus 4.7, - // Opus 4.6, and Sonnet 4.6; the beta header is redundant there. + // Adaptive thinking auto-enables interleaved thinking on Opus 4.8, + // Opus 4.7, Opus 4.6, and Sonnet 4.6; the beta header is redundant there. let chat_url = Url::parse("https://api.anthropic.com/v1/messages").unwrap(); let model_url = Url::parse("https://api.anthropic.com/v1/models").unwrap(); @@ -832,9 +835,11 @@ mod tests { ); for model_id in [ + "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", + "us.anthropic.claude-opus-4-8", "us.anthropic.claude-opus-4-7", "global.anthropic.claude-sonnet-4-6", ] { From 22edd31f1b3d94c6882554d0fb577d47adc06c59 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 29 May 2026 15:27:34 +0530 Subject: [PATCH 12/70] refactor(editor): replace reedline with rustyline completer (#3399) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- Cargo.lock | 85 ++--- Cargo.toml | 1 - crates/forge_main/Cargo.toml | 3 +- crates/forge_main/src/completer/command.rs | 14 +- .../src/completer/input_completer.rs | 20 +- .../forge_main/src/completer/search_term.rs | 12 +- crates/forge_main/src/editor.rs | 346 +++++++++++------- crates/forge_main/src/highlighter.rs | 19 +- crates/forge_main/src/prompt.rs | 98 +++-- 9 files changed, 319 insertions(+), 279 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e3e90ba12..bbd5b0f37d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "x11rb", ] @@ -334,11 +334,10 @@ dependencies = [ [[package]] name = "aws-sdk-bedrockruntime" -version = "1.132.0" +version = "1.131.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a2940faeb61f4f579a434bc3a546e9ab49a89596e94527d329281ef55fd44d" +checksum = "e494025b4c578bfefd025aada69c51ab1db6b7589f61cb78ae681f3115269209" dependencies = [ - "arc-swap", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -1040,7 +1039,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1364,7 +1363,6 @@ dependencies = [ "mio", "parking_lot", "rustix 1.1.4", - "serde", "signal-hook 0.3.18", "signal-hook-mio", "winapi", @@ -1793,7 +1791,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1966,7 +1964,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2058,17 +2056,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix 1.1.4", - "windows-sys 0.59.0", -] - [[package]] name = "fdeflate" version = "0.3.7" @@ -2240,7 +2227,7 @@ dependencies = [ "serde_json", "serde_yml 0.0.13", "sha2 0.11.0", - "strum 0.28.0", + "strum", "strum_macros 0.28.0", "tempfile", "thiserror 2.0.18", @@ -2335,7 +2322,7 @@ dependencies = [ "serde", "serde_json", "serde_yml 0.0.13", - "strum 0.28.0", + "strum", "strum_macros 0.28.0", "thiserror 2.0.18", "tokio", @@ -2481,7 +2468,6 @@ dependencies = [ "colored", "console", "convert_case 0.11.0", - "crossterm 0.29.0", "derive_setters", "dirs", "enable-ansi-support", @@ -2513,14 +2499,14 @@ dependencies = [ "num-format", "open", "pretty_assertions", - "reedline", "regex", "rustls 0.23.40", + "rustyline", "serde", "serde_json", "serial_test", "strip-ansi-escapes", - "strum 0.28.0", + "strum", "strum_macros 0.28.0", "tempfile", "terminal_size", @@ -2605,7 +2591,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "strum 0.28.0", + "strum", "tempfile", "thiserror 2.0.18", "tokio", @@ -2681,7 +2667,7 @@ dependencies = [ "serde_urlencoded", "serde_yml 0.0.13", "strip-ansi-escapes", - "strum 0.28.0", + "strum", "strum_macros 0.28.0", "tempfile", "thiserror 2.0.18", @@ -4879,7 +4865,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5618,7 +5604,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6474,7 +6460,7 @@ dependencies = [ "once_cell", "socket2 0.6.3", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6655,26 +6641,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "reedline" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" -dependencies = [ - "chrono", - "crossterm 0.29.0", - "fd-lock", - "itertools", - "nu-ansi-term", - "serde", - "strip-ansi-escapes", - "strum 0.27.2", - "thiserror 2.0.18", - "unicase", - "unicode-segmentation", - "unicode-width 0.2.2", -] - [[package]] name = "ref-cast" version = "1.0.25" @@ -7071,7 +7037,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7084,7 +7050,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7164,7 +7130,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7941,15 +7907,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros 0.27.2", -] - [[package]] name = "strum" version = "0.28.0" @@ -8127,7 +8084,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8190,7 +8147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9279,7 +9236,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 74ce489076..092735ff4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,6 @@ posthog-rs = "0.7.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" -reedline = "=0.47.0" rustyline = "18.0.0" regex = "1.12.3" reqwest = { version = "0.12.23", features = [ diff --git a/crates/forge_main/Cargo.toml b/crates/forge_main/Cargo.toml index 57d0124d03..ffc3fd859c 100644 --- a/crates/forge_main/Cargo.toml +++ b/crates/forge_main/Cargo.toml @@ -39,8 +39,7 @@ colored.workspace = true anyhow.workspace = true derive_setters.workspace = true lazy_static.workspace = true -reedline.workspace = true -crossterm = "0.29.0" +rustyline.workspace = true nu-ansi-term.workspace = true tracing.workspace = true chrono.workspace = true diff --git a/crates/forge_main/src/completer/command.rs b/crates/forge_main/src/completer/command.rs index 7e6287ff0d..04daab5ec3 100644 --- a/crates/forge_main/src/completer/command.rs +++ b/crates/forge_main/src/completer/command.rs @@ -1,8 +1,9 @@ use std::sync::Arc; use forge_select::ForgeWidget; -use reedline::{Completer, Span, Suggestion}; +use crate::completer::input_completer::InputSuggestion; +use crate::completer::search_term::Span; use crate::model::{ForgeCommand, ForgeCommandManager}; /// A display wrapper for `ForgeCommand` that renders the name and description @@ -25,8 +26,8 @@ impl CommandCompleter { } } -impl Completer for CommandCompleter { - fn complete(&mut self, line: &str, _: usize) -> Vec { +impl CommandCompleter { + pub fn complete(&mut self, line: &str, _: usize) -> Vec { // Determine which sentinel the user typed (`:` or `/`), defaulting to `/`. let sentinel = if line.starts_with(':') { ':' } else { '/' }; @@ -74,15 +75,10 @@ impl Completer for CommandCompleter { match builder.prompt() { Ok(Some(row)) => { - vec![Suggestion { + vec![InputSuggestion { value: row.0.name, - description: None, - style: None, - extra: None, span: Span::new(0, line.len()), append_whitespace: true, - match_indices: None, - display_override: None, }] } _ => vec![], diff --git a/crates/forge_main/src/completer/input_completer.rs b/crates/forge_main/src/completer/input_completer.rs index d5548f5868..d1df03d84b 100644 --- a/crates/forge_main/src/completer/input_completer.rs +++ b/crates/forge_main/src/completer/input_completer.rs @@ -3,10 +3,9 @@ use std::sync::Arc; use forge_select::{ForgeWidget, PreviewLayout, PreviewPlacement, SelectRow}; use forge_walker::Walker; -use reedline::{Completer, Span, Suggestion}; use crate::completer::CommandCompleter; -use crate::completer::search_term::SearchTerm; +use crate::completer::search_term::{SearchTerm, Span}; use crate::model::ForgeCommandManager; pub fn select_workspace_file(cwd: &Path, query: Option) -> anyhow::Result> { @@ -60,14 +59,18 @@ pub struct InputCompleter { command: CommandCompleter, } +pub struct InputSuggestion { + pub value: String, + pub span: Span, + pub append_whitespace: bool, +} + impl InputCompleter { pub fn new(cwd: PathBuf, command_manager: Arc) -> Self { Self { cwd, command: CommandCompleter::new(command_manager) } } -} -impl Completer for InputCompleter { - fn complete(&mut self, line: &str, pos: usize) -> Vec { + pub fn complete(&mut self, line: &str, pos: usize) -> Vec { if line.starts_with('/') || line.starts_with(':') { // if the line starts with '/' or ':' it's probably a command, so we delegate to // the command completer. @@ -86,15 +89,10 @@ impl Completer for InputCompleter { if let Ok(Some(selected)) = select_workspace_file(&self.cwd, initial_text) { let value = format!("[{}]", selected); - return vec![Suggestion { - description: None, + return vec![InputSuggestion { value, - style: None, - extra: None, span: Span::new(query.span.start, query.span.end), append_whitespace: true, - match_indices: None, - display_override: None, }]; } } diff --git a/crates/forge_main/src/completer/search_term.rs b/crates/forge_main/src/completer/search_term.rs index a3c7559446..f3e38f7980 100644 --- a/crates/forge_main/src/completer/search_term.rs +++ b/crates/forge_main/src/completer/search_term.rs @@ -1,4 +1,14 @@ -use reedline::Span; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +impl Span { + pub fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} pub struct SearchTerm { line: String, diff --git a/crates/forge_main/src/editor.rs b/crates/forge_main/src/editor.rs index 9718b8cc72..14da15deef 100644 --- a/crates/forge_main/src/editor.rs +++ b/crates/forge_main/src/editor.rs @@ -1,13 +1,20 @@ +use std::borrow::Cow; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; -use crossterm::event::Event; +use console::{measure_text_width, strip_ansi_codes}; use forge_api::Environment; -use nu_ansi_term::{Color, Style}; -use reedline::{ - ColumnarMenu, DefaultHinter, EditCommand, EditMode, Emacs, FileBackedHistory, KeyCode, - KeyModifiers, MenuBuilder, PromptEditMode, Reedline, ReedlineEvent, ReedlineMenu, - ReedlineRawEvent, Signal, default_emacs_keybindings, +use nu_ansi_term::Style; +use rustyline::completion::{Completer, Pair}; +use rustyline::config::{ColorMode, CompletionType, Config}; +use rustyline::error::ReadlineError as RustyReadlineError; +use rustyline::highlight::Highlighter; +use rustyline::hint::{Hinter, HistoryHinter}; +use rustyline::history::DefaultHistory; +use rustyline::validate::Validator; +use rustyline::{ + Cmd, Context as RustylineContext, Editor, EventHandler, Helper, KeyCode, KeyEvent, Modifiers, + Prompt as RustylinePrompt, }; use super::completer::InputCompleter; @@ -16,14 +23,17 @@ use crate::highlighter::ForgeHighlighter; use crate::model::ForgeCommandManager; use crate::prompt::ForgePrompt; -// TODO: Store the last `HISTORY_CAPACITY` commands in the history file const HISTORY_CAPACITY: usize = 1024 * 1024; -const COMPLETION_MENU: &str = "completion_menu"; +/// Interactive terminal editor used by the Forge prompt. pub struct ForgeEditor { - editor: Reedline, + editor: Editor, + history_file: PathBuf, + pending_buffer: Option, } +/// Result of reading one prompt interaction from the terminal. +#[derive(Debug, PartialEq, Eq)] pub enum ReadResult { Success(String), Empty, @@ -32,162 +42,234 @@ pub enum ReadResult { } impl ForgeEditor { - fn init() -> reedline::Keybindings { - let mut keybindings = default_emacs_keybindings(); - // on TAB press shows the completion menu, and if we've exact match it will - // insert it - keybindings.add_binding( - KeyModifiers::NONE, - KeyCode::Tab, - ReedlineEvent::UntilFound(vec![ - ReedlineEvent::Menu(COMPLETION_MENU.to_string()), - ReedlineEvent::Edit(vec![EditCommand::Complete]), - ]), - ); - - // on CTRL + k press clears the screen - keybindings.add_binding( - KeyModifiers::CONTROL, - KeyCode::Char('k'), - ReedlineEvent::ClearScreen, - ); - - // on CTRL + r press searches the history - keybindings.add_binding( - KeyModifiers::CONTROL, - KeyCode::Char('r'), - ReedlineEvent::SearchHistory, - ); - - // on ALT + Enter press inserts a newline - keybindings.add_binding( - KeyModifiers::ALT, - KeyCode::Enter, - ReedlineEvent::Edit(vec![EditCommand::InsertNewline]), - ); - - keybindings - } - + /// Creates a new interactive editor with history, completion, and + /// highlighting. pub fn new( env: Environment, custom_history_path: Option, manager: Arc, ) -> Self { - // Store file history in system config directory let history_file = env.history_path(custom_history_path.as_ref()); - - let history = Box::new( - FileBackedHistory::with_file(HISTORY_CAPACITY, history_file).unwrap_or_default(), + let helper = ForgeHelper::new(env.cwd, manager); + let config = Config::builder() + .max_history_size(HISTORY_CAPACITY) + .expect("rustyline history capacity should be valid") + .completion_type(CompletionType::List) + .completion_show_all_if_ambiguous(true) + .color_mode(ColorMode::Forced) + .enable_signals(true) + .build(); + let mut editor = Editor::::with_config(config) + .expect("rustyline editor should initialize for an interactive terminal"); + editor.bind_sequence( + KeyEvent(KeyCode::Enter, Modifiers::ALT), + EventHandler::Simple(Cmd::Newline), ); - let completion_menu = Box::new( - ColumnarMenu::default() - .with_name(COMPLETION_MENU) - .with_marker("") - .with_text_style(Style::new().bold().fg(Color::Cyan)) - .with_selected_text_style(Style::new().on(Color::White).fg(Color::Black)), + editor.bind_sequence( + KeyEvent(KeyCode::Char('k'), Modifiers::CTRL), + EventHandler::Simple(Cmd::ClearScreen), ); + editor.bind_sequence( + KeyEvent(KeyCode::Char('K'), Modifiers::CTRL), + EventHandler::Simple(Cmd::ClearScreen), + ); + editor.set_helper(Some(helper)); + let _ = editor.load_history(&history_file); + Self { editor, history_file, pending_buffer: None } + } - let edit_mode = Box::new(ForgeEditMode::new(Self::init())); - - let editor = Reedline::create() - .with_completer(Box::new(InputCompleter::new(env.cwd, manager))) - .with_history(history) - .with_highlighter(Box::new(ForgeHighlighter)) - .with_hinter(Box::new( - DefaultHinter::default().with_style(Style::new().fg(Color::DarkGray)), - )) - .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) - .with_edit_mode(edit_mode) - .with_quick_completions(true) - .with_ansi_colors(true) - .use_bracketed_paste(true); - Self { editor } + fn normalize_result(&mut self, buffer: String) -> ReadResult { + let result = normalize_result_text(buffer); + if let ReadResult::Success(text) = &result { + let _ = self.editor.add_history_entry(text.as_str()); + let _ = self.editor.save_history(&self.history_file); + } + result } + /// Reads one logical input from the terminal. pub fn prompt(&mut self, prompt: &mut ForgePrompt) -> anyhow::Result { - let signal = self.editor.read_line(prompt); + let prompt_text = render_prompt(prompt); + let initial = self.pending_buffer.take().unwrap_or_default(); + let readline = if initial.is_empty() { + self.editor.readline(&prompt_text) + } else { + self.editor + .readline_with_initial(&prompt_text, (&initial, "")) + }; prompt.refresh(); - signal - .map(Into::into) - .map_err(|e| anyhow::anyhow!(ReadLineError(e))) + + match readline { + Ok(buffer) => Ok(self.normalize_result(buffer)), + Err(RustyReadlineError::Interrupted) => Ok(ReadResult::Continue), + Err(RustyReadlineError::Eof) => Ok(ReadResult::Exit), + Err(error) => Err(anyhow::anyhow!(ReadLineError(error))), + } } - /// Sets the buffer content to be pre-filled on the next prompt + /// Sets the buffer content to be pre-filled on the next prompt. pub fn set_buffer(&mut self, content: String) { - self.editor - .run_edit_commands(&[EditCommand::InsertString(content)]); + self.pending_buffer = Some(content); } } #[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct ReadLineError(std::io::Error); - -/// Custom edit mode that wraps Emacs and intercepts paste events. -/// -/// When the terminal sends a bracketed-paste (e.g. from a drag-and-drop), -/// this mode checks whether the pasted text is an existing file path and, -/// if so, wraps it in `@[...]` before it reaches the reedline buffer. This -/// gives the user immediate visual feedback in the input field. -struct ForgeEditMode { - inner: Emacs, +#[error("failed to read line from terminal: {0}")] +pub struct ReadLineError(RustyReadlineError); + +fn normalize_result_text(buffer: String) -> ReadResult { + let trimmed = buffer.trim(); + if trimmed.is_empty() { + return ReadResult::Empty; + } + ReadResult::Success(wrap_pasted_text(trimmed)) } -impl ForgeEditMode { - /// Creates a new `ForgeEditMode` wrapping an Emacs mode with the given - /// keybindings. - fn new(keybindings: reedline::Keybindings) -> Self { - Self { inner: Emacs::new(keybindings) } +fn render_prompt(prompt: &ForgePrompt) -> ResponsivePrompt { + let left = prompt.render_prompt_left(); + let indicator = prompt.render_prompt_indicator(); + let right = prompt.render_prompt_right(); + let right = right.trim_start(); + + if right.trim().is_empty() { + let prompt = format!("{left}{indicator}"); + return ResponsivePrompt { raw: prompt.clone(), styled: prompt }; + } + + if let Some((first_line, remaining)) = left.split_once('\n') { + let right = render_right_prompt(right); + return ResponsivePrompt { + raw: format!("{first_line}\n{remaining}{indicator}"), + styled: format!("{first_line}{right}\n{remaining}{indicator}"), + }; + } + + let right = render_right_prompt(right); + ResponsivePrompt { + raw: format!("{left}{indicator}"), + styled: format!("{left}{right}{indicator}"), } } -impl EditMode for ForgeEditMode { - fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent { - // Convert to the underlying crossterm event so we can inspect it - let raw: Event = event.into(); +fn render_right_prompt(right: &str) -> String { + let width = measure_text_width(strip_ansi_codes(right).as_ref()); + format!("\x1b[s\x1b[999C\x1b[{width}D{right}\x1b[K\x1b[u") +} - if let Event::Paste(ref body) = raw { - let wrapped = wrap_pasted_text(body); - return ReedlineEvent::Edit(vec![EditCommand::InsertString(wrapped)]); - } +struct ResponsivePrompt { + raw: String, + styled: String, +} - // For every other event, delegate to the inner Emacs mode. - // We need to reconstruct a ReedlineRawEvent from the crossterm Event. - // ReedlineRawEvent implements TryFrom. - match ReedlineRawEvent::try_from(raw) { - Ok(raw_event) => self.inner.parse_event(raw_event), - Err(()) => ReedlineEvent::None, - } +impl RustylinePrompt for ResponsivePrompt { + fn raw(&self) -> &str { + &self.raw } - fn edit_mode(&self) -> PromptEditMode { - self.inner.edit_mode() + fn styled(&self) -> &str { + &self.styled } } -impl From for ReadResult { - fn from(signal: Signal) -> Self { - match signal { - Signal::Success(buffer) => { - let trimmed = buffer.trim(); - if trimmed.is_empty() { - ReadResult::Empty - } else { - ReadResult::Success(trimmed.to_string()) - } - } - Signal::ExternalBreak(buffer) => { - let trimmed = buffer.trim(); - if trimmed.is_empty() { - ReadResult::Empty +struct ForgeHelper { + completer: Mutex, + highlighter: ForgeHighlighter, + hinter: HistoryHinter, +} + +impl ForgeHelper { + fn new(cwd: PathBuf, command_manager: Arc) -> Self { + Self { + completer: Mutex::new(InputCompleter::new(cwd, command_manager)), + highlighter: ForgeHighlighter, + hinter: HistoryHinter {}, + } + } +} + +impl Helper for ForgeHelper {} + +impl Completer for ForgeHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &RustylineContext<'_>, + ) -> rustyline::Result<(usize, Vec)> { + let mut completer = self + .completer + .lock() + .expect("input completer mutex poisoned"); + let suggestions = completer.complete(line, pos); + let start = suggestions + .iter() + .map(|suggestion| suggestion.span.start) + .min() + .unwrap_or(pos); + let pairs = suggestions + .into_iter() + .map(|suggestion| { + let replacement = if suggestion.append_whitespace { + format!("{} ", suggestion.value) } else { - ReadResult::Success(trimmed.to_string()) - } + suggestion.value + }; + Pair { display: replacement.clone(), replacement } + }) + .collect(); + Ok((start, pairs)) + } +} + +impl Hinter for ForgeHelper { + type Hint = String; + + fn hint(&self, line: &str, pos: usize, ctx: &RustylineContext<'_>) -> Option { + self.hinter.hint(line, pos, ctx) + } +} + +impl Highlighter for ForgeHelper { + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + let styled = self.highlighter.highlight(line, pos); + if styled.buffer.is_empty() { + return Cow::Borrowed(line); + } + + let default_style = Style::new(); + let mut rendered = String::with_capacity(line.len()); + for (style, text) in styled.buffer { + if style == default_style { + rendered.push_str(&text); + } else { + rendered.push_str(&style.paint(text).to_string()); } - Signal::CtrlC => ReadResult::Continue, - Signal::CtrlD => ReadResult::Exit, - _ => ReadResult::Continue, } + Cow::Owned(rendered) + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Cow::Owned(Style::new().dimmed().paint(hint).to_string()) + } +} + +impl Validator for ForgeHelper {} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_normalize_result_wraps_existing_pasted_path() { + let fixture = "/usr/bin/env".to_string(); + + let actual = normalize_result_text(fixture); + + let expected = ReadResult::Success("@[/usr/bin/env]".to_string()); + assert_eq!(actual, expected); } } diff --git a/crates/forge_main/src/highlighter.rs b/crates/forge_main/src/highlighter.rs index f82e2cdb83..10919c3c75 100644 --- a/crates/forge_main/src/highlighter.rs +++ b/crates/forge_main/src/highlighter.rs @@ -1,5 +1,18 @@ use nu_ansi_term::{Color, Style}; -use reedline::{Highlighter, StyledText}; + +pub(crate) struct StyledText { + pub(crate) buffer: Vec<(Style, String)>, +} + +impl StyledText { + pub fn new() -> Self { + Self { buffer: Vec::new() } + } + + pub fn push(&mut self, value: (Style, String)) { + self.buffer.push(value); + } +} /// Syntax highlighter for the forge readline prompt. /// @@ -11,8 +24,8 @@ use reedline::{Highlighter, StyledText}; /// - All other text is rendered in the default terminal style. pub struct ForgeHighlighter; -impl Highlighter for ForgeHighlighter { - fn highlight(&self, line: &str, _cursor: usize) -> StyledText { +impl ForgeHighlighter { + pub(crate) fn highlight(&self, line: &str, _cursor: usize) -> StyledText { let mut styled = StyledText::new(); if line.is_empty() { diff --git a/crates/forge_main/src/prompt.rs b/crates/forge_main/src/prompt.rs index fce8ba4388..303bcfed15 100644 --- a/crates/forge_main/src/prompt.rs +++ b/crates/forge_main/src/prompt.rs @@ -6,14 +6,10 @@ use convert_case::{Case, Casing}; use derive_setters::Setters; use forge_api::{AgentId, Effort, ModelId, Usage}; use nu_ansi_term::{Color, Style}; -use reedline::{Prompt, PromptHistorySearchStatus}; use crate::display_constants::markers; use crate::utils::humanize_number; -// Constants -const MULTILINE_INDICATOR: &str = "::: "; - // Nerd font symbols — left prompt const DIR_SYMBOL: &str = "\u{ea83}"; // 󪃃 folder icon const BRANCH_SYMBOL: &str = "\u{f418}"; // branch icon @@ -64,10 +60,8 @@ impl ForgePrompt { self.git_branch = git_branch; self } -} -impl Prompt for ForgePrompt { - fn render_prompt_left(&self) -> Cow<'_, str> { + pub fn render_prompt_left(&self) -> Cow<'_, str> { // Left prompt layout: // // AGENT_NAME 󪃃 dir branch @@ -119,7 +113,7 @@ impl Prompt for ForgePrompt { Cow::Owned(result) } - fn render_prompt_right(&self) -> Cow<'_, str> { + pub fn render_prompt_right(&self) -> Cow<'_, str> { // Right prompt layout: agent · tokens · cost · model // Active (tokens > 0): bright white for agent/tokens, green for cost // Inactive (no tokens): all segments dimmed @@ -208,39 +202,9 @@ impl Prompt for ForgePrompt { Cow::Owned(result) } - fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<'_, str> { + pub fn render_prompt_indicator(&self) -> Cow<'_, str> { Cow::Borrowed("") } - - fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> { - Cow::Borrowed(MULTILINE_INDICATOR) - } - - fn render_prompt_history_search_indicator( - &self, - history_search: reedline::PromptHistorySearch, - ) -> Cow<'_, str> { - let prefix = match history_search.status { - PromptHistorySearchStatus::Passing => "", - PromptHistorySearchStatus::Failing => "failing ", - }; - - let mut result = String::with_capacity(32); - - // Handle empty search term more elegantly - if history_search.term.is_empty() { - write!(result, "({prefix}reverse-search) ").unwrap(); - } else { - write!( - result, - "({}reverse-search: {}) ", - prefix, history_search.term - ) - .unwrap(); - } - - Cow::Owned(Style::new().fg(Color::White).paint(&result).to_string()) - } } /// Gets the current git branch name if available @@ -291,6 +255,39 @@ mod tests { } } + enum PromptHistorySearchStatus { + Passing, + Failing, + } + + struct PromptHistorySearch { + status: PromptHistorySearchStatus, + term: String, + } + + fn render_prompt_history_search_indicator( + history_search: PromptHistorySearch, + ) -> Cow<'static, str> { + let prefix = match history_search.status { + PromptHistorySearchStatus::Passing => "", + PromptHistorySearchStatus::Failing => "failing ", + }; + + let mut result = String::with_capacity(32); + if history_search.term.is_empty() { + write!(result, "({prefix}reverse-search) ").unwrap(); + } else { + write!( + result, + "({}reverse-search: {}) ", + prefix, history_search.term + ) + .unwrap(); + } + + Cow::Owned(Style::new().fg(Color::White).paint(&result).to_string()) + } + #[test] fn test_render_prompt_left() { let prompt = ForgePrompt::default(); @@ -347,22 +344,13 @@ mod tests { assert!(actual.contains(AGENT_SYMBOL)); } - #[test] - fn test_render_prompt_multiline_indicator() { - let prompt = ForgePrompt::default(); - let actual = prompt.render_prompt_multiline_indicator(); - let expected = MULTILINE_INDICATOR; - assert_eq!(actual, expected); - } - #[test] fn test_render_prompt_history_search_indicator_passing() { - let prompt = ForgePrompt::default(); - let history_search = reedline::PromptHistorySearch { + let history_search = PromptHistorySearch { status: PromptHistorySearchStatus::Passing, term: "test".to_string(), }; - let actual = prompt.render_prompt_history_search_indicator(history_search); + let actual = render_prompt_history_search_indicator(history_search); let expected = Style::new() .fg(Color::White) .paint("(reverse-search: test) ") @@ -372,12 +360,11 @@ mod tests { #[test] fn test_render_prompt_history_search_indicator_failing() { - let prompt = ForgePrompt::default(); - let history_search = reedline::PromptHistorySearch { + let history_search = PromptHistorySearch { status: PromptHistorySearchStatus::Failing, term: "test".to_string(), }; - let actual = prompt.render_prompt_history_search_indicator(history_search); + let actual = render_prompt_history_search_indicator(history_search); let expected = Style::new() .fg(Color::White) .paint("(failing reverse-search: test) ") @@ -387,12 +374,11 @@ mod tests { #[test] fn test_render_prompt_history_search_indicator_empty_term() { - let prompt = ForgePrompt::default(); - let history_search = reedline::PromptHistorySearch { + let history_search = PromptHistorySearch { status: PromptHistorySearchStatus::Passing, term: "".to_string(), }; - let actual = prompt.render_prompt_history_search_indicator(history_search); + let actual = render_prompt_history_search_indicator(history_search); let expected = Style::new() .fg(Color::White) .paint("(reverse-search) ") From c2437e99f3d98b6700ea3e1dd9e5f4537f48f191 Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Tue, 2 Jun 2026 11:01:16 +0700 Subject: [PATCH 13/70] fix(minimax): MiniMax M3 model support (#3434) Co-authored-by: ForgeCode Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/dto/openai/transformers/minimax.rs | 52 ++++++++++++++++--- crates/forge_repo/src/provider/provider.json | 21 ++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/crates/forge_app/src/dto/openai/transformers/minimax.rs b/crates/forge_app/src/dto/openai/transformers/minimax.rs index cdfb6c90ac..ca55d1d57f 100644 --- a/crates/forge_app/src/dto/openai/transformers/minimax.rs +++ b/crates/forge_app/src/dto/openai/transformers/minimax.rs @@ -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); } @@ -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 + } } diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index d492d38696..04c42ecc0a 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -1443,6 +1443,16 @@ "supports_reasoning": true, "input_modalities": ["text"] }, + { + "id": "minimax.minimax-m3", + "name": "MiniMax M3", + "description": "Frontier coding model with 1M context, MSA sparse attention, native multimodality, and agentic capabilities", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "minimax.minimax-m2.1", "name": "MiniMax M2.1", @@ -1453,6 +1463,7 @@ "supports_reasoning": true, "input_modalities": ["text"] }, + { "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", "name": "Claude Sonnet 4 (US)", @@ -2506,6 +2517,16 @@ "response_type": "Anthropic", "url": "https://{{#if HOSTNAME}}{{HOSTNAME}}{{else}}api.minimax.io{{/if}}/anthropic/v1/messages", "models": [ + { + "id": "MiniMax-M3", + "name": "MiniMax M3", + "description": "Frontier coding model with 1M context, MSA sparse attention, native multimodality (image/video), and agentic capabilities", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "MiniMax-M2.7", "name": "MiniMax M2.7", From 7e5aa7f80c89549c874ebd53f6bf0df47e93c61b Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 2 Jun 2026 06:38:41 +0200 Subject: [PATCH 14/70] fix(gemini): strip propertyNames from tool schemas (#3426) Co-authored-by: Amit Singh --- crates/forge_app/src/utils.rs | 39 +++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/forge_app/src/utils.rs b/crates/forge_app/src/utils.rs index eedda7ad9b..349157d793 100644 --- a/crates/forge_app/src/utils.rs +++ b/crates/forge_app/src/utils.rs @@ -581,6 +581,7 @@ fn normalize_gemini_schema_subset_keywords(map: &mut serde_json::Map Date: Wed, 3 Jun 2026 15:44:46 +0530 Subject: [PATCH 15/70] fix(http): map invalid response status to openai error (#3439) --- crates/forge_infra/src/http.rs | 7 +++++-- .../forge_repo/src/provider/openai_responses/repository.rs | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/forge_infra/src/http.rs b/crates/forge_infra/src/http.rs index 228cc902ef..aaa03c9d25 100644 --- a/crates/forge_infra/src/http.rs +++ b/crates/forge_infra/src/http.rs @@ -173,8 +173,11 @@ impl ForgeHttpInfra { .text() .await .unwrap_or_else(|_| "Unable to read response body".to_string()); - return Err(anyhow::anyhow!(error_body)) - .with_context(|| format_http_context(Some(status), method, url)); + return Err(anyhow::Error::from( + forge_app::dto::openai::Error::InvalidStatusCode(status.as_u16()), + )) + .context(error_body) + .with_context(|| format_http_context(Some(status), method, url)); } Ok(response) diff --git a/crates/forge_repo/src/provider/openai_responses/repository.rs b/crates/forge_repo/src/provider/openai_responses/repository.rs index d709972170..99fd725dc0 100644 --- a/crates/forge_repo/src/provider/openai_responses/repository.rs +++ b/crates/forge_repo/src/provider/openai_responses/repository.rs @@ -1568,7 +1568,7 @@ mod tests { let mut fixture = MockServer::new().await; let _mock = fixture - .mock_codex_responses_stream("/backend-api/codex/responses", vec![], 400) + .mock_codex_responses_stream("/backend-api/codex/responses", vec![], 429) .await; let codex_url = format!("{}/backend-api/codex/responses", fixture.url()); @@ -1593,8 +1593,10 @@ mod tests { let actual = provider_impl .chat(&ModelId::from("gpt-5.1-codex"), context) .await; + let actual = actual.err().expect("chat should fail with status error"); - assert!(actual.is_err()); + let expected = Some(429); + assert_eq!(retry::get_api_status_code(&actual), expected); Ok(()) } From 709850ed339033fa806ca3c20350171ef20545d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 05:35:03 +0000 Subject: [PATCH 16/70] chore(deps): update aws-sdk-rust monorepo (#3420) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 60 +++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbd5b0f37d..4db15883e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "x11rb", ] @@ -242,9 +242,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.17" +version = "1.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517aa062d8bd9015ee23d6daa5e1c1372328412fdae4e6c4c1be9b69c6ad37a2" +checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" dependencies = [ "aws-credential-types", "aws-runtime", @@ -334,10 +334,11 @@ dependencies = [ [[package]] name = "aws-sdk-bedrockruntime" -version = "1.131.0" +version = "1.133.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e494025b4c578bfefd025aada69c51ab1db6b7589f61cb78ae681f3115269209" +checksum = "76314880945928a4ee3956e92af451ef29ef04b734d008bb94b11158a60a1034" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -361,10 +362,11 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.99.0" +version = "1.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f4055e6099b2ec264abdc0d9bbfffce306c1601809275c861594779a0b04b45" +checksum = "b647baea49ff551960b904f905681e9b4765a6c4ea08631e89dc52d8bd3f5896" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -385,10 +387,11 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.101.0" +version = "1.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f009ba0284c5d696425fd7b4dcc5b189f5726f4041b7a5794daecb3a68d598" +checksum = "7ae401c65ff288aa7873117fe535cd32b7b1bb0bc43751d28901a1d5f20636b9" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -409,10 +412,11 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.104.0" +version = "1.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa6622798e19e6a76b690562085dd4771c736cd48343464a53ab4ae2f2c9f84" +checksum = "4c80de7bb7d03e9ca8c9fd7b489f20f3948d3f3be91a7953591347d238115408" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -434,9 +438,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" +checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -523,9 +527,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.6" +version = "0.62.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" +checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -579,9 +583,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" +checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -619,9 +623,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.8" +version = "1.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" +checksum = "53f93074121a1be41317b9aa607143ae17900631f7f59a99f2b905d519d6783b" dependencies = [ "base64-simd", "bytes", @@ -1039,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1964,7 +1968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4865,7 +4869,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6460,7 +6464,7 @@ dependencies = [ "once_cell", "socket2 0.6.3", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7037,7 +7041,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7050,7 +7054,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7130,7 +7134,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8084,7 +8088,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9236,7 +9240,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From f82b86aa408426affbdcf779cfad2408d51e3e2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 05:47:19 +0000 Subject: [PATCH 17/70] chore(deps): update dependency @ai-sdk/google-vertex to v4.0.142 (#3440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71800200d2..0765290aca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,9 +80,9 @@ } }, "node_modules/@ai-sdk/google-vertex": { - "version": "4.0.140", - "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.140.tgz", - "integrity": "sha512-Fz3STER9hcrY0uJ6wMg8tSasS5+FbLnBU9N89cw9KBkSUtq+Hefeert7y/UA7RUgPS+d6Z+kzmkqbdAU5dn+oA==", + "version": "4.0.142", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.142.tgz", + "integrity": "sha512-bTWfj0ITBHjAVJHWCA0DB7PO+aDX8bWxTI9hpNAKH7e5uO74URKdi22zlQAOsROEufcLYiAw0LjrYmmXiksErw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/anthropic": "3.0.81", From 7ccf6001deea998fa41c2143b4e1f081f37505ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 07:41:51 +0000 Subject: [PATCH 18/70] chore(deps): update dependency ai to v6.0.197 (#3444) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0765290aca..900f2f2186 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.121", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.121.tgz", - "integrity": "sha512-uY248djJRxa5W68MHiyqO8WLdOeKQoRClGg7PVX/VPhVW8SJNM7/l5DcrA5WAM3YfQrLyNkgZa2VOu8T0t8LUw==", + "version": "3.0.125", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.125.tgz", + "integrity": "sha512-tocl7cUDoTpmhZqeW8XVKMMznZQwwQAEunF0VyNKmf64qt8NbMIAEiet/vRMzh7Jr9WcFeb6EZjmhLTP4Qx2Og==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -707,12 +707,12 @@ } }, "node_modules/ai": { - "version": "6.0.193", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.193.tgz", - "integrity": "sha512-VQOTOse8+X8kMtg61DNSXlYJzwOW4NjMLDJNk/qxClWsFe4oiyFJDHGGG1oezfGcFzuYuQe/8Z7r4kwiZWh2YQ==", + "version": "6.0.197", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.197.tgz", + "integrity": "sha512-U3KsjkqwQXGHC0u0VeUDqUaNaBS/uQc7v4Vj92Cjv5lPx5DIyRBQYk4Hipy5vwD9AQKIG8uRvdaN9R+pAvrtcQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.121", + "@ai-sdk/gateway": "3.0.125", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" From f996b4706cb5cad58f618731e7094a4aff95a381 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 08:42:58 +0000 Subject: [PATCH 19/70] chore(deps): update dependency tsx to v4.22.4 (#3427) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 900f2f2186..cab1897165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1821,9 +1821,9 @@ } }, "node_modules/tsx": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", - "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" From c804101f6c97afe2a70fc09eb8454988085e1e44 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:21:45 +0000 Subject: [PATCH 20/70] chore(deps): update rust crate chrono to v0.4.45 (#3445) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4db15883e6..ba7604b809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,9 +931,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -4573,7 +4573,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] From 8d00f8a25521863a396a2ebb5f67ed7faf01fcb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:40:46 +0000 Subject: [PATCH 21/70] chore(deps): update rust crate diesel to v2.3.10 (#3446) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba7604b809..1a4a933a34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1681,9 +1681,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.9" +version = "2.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9940fb8467a0a06312218ed384185cb8536aa10d8ec017d0ce7fad2c1bd882d5" +checksum = "29fe29a87fb84c631ffb3ba21798c4b1f3a964701ba78f0dce4bf8668562ec88" dependencies = [ "chrono", "diesel_derives", @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -4573,7 +4573,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] From 317656da56680546d40de344798fc4c78ca250a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:59:57 +0000 Subject: [PATCH 22/70] chore(deps): update rust crate unicode-segmentation to v1.13.3 (#3431) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a4a933a34..7d5fdedb46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8794,9 +8794,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" From 525aa6f66c0318fd9ee4b0a3492344543c8e3bf0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:19:47 +0000 Subject: [PATCH 23/70] chore(deps): update rust crate uuid to v1.23.2 (#3421) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d5fdedb46..fff5c761a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.17.0", + "hashbrown 0.16.1", ] [[package]] @@ -8085,7 +8085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -8931,9 +8931,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", From 36cad6598fd465a4c10b89e7ae2af74406fe300b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:37:05 +0000 Subject: [PATCH 24/70] chore(deps): update dependency @types/node to v24.13.0 (#3447) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index cab1897165..023afb8310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -659,12 +659,12 @@ } }, "node_modules/@types/node": { - "version": "24.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", - "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "version": "24.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.0.tgz", + "integrity": "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/tmp": { @@ -1865,9 +1865,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/web-streams-polyfill": { From ccdf57ac1f64c818ee56b5d923bcbcaaacbe3d8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:18:26 +0000 Subject: [PATCH 25/70] chore(deps): update rust to 1.96 (#3419) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c1f37b40ff..5c97b7a605 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.94" +channel = "1.96" profile = "default" From aba495b203f0d4cd7bb31c1b84ac1ff008667e98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:04:05 +0000 Subject: [PATCH 26/70] chore(deps): update rust crate ignore to v0.4.26 (#3453) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fff5c761a5..0861f27dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4702,9 +4702,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" dependencies = [ "crossbeam-deque", "globset", @@ -9240,7 +9240,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] From c2c8ea5553d16d39dec63c83251cec0e6024bdb4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:24:52 +0000 Subject: [PATCH 27/70] chore(deps): update rust crate google-cloud-auth to v1.12.0 (#3449) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0861f27dd1..89a3e03284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,9 +285,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "google-cloud-auth" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd4f8c914f230834828771125168eaa39bc6602e32cb0316ceeff2add10d449" +checksum = "4595b69c95c727ac896a4f2f16d0cdaa6f547de0bc5d64dbe9201487fc3226d5" dependencies = [ "async-trait", "aws-lc-rs", @@ -3964,7 +3964,7 @@ dependencies = [ "hmac 0.13.0", "http 1.4.1", "jsonwebtoken", - "reqwest 0.13.3", + "reqwest 0.13.4", "rustc_version", "rustls 0.23.40", "rustls-pki-types", @@ -3979,11 +3979,10 @@ dependencies = [ [[package]] name = "google-cloud-gax" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d597e9e4758fc778a60d8c28a8677629675ae40d8652ec000ae5f53f5ae7ec" +checksum = "4f60f45dd97ff91cedfcb6b2b9f860d3d84739386c3557027687c52cc0e698fd" dependencies = [ - "base64 0.22.1", "bytes", "futures", "google-cloud-rpc", @@ -4012,9 +4011,9 @@ dependencies = [ [[package]] name = "google-cloud-wkt" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5daa3084991800bcc5333d7e77bb19259a02b34ee35f35c27b49d602732306e" +checksum = "88e0186e2221bf82c5296500251b4650b111172c324984159a0de9f6bcaa18a5" dependencies = [ "base64 0.22.1", "bytes", @@ -4555,7 +4554,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -4573,7 +4572,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -6096,18 +6095,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -6188,7 +6187,7 @@ dependencies = [ "chrono", "derive_builder", "regex", - "reqwest 0.13.3", + "reqwest 0.13.4", "semver", "serde", "serde_json", @@ -6424,7 +6423,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -6462,7 +6461,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -6801,9 +6800,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -8085,7 +8084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -9240,7 +9239,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] From fbd16e77e42c6b76f5e1d961467dca48d57d3fae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:43:12 +0000 Subject: [PATCH 28/70] chore(deps): update rust crate serial_test to v3.5.0 (#3423) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89a3e03284..7d0f2e8afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7206,15 +7206,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - [[package]] name = "schannel" version = "0.1.29" @@ -7293,12 +7284,6 @@ dependencies = [ "untrusted 0.9.0", ] -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "security-framework" version = "3.7.0" @@ -7504,24 +7489,23 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" dependencies = [ "futures-executor", "futures-util", "log", "once_cell", "parking_lot", - "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", From 6b65b2a9743ee858da045e90489d39e6c35f5161 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:01:27 +0000 Subject: [PATCH 29/70] fix(deps): update rust crate posthog-rs to 0.9.0 (#3451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 21 +++++++++++---------- Cargo.toml | 2 +- crates/forge_tracker/Cargo.toml | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d0f2e8afc..62c685a4dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -4554,7 +4554,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -4572,7 +4572,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -5710,7 +5710,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "getrandom 0.2.17", "http 1.4.1", @@ -6180,12 +6180,13 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.7.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fe987ce1bbf655e4a753d5a72d4729fe69fbfbf8d9ca6d1afd452d7117ea54" +checksum = "669f9027dd349b5893f3e380ea54fcd1e27da3e466248696b78c5d2648ff0a3d" dependencies = [ "chrono", "derive_builder", + "flate2", "regex", "reqwest 0.13.4", "semver", @@ -6423,7 +6424,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -6461,7 +6462,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.59.0", ] @@ -9223,7 +9224,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 092735ff4f..71c8cce5b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ 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.9.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" diff --git a/crates/forge_tracker/Cargo.toml b/crates/forge_tracker/Cargo.toml index 3e83449f6a..10f331833b 100644 --- a/crates/forge_tracker/Cargo.toml +++ b/crates/forge_tracker/Cargo.toml @@ -13,7 +13,7 @@ serde_json.workspace = true tokio.workspace = true tracing.workspace = true sysinfo.workspace = true -posthog-rs = "0.7.0" +posthog-rs = "0.9.0" async-trait.workspace = true chrono.workspace = true whoami.workspace = true From bdeb1bc9460627ab040b16407d98c4aeffb03bd2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:12:35 +0000 Subject: [PATCH 30/70] fix(deps): update rust crate posthog-rs to 0.10.0 (#3455) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/forge_tracker/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62c685a4dd..3ae3eb8dcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6180,9 +6180,9 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "669f9027dd349b5893f3e380ea54fcd1e27da3e466248696b78c5d2648ff0a3d" +checksum = "0cda11c0afa113ad8e7da905846ca18cd23b535aef76c66634235b8807c2c321" dependencies = [ "chrono", "derive_builder", diff --git a/Cargo.toml b/Cargo.toml index 71c8cce5b2..f6aaba955c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ machineid-rs = "1.2.4" mockito = "1.7.2" nom = "8.0.0" nu-ansi-term = "0.50.1" -posthog-rs = "0.9.0" +posthog-rs = "0.10.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" diff --git a/crates/forge_tracker/Cargo.toml b/crates/forge_tracker/Cargo.toml index 10f331833b..14b0f79803 100644 --- a/crates/forge_tracker/Cargo.toml +++ b/crates/forge_tracker/Cargo.toml @@ -13,7 +13,7 @@ serde_json.workspace = true tokio.workspace = true tracing.workspace = true sysinfo.workspace = true -posthog-rs = "0.9.0" +posthog-rs = "0.10.0" async-trait.workspace = true chrono.workspace = true whoami.workspace = true From 38eca6afd1875e6a2f2225e440df302d7dc2eced Mon Sep 17 00:00:00 2001 From: Gregory <205641639+ambient-gregory@users.noreply.github.com> Date: Sat, 6 Jun 2026 07:38:00 +0200 Subject: [PATCH 31/70] fix(provider): Ambient as a built-in verified-inference provider (#3389) Co-authored-by: Amit Singh --- crates/forge_domain/src/provider.rs | 24 +++++++++++++++++++ crates/forge_repo/src/provider/provider.json | 9 +++++++ .../forge_repo/src/provider/provider_repo.rs | 17 +++++++++++++ 3 files changed, 50 insertions(+) diff --git a/crates/forge_domain/src/provider.rs b/crates/forge_domain/src/provider.rs index c88f53c636..2038031c0e 100644 --- a/crates/forge_domain/src/provider.rs +++ b/crates/forge_domain/src/provider.rs @@ -81,6 +81,7 @@ impl ProviderId { pub const ADAL: ProviderId = ProviderId(Cow::Borrowed("adal")); pub const XIAOMI_MIMO: ProviderId = ProviderId(Cow::Borrowed("xiaomi_mimo")); pub const NVIDIA: ProviderId = ProviderId(Cow::Borrowed("nvidia")); + pub const AMBIENT: ProviderId = ProviderId(Cow::Borrowed("ambient")); /// Returns all built-in provider IDs /// @@ -121,6 +122,7 @@ impl ProviderId { ProviderId::ADAL, ProviderId::XIAOMI_MIMO, ProviderId::NVIDIA, + ProviderId::AMBIENT, ] } @@ -155,6 +157,7 @@ impl ProviderId { "adal" => "AdaL".to_string(), "xiaomi_mimo" => "XiaomiMimo".to_string(), "nvidia" => "NVIDIA".to_string(), + "ambient" => "Ambient".to_string(), _ => { // For other providers, use UpperCamelCase conversion use convert_case::{Case, Casing}; @@ -210,6 +213,7 @@ impl std::str::FromStr for ProviderId { "adal" => ProviderId::ADAL, "xiaomi_mimo" => ProviderId::XIAOMI_MIMO, "nvidia" => ProviderId::NVIDIA, + "ambient" => ProviderId::AMBIENT, // For custom providers, use Cow::Owned to avoid memory leaks custom => ProviderId(Cow::Owned(custom.to_string())), }; @@ -586,6 +590,7 @@ mod tests { assert_eq!(ProviderId::OPENCODE_GO.to_string(), "OpenCode Go"); assert_eq!(ProviderId::GOOGLE_AI_STUDIO.to_string(), "GoogleAIStudio"); assert_eq!(ProviderId::NVIDIA.to_string(), "NVIDIA"); + assert_eq!(ProviderId::AMBIENT.to_string(), "Ambient"); } #[test] @@ -626,6 +631,7 @@ mod tests { assert!(built_in.contains(&ProviderId::OPENCODE_GO)); assert!(built_in.contains(&ProviderId::GOOGLE_AI_STUDIO)); assert!(built_in.contains(&ProviderId::NVIDIA)); + assert!(built_in.contains(&ProviderId::AMBIENT)); } #[test] @@ -689,6 +695,24 @@ mod tests { assert!(built_in.contains(&ProviderId::XIAOMI_MIMO)); } + #[test] + fn test_ambient_from_str() { + let actual = ProviderId::from_str("ambient").unwrap(); + let expected = ProviderId::AMBIENT; + assert_eq!(actual, expected); + } + + #[test] + fn test_ambient_display_name() { + assert_eq!(ProviderId::AMBIENT.to_string(), "Ambient"); + } + + #[test] + fn test_ambient_in_built_in_providers() { + let built_in = ProviderId::built_in_providers(); + assert!(built_in.contains(&ProviderId::AMBIENT)); + } + #[test] fn test_io_intelligence() { let fixture = "test_key"; diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index 04c42ecc0a..d4db7d3439 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -3730,5 +3730,14 @@ "url": "https://integrate.api.nvidia.com/v1/chat/completions", "models": "https://integrate.api.nvidia.com/v1/models", "auth_methods": ["api_key"] + }, + { + "id": "ambient", + "api_key_vars": "AMBIENT_API_KEY", + "url_param_vars": [], + "response_type": "OpenAI", + "url": "https://api.ambient.xyz/v1/chat/completions", + "models": "https://api.ambient.xyz/v1/models", + "auth_methods": ["api_key"] } ] diff --git a/crates/forge_repo/src/provider/provider_repo.rs b/crates/forge_repo/src/provider/provider_repo.rs index e7c68a07e6..9f9d2a5877 100644 --- a/crates/forge_repo/src/provider/provider_repo.rs +++ b/crates/forge_repo/src/provider/provider_repo.rs @@ -885,6 +885,23 @@ mod tests { ); } + #[test] + fn test_ambient_config() { + let configs = get_provider_configs(); + let config = configs + .iter() + .find(|c| c.id == ProviderId::AMBIENT) + .unwrap(); + assert_eq!(config.id, ProviderId::AMBIENT); + assert_eq!(config.api_key_vars, Some("AMBIENT_API_KEY".to_string())); + assert!(config.url_param_vars.is_empty()); + assert_eq!(config.response_type, Some(ProviderResponse::OpenAI)); + assert_eq!( + config.url.as_str(), + "https://api.ambient.xyz/v1/chat/completions" + ); + } + #[test] fn test_provider_entry_with_static_models_converts_to_hardcoded() { let model = forge_domain::Model::new("Qwen3.6-35B-A3b-q3-mlx") From 573f0977b80e4259f2504fa4d87f9572d996a616 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 05:42:08 +0000 Subject: [PATCH 32/70] chore(deps): update dependency @types/node to v24.13.1 (#3458) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Amit Singh --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 023afb8310..12d1d08b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -659,9 +659,9 @@ } }, "node_modules/@types/node": { - "version": "24.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.0.tgz", - "integrity": "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg==", + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", "license": "MIT", "dependencies": { "undici-types": "~7.18.0" From 40e3ba13af52058289a0ad792a063773ab7c4ad9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 11:19:30 +0530 Subject: [PATCH 33/70] build(deps): bump brace-expansion from 5.0.5 to 5.0.6 (#3359) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12d1d08b7a..07d2f48095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -796,9 +796,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" From 339cfe68b3bfc246b550b5de122688ea91e4abfc Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Sat, 6 Jun 2026 19:18:34 +0530 Subject: [PATCH 34/70] fix(openai_responses): preserve 503 retryable errors in stream (#3460) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- crates/forge_app/src/lib.rs | 2 +- .../provider/openai_responses/repository.rs | 134 +++++++++++++++++- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/crates/forge_app/src/lib.rs b/crates/forge_app/src/lib.rs index 66de3e618d..e0b747ae9d 100644 --- a/crates/forge_app/src/lib.rs +++ b/crates/forge_app/src/lib.rs @@ -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; diff --git a/crates/forge_repo/src/provider/openai_responses/repository.rs b/crates/forge_repo/src/provider/openai_responses/repository.rs index 99fd725dc0..3c113efbfd 100644 --- a/crates/forge_repo/src/provider/openai_responses/repository.rs +++ b/crates/forge_repo/src/provider/openai_responses/repository.rs @@ -249,11 +249,22 @@ impl OpenAIResponsesProvider { } } Err(forge_eventsource::Error::StreamEnded) => None, - Err(forge_eventsource::Error::InvalidStatusCode(_, response)) - | Err(forge_eventsource::Error::InvalidContentType(_, response)) => { + Err(forge_eventsource::Error::InvalidStatusCode(status, response)) => { let (_, reason) = read_http_error_reason(*response).await; - Some(Err(anyhow::anyhow!(reason) - .context(format_http_context(None, "POST", &url)))) + Some(Err(anyhow::Error::from( + forge_app::dto::openai::Error::InvalidStatusCode(status.as_u16()), + ) + .context(reason) + .context(format_http_context(None, "POST", &url)))) + } + Err(forge_eventsource::Error::InvalidContentType(_, response)) => { + let status = response.status(); + let (_, reason) = read_http_error_reason(*response).await; + Some(Err(anyhow::Error::from( + forge_app::dto::openai::Error::InvalidStatusCode(status.as_u16()), + ) + .context(reason) + .context(format_http_context(None, "POST", &url)))) } Err(e) => { Some(Err(anyhow::Error::from(e) @@ -1678,4 +1689,119 @@ mod tests { assert!(err_str.contains("/v1/responses"), "missing url: {err_str}"); Ok(()) } + + /// Tests that a 503 Service Unavailable error from the SSE endpoint is + /// correctly classified as retryable by the retry logic. + #[tokio::test] + async fn test_stream_503_error_is_retryable() -> anyhow::Result<()> { + let mut fixture = MockServer::new().await; + let _mock = fixture + .mock_post_error("/v1/responses", "upstream connec", 503) + .await; + + let provider = openai_responses( + "test-api-key", + &format!("{}/v1/chat/completions", fixture.url()), + ); + let infra = Arc::new(MockHttpClient { client: reqwest::Client::new() }); + let provider_impl = OpenAIResponsesProvider::new(provider, infra); + let context = ChatContext::default() + .add_message(ContextMessage::user("Hi", None)) + .stream(true); + + let mut stream = provider_impl + .chat(&ModelId::from("gpt-4o"), context) + .await?; + + let actual = stream.next().await.expect("stream should yield one item"); + assert!(actual.is_err()); + let error = actual.unwrap_err(); + + // Verify the status code is preserved in the error + let expected = Some(503u16); + assert_eq!(retry::get_api_status_code(&error), expected); + + // Verify it is classified as retryable + let retry_config = + forge_config::RetryConfig::default().status_codes(vec![429, 500, 502, 503, 504]); + let retry_error = retry::into_retry(error, &retry_config); + assert!( + retry_error + .downcast_ref::() + .is_some_and(|e| { matches!(e, forge_domain::Error::Retryable(_)) }), + "503 error should be classified as retryable" + ); + + Ok(()) + } + + /// Tests that the retry_with_config mechanism will actually retry an + /// operation that produces a 503 error from the OpenAI Responses stream. + #[tokio::test] + async fn test_503_error_triggers_retry() -> anyhow::Result<()> { + use std::sync::atomic::{AtomicUsize, Ordering}; + + let mut fixture = MockServer::new().await; + let _mock = fixture + .mock_post_error("/v1/responses", "upstream connec", 503) + .await; + + let provider = openai_responses( + "test-api-key", + &format!("{}/v1/chat/completions", fixture.url()), + ); + let infra = Arc::new(MockHttpClient { client: reqwest::Client::new() }); + let provider_impl = OpenAIResponsesProvider::new(provider, infra); + let retry_config = forge_config::RetryConfig::default() + .status_codes(vec![429, 500, 502, 503, 504]) + .max_attempts(3usize) + .min_delay_ms(1u64); + + let attempt_count = Arc::new(AtomicUsize::new(0)); + let attempt_count_clone = attempt_count.clone(); + + let result: anyhow::Result<()> = forge_app::retry::retry_with_config( + &retry_config, + || { + let provider_impl = provider_impl.clone(); + let retry_config = retry_config.clone(); + attempt_count_clone.fetch_add(1, Ordering::SeqCst); + async move { + let context = ChatContext::default() + .add_message(ContextMessage::user("Hi", None)) + .stream(true); + + let mut stream = provider_impl + .chat(&ModelId::from("gpt-4o"), context) + .await + .map_err(|e| retry::into_retry(e, &retry_config))?; + + // Drain the stream to surface the 503 error + while let Some(item) = stream.next().await { + let _ = item.map_err(|e| retry::into_retry(e, &retry_config))?; + } + + // The first attempt should never reach here (503 error), + // but if the mock server stops returning 503, we succeed. + Ok(()) + } + }, + None::, + ) + .await; + + // The operation should have failed after exhausting retries + assert!(result.is_err(), "Expected error after retries"); + + // Verify that the operation was retried (1 initial + up to max_attempts + // retries) + let actual_attempts = attempt_count.load(Ordering::SeqCst); + let expected_min_attempts = 2; // At least initial + 1 retry + assert!( + actual_attempts >= expected_min_attempts, + "Expected at least {expected_min_attempts} attempts, got {actual_attempts}" + ); + + Ok(()) + } } From 4a359a5862a46222b0afc1fd179aadb09dd06408 Mon Sep 17 00:00:00 2001 From: resrever Date: Sat, 6 Jun 2026 12:33:57 -0400 Subject: [PATCH 35/70] fix(select): ignore key Release events so pickers do not close instantly on Windows (#3462) Co-authored-by: Claude Opus 4.8 --- crates/forge_select/src/preview.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/forge_select/src/preview.rs b/crates/forge_select/src/preview.rs index 16e9367d9a..85061dbecf 100644 --- a/crates/forge_select/src/preview.rs +++ b/crates/forge_select/src/preview.rs @@ -8,8 +8,8 @@ use std::{cmp, fmt}; use bstr::ByteSlice; use crossterm::cursor::{Hide, MoveTo, MoveToColumn, MoveUp, Show}; use crossterm::event::{ - self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, - MouseEventKind, + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, + KeyModifiers, MouseEventKind, }; use crossterm::style::{ Attribute, Color, Print, ResetColor, SetAttribute, SetBackgroundColor, SetForegroundColor, @@ -435,6 +435,12 @@ fn run_select_ui_values(options: SelectUiOptions) -> anyhow::Result {} Event::Key(key) => { match handle_key_event( key, From d364ece2c465e93f54927f7ff622df349243a6f1 Mon Sep 17 00:00:00 2001 From: resrever Date: Sat, 6 Jun 2026 12:39:06 -0400 Subject: [PATCH 36/70] fix(editor): strip ANSI from rustyline prompt raw() so the cursor tracks on Windows (#3461) Co-authored-by: Claude Opus 4.8 Co-authored-by: Amit Singh --- crates/forge_main/src/editor.rs | 48 ++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/crates/forge_main/src/editor.rs b/crates/forge_main/src/editor.rs index 14da15deef..0f96a61492 100644 --- a/crates/forge_main/src/editor.rs +++ b/crates/forge_main/src/editor.rs @@ -131,24 +131,34 @@ fn render_prompt(prompt: &ForgePrompt) -> ResponsivePrompt { let right = prompt.render_prompt_right(); let right = right.trim_start(); + // `raw` is what rustyline measures to position the cursor; `styled` is what + // it prints. `raw` MUST be free of ANSI escapes: rustyline's Windows + // console backend computes cursor columns by counting grapheme widths of + // `raw` (it cannot interpret escape sequences and debug-asserts against + // them), so any styling left in `raw` is counted as visible width and + // pushes the cursor past where the text actually is. The left prompt and + // indicator are styled via `nu_ansi_term`, so strip those codes for `raw`. + // The right prompt is positioned off to the side with cursor save/restore + // and is not part of the input-line geometry, so it is excluded from `raw` + // entirely. if right.trim().is_empty() { - let prompt = format!("{left}{indicator}"); - return ResponsivePrompt { raw: prompt.clone(), styled: prompt }; + let styled = format!("{left}{indicator}"); + let raw = strip_ansi_codes(&styled).into_owned(); + return ResponsivePrompt { raw, styled }; } if let Some((first_line, remaining)) = left.split_once('\n') { let right = render_right_prompt(right); + let raw = strip_ansi_codes(&format!("{first_line}\n{remaining}{indicator}")).into_owned(); return ResponsivePrompt { - raw: format!("{first_line}\n{remaining}{indicator}"), + raw, styled: format!("{first_line}{right}\n{remaining}{indicator}"), }; } let right = render_right_prompt(right); - ResponsivePrompt { - raw: format!("{left}{indicator}"), - styled: format!("{left}{right}{indicator}"), - } + let raw = strip_ansi_codes(&format!("{left}{indicator}")).into_owned(); + ResponsivePrompt { raw, styled: format!("{left}{right}{indicator}") } } fn render_right_prompt(right: &str) -> String { @@ -272,4 +282,28 @@ mod tests { let expected = ReadResult::Success("@[/usr/bin/env]".to_string()); assert_eq!(actual, expected); } + + #[test] + fn test_render_prompt_raw_has_no_ansi_escapes() { + use std::path::PathBuf; + + use forge_api::{AgentId, ModelId}; + + // rustyline measures `raw()` to position the cursor and, on Windows, + // cannot interpret ANSI escapes (it counts their bytes as visible + // columns). `raw()` must therefore be free of escape sequences even + // though the visible prompt is styled. + let mut prompt = ForgePrompt::new(PathBuf::from("project"), AgentId::default()); + prompt.model(ModelId::new("anthropic/claude-opus-4")); + + let rendered = render_prompt(&prompt); + + assert!( + !rendered.raw.contains('\u{1b}'), + "raw prompt must not contain ANSI escape sequences: {:?}", + rendered.raw + ); + // The styled prompt, by contrast, does carry styling for display. + assert!(rendered.styled.contains('\u{1b}')); + } } From a53a8aa0b367df736ebb72a1ed9f53cae3695b0b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2026 17:44:35 +0000 Subject: [PATCH 37/70] chore(deps): update tokio-prost monorepo to v0.14.4 (#3467) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ae3eb8dcf..3e9f2524b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6319,9 +6319,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -6350,9 +6350,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools", @@ -6363,9 +6363,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] From 1e1e8148e93d288068633675baeb13b88606ea89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 00:02:11 +0000 Subject: [PATCH 38/70] chore(deps): update dependency ai to v6.0.198 (#3470) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07d2f48095..1f5f69b61c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.125", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.125.tgz", - "integrity": "sha512-tocl7cUDoTpmhZqeW8XVKMMznZQwwQAEunF0VyNKmf64qt8NbMIAEiet/vRMzh7Jr9WcFeb6EZjmhLTP4Qx2Og==", + "version": "3.0.126", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.126.tgz", + "integrity": "sha512-OHwcwdVkzLo7Rx4eeFyyH3e/vlsYg11HIgKPf5yH6jkptI1Kzq8BX9xZPIBq4BrLBTF2p6pgvWqRfy6AvARGkQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -707,12 +707,12 @@ } }, "node_modules/ai": { - "version": "6.0.197", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.197.tgz", - "integrity": "sha512-U3KsjkqwQXGHC0u0VeUDqUaNaBS/uQc7v4Vj92Cjv5lPx5DIyRBQYk4Hipy5vwD9AQKIG8uRvdaN9R+pAvrtcQ==", + "version": "6.0.198", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.198.tgz", + "integrity": "sha512-NEXlYGu3n7VTG6cn3SAeimfy60FLH/R5uRpl7zqDu0BLtIuoY1lALBGoJzltoVqd/iFQH1asIy/VqZqLPDWv0w==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.125", + "@ai-sdk/gateway": "3.0.126", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" From f3c17fa602297faa1a167269ed7e310f17d7cee5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 01:10:16 +0000 Subject: [PATCH 39/70] chore(deps): update rust crate http to v1.4.2 (#3471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 74 +++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e9f2524b7..31f8701a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,7 +262,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 1.4.1", + "http 1.4.2", "sha1", "time", "tokio", @@ -324,7 +324,7 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "percent-encoding", "pin-project-lite", @@ -354,7 +354,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "http-body-util", "regex-lite", "tracing", @@ -380,7 +380,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "regex-lite", "tracing", ] @@ -405,7 +405,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "regex-lite", "tracing", ] @@ -431,7 +431,7 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "regex-lite", "tracing", ] @@ -452,7 +452,7 @@ dependencies = [ "hex", "hmac 0.13.0", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "percent-encoding", "sha2 0.11.0", "time", @@ -494,7 +494,7 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "percent-encoding", @@ -571,7 +571,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -592,7 +592,7 @@ dependencies = [ "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "pin-project-lite", "tokio", "tracing", @@ -618,7 +618,7 @@ checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.4.1", + "http 1.4.2", ] [[package]] @@ -631,7 +631,7 @@ dependencies = [ "bytes", "bytes-utils", "http 0.2.12", - "http 1.4.1", + "http 1.4.2", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -677,7 +677,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "itoa", @@ -701,7 +701,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "mime", @@ -2370,7 +2370,7 @@ version = "0.1.0" dependencies = [ "futures", "futures-core", - "http 1.4.1", + "http 1.4.2", "nom", "pin-project-lite", "reqwest 0.11.27", @@ -2425,7 +2425,7 @@ dependencies = [ "futures", "glob", "google-cloud-auth", - "http 1.4.1", + "http 1.4.2", "libsqlite3-sys", "oauth2", "open", @@ -2656,7 +2656,7 @@ dependencies = [ "grep-searcher", "handlebars", "html2md", - "http 1.4.1", + "http 1.4.2", "humantime", "ignore", "infer", @@ -2760,7 +2760,7 @@ dependencies = [ "derive_more", "dirs", "forge_domain", - "http 1.4.1", + "http 1.4.2", "lazy_static", "machineid-rs", "posthog-rs", @@ -3962,7 +3962,7 @@ dependencies = [ "google-cloud-gax", "hex", "hmac 0.13.0", - "http 1.4.1", + "http 1.4.2", "jsonwebtoken", "reqwest 0.13.4", "rustc_version", @@ -3987,7 +3987,7 @@ dependencies = [ "futures", "google-cloud-rpc", "google-cloud-wkt", - "http 1.4.1", + "http 1.4.2", "pin-project", "rand 0.10.1", "serde", @@ -4103,7 +4103,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.1", + "http 1.4.2", "indexmap 2.14.0", "slab", "tokio", @@ -4359,9 +4359,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -4385,7 +4385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.1", + "http 1.4.2", ] [[package]] @@ -4396,7 +4396,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "pin-project-lite", ] @@ -4469,7 +4469,7 @@ dependencies = [ "futures-channel", "futures-core", "h2 0.4.13", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "httparse", "httpdate", @@ -4501,7 +4501,7 @@ version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" dependencies = [ - "http 1.4.1", + "http 1.4.2", "hyper 1.9.0", "hyper-util", "rustls 0.23.40", @@ -4547,7 +4547,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "hyper 1.9.0", "ipnet", @@ -5420,7 +5420,7 @@ dependencies = [ "bytes", "colored", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper 1.9.0", @@ -5471,7 +5471,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.1", + "http 1.4.2", "httparse", "memchr", "mime", @@ -5713,7 +5713,7 @@ dependencies = [ "base64 0.22.1", "chrono", "getrandom 0.2.17", - "http 1.4.1", + "http 1.4.2", "rand 0.8.5", "reqwest 0.12.28", "serde", @@ -6767,7 +6767,7 @@ dependencies = [ "futures-util", "h2 0.4.13", "hickory-resolver", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper 1.9.0", @@ -6810,7 +6810,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper 1.9.0", @@ -6869,7 +6869,7 @@ dependencies = [ "base64 0.22.1", "chrono", "futures", - "http 1.4.1", + "http 1.4.2", "oauth2", "pastey", "pin-project-lite", @@ -8486,7 +8486,7 @@ dependencies = [ "base64 0.22.1", "bytes", "h2 0.4.13", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper 1.9.0", @@ -8575,7 +8575,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "iri-string", @@ -8859,7 +8859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ "base64 0.22.1", - "http 1.4.1", + "http 1.4.2", "httparse", "log", ] From 91f78ac6aec2c36551f8bc23206d5a273a7be486 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:57:18 +0000 Subject: [PATCH 40/70] chore(deps): update rust crate uuid to v1.23.3 (#3473) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31f8701a5e..d923c69675 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.17.0", + "hashbrown 0.16.1", ] [[package]] @@ -8069,7 +8069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -8915,9 +8915,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", From f86077c524f781849da95ffbf56f5e7122478d34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 23:33:53 +0000 Subject: [PATCH 41/70] chore(deps): update dependency @ai-sdk/google-vertex to v4.0.143 (#3475) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f5f69b61c..ce6d641b22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,9 +31,9 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "3.0.81", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.81.tgz", - "integrity": "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA==", + "version": "3.0.82", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.82.tgz", + "integrity": "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -80,12 +80,12 @@ } }, "node_modules/@ai-sdk/google-vertex": { - "version": "4.0.142", - "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.142.tgz", - "integrity": "sha512-bTWfj0ITBHjAVJHWCA0DB7PO+aDX8bWxTI9hpNAKH7e5uO74URKdi22zlQAOsROEufcLYiAw0LjrYmmXiksErw==", + "version": "4.0.143", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.143.tgz", + "integrity": "sha512-ue7NXgSX8F7ASv5y/+HTaAdz+QyZQB0uReeujestsnqCWk5LT52lkIon9ukXC/GbqyF+vvcojLoMWeuLQxDnbQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/anthropic": "3.0.81", + "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/google": "3.0.80", "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", From 88b024de3d3557b0fafcbc809c1c3d624ba30de2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 01:39:01 +0000 Subject: [PATCH 42/70] chore(deps): update dependency ai to v6.0.199 (#3476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce6d641b22..b7cfde5f5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.126", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.126.tgz", - "integrity": "sha512-OHwcwdVkzLo7Rx4eeFyyH3e/vlsYg11HIgKPf5yH6jkptI1Kzq8BX9xZPIBq4BrLBTF2p6pgvWqRfy6AvARGkQ==", + "version": "3.0.127", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.127.tgz", + "integrity": "sha512-Obmw5hmE5x+ccRrMp/Djx5r0rpFVX87YqE6OY06g5fwYlRI30dA84ARfTzX45ivCvkW4eCnBpOVXVWQ/pjH85w==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -707,12 +707,12 @@ } }, "node_modules/ai": { - "version": "6.0.198", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.198.tgz", - "integrity": "sha512-NEXlYGu3n7VTG6cn3SAeimfy60FLH/R5uRpl7zqDu0BLtIuoY1lALBGoJzltoVqd/iFQH1asIy/VqZqLPDWv0w==", + "version": "6.0.199", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.199.tgz", + "integrity": "sha512-6H9RPEjzBQECM+eU1JxAh6jHcZPU/6q5QZ8D8QV8agubf0Mm/kcBlwqrFcFtup6RQzmEvMkVaQOoLCZ8bQ13lA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.126", + "@ai-sdk/gateway": "3.0.127", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" From c3924e2543f72ae37799e298a6bbfd2d23cbc9db Mon Sep 17 00:00:00 2001 From: Sandipsinh Dilipsinh Rathod Date: Wed, 10 Jun 2026 02:00:31 +0000 Subject: [PATCH 43/70] fix(anthropic): support for claude mythos and fable models (#3474) --- crates/forge_app/src/dto/anthropic/response.rs | 7 +++++++ .../src/transformers/model_specific_reasoning.rs | 2 ++ crates/forge_repo/src/provider/anthropic.rs | 4 +++- crates/forge_repo/src/provider/provider.json | 10 ++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/forge_app/src/dto/anthropic/response.rs b/crates/forge_app/src/dto/anthropic/response.rs index ba32540fe7..d964b05953 100644 --- a/crates/forge_app/src/dto/anthropic/response.rs +++ b/crates/forge_app/src/dto/anthropic/response.rs @@ -34,6 +34,8 @@ impl From 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, @@ -77,6 +79,11 @@ impl From for forge_domain::Model { /// header /// - Legacy models may have different context lengths fn get_context_length(model_id: &str) -> Option { + // Claude Mythos / Fable models (1M context) + if model_id.starts_with("claude-mythos") || model_id.starts_with("claude-fable") { + return Some(1_000_000); + } + // Current models (200K context) if model_id.starts_with("claude-sonnet-4-5-") || model_id.starts_with("claude-haiku-4-5-") diff --git a/crates/forge_app/src/transformers/model_specific_reasoning.rs b/crates/forge_app/src/transformers/model_specific_reasoning.rs index 8a98030adb..d003eb1719 100644 --- a/crates/forge_app/src/transformers/model_specific_reasoning.rs +++ b/crates/forge_app/src/transformers/model_specific_reasoning.rs @@ -31,6 +31,8 @@ impl ModelSpecificReasoning { || id.contains("48-opus") || id.contains("opus-4-7") || id.contains("47-opus") + || id.contains("mythos") + || id.contains("fable") { // Opus 4.8 shares Opus 4.7's API contract: adaptive thinking only // (legacy `budget_tokens` returns 400) and non-default sampling diff --git a/crates/forge_repo/src/provider/anthropic.rs b/crates/forge_repo/src/provider/anthropic.rs index d8c004b5b2..009568bcdb 100644 --- a/crates/forge_repo/src/provider/anthropic.rs +++ b/crates/forge_repo/src/provider/anthropic.rs @@ -101,7 +101,9 @@ fn interleaved_thinking_required(model: Option<&ModelId>) -> bool { !(id.contains("opus-4-8") || id.contains("opus-4-7") || id.contains("opus-4-6") - || id.contains("sonnet-4-6")) + || id.contains("sonnet-4-6") + || id.contains("mythos") + || id.contains("fable")) } impl Anthropic { diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index d4db7d3439..e01ef63a47 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -634,6 +634,16 @@ "response_type": "Anthropic", "url": "https://api.anthropic.com/v1/messages", "models": [ + { + "id": "claude-fable-5", + "name": "Claude Fable 5", + "description": "Most capable widely released model for demanding reasoning and agentic work", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "claude-opus-4-8", "name": "Claude Opus 4.8", From d6c069d564d0851fa71ec597465c58f5f7b8b741 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 04:47:08 +0000 Subject: [PATCH 44/70] chore(deps): update rust crate regex to v1.12.4 (#3478) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d923c69675..f3bf0fb99c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6679,9 +6679,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -6708,9 +6708,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" From 0ccb9299bf0a8217be90498dc823f48b75fa83ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 02:15:53 +0000 Subject: [PATCH 45/70] chore(deps): update dependency ai to v6.0.200 (#3481) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b7cfde5f5d..9f454fe0bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -707,9 +707,9 @@ } }, "node_modules/ai": { - "version": "6.0.199", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.199.tgz", - "integrity": "sha512-6H9RPEjzBQECM+eU1JxAh6jHcZPU/6q5QZ8D8QV8agubf0Mm/kcBlwqrFcFtup6RQzmEvMkVaQOoLCZ8bQ13lA==", + "version": "6.0.200", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.200.tgz", + "integrity": "sha512-kme8AWPy7om8s5Isj/uIE0T3hVf8d5QEIpd2WJ/RiAansglud92fIEurCaz/ZYgjW5abcxuyhv55soQnLIWYEA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/gateway": "3.0.127", From e921ef5295810440f4ef25e567821abc0edeec20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 05:05:45 +0000 Subject: [PATCH 46/70] chore(deps): update dependency @types/node to v24.13.2 (#3483) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f454fe0bc..6a16d2ad6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -659,9 +659,9 @@ } }, "node_modules/@types/node": { - "version": "24.13.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", - "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "license": "MIT", "dependencies": { "undici-types": "~7.18.0" From 10a386508f0e8353f569a7234b688f78635d14e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:38:51 +0000 Subject: [PATCH 47/70] chore(deps): update dependency ai to v6.0.201 (#3484) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a16d2ad6a..1392af1cc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -707,9 +707,9 @@ } }, "node_modules/ai": { - "version": "6.0.200", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.200.tgz", - "integrity": "sha512-kme8AWPy7om8s5Isj/uIE0T3hVf8d5QEIpd2WJ/RiAansglud92fIEurCaz/ZYgjW5abcxuyhv55soQnLIWYEA==", + "version": "6.0.201", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.201.tgz", + "integrity": "sha512-iDmXSy7csk+SYaCkTUMwsOE/73pksR1spctV+u+DW97v+R/Cj01gchgCUNVrn9QyNHqOJQuTg+JBVsBsp6lOag==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/gateway": "3.0.127", From 04c80b5fa23dde6f0c8102f1294dd842b33afab1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:04:47 +0000 Subject: [PATCH 48/70] chore(deps): update rust crate insta to v1.48.0 (#3486) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3bf0fb99c..06aa290e74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1795,7 +1795,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1968,7 +1968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4801,9 +4801,9 @@ checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] name = "insta" -version = "1.47.2" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" dependencies = [ "console", "once_cell", @@ -4868,7 +4868,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5607,7 +5607,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7054,7 +7054,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7134,7 +7134,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8069,10 +8069,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8135,7 +8135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9224,7 +9224,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From fc8999dcc8027bc7c352e8b9834c2c6aba73ada5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:12:05 +0000 Subject: [PATCH 49/70] chore(deps): update dependency @ai-sdk/google-vertex to v4.0.144 (#3488) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 106 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1392af1cc2..e91bf1c62e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,13 +31,30 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "3.0.82", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.82.tgz", - "integrity": "sha512-WKKou2wbhGGYV8PSALAPyV2YY4nfCqCPkyBzYtJtDA9yCcIFwsbtkTNgg7bqtLCVzeEsY7wwxRoCWy+EMfrw/A==", + "version": "3.0.83", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.83.tgz", + "integrity": "sha512-K4EZaMpZNsflB3dRrOzljEYooW5CI1zFYI/kaNcJ+YJAZe14UOIFL+fC7lg88S21r6RDgFr1Bs9PFFuzZk/4IQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27" + "@ai-sdk/provider-utils": "4.0.28" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", + "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" }, "engines": { "node": ">=18" @@ -64,13 +81,13 @@ } }, "node_modules/@ai-sdk/google": { - "version": "3.0.80", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.80.tgz", - "integrity": "sha512-5ORbm/yFUPO0MEvZsxBMN0cdKw2+lwU/wVn5KN3KF8Dmk1LughuDuUohMh/7iU/XFTiyB0OvmTW/tdV/J7O9zg==", + "version": "3.0.81", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.81.tgz", + "integrity": "sha512-Xvr/bAdWY/KC63wd593OJwYtRBXFqDpmbQNMPX5HZTTYs6kYFMJ1KQJ/trwUiD/0RxyIsxIjE/b2SfdKEbBnBg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27" + "@ai-sdk/provider-utils": "4.0.28" }, "engines": { "node": ">=18" @@ -80,16 +97,16 @@ } }, "node_modules/@ai-sdk/google-vertex": { - "version": "4.0.143", - "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.143.tgz", - "integrity": "sha512-ue7NXgSX8F7ASv5y/+HTaAdz+QyZQB0uReeujestsnqCWk5LT52lkIon9ukXC/GbqyF+vvcojLoMWeuLQxDnbQ==", + "version": "4.0.144", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.144.tgz", + "integrity": "sha512-3P9p++jUvAirTLB42DFqex+7zmvQ9vy9sNDh1iK41JhjpQ15qoIewvAoqvVDvCaLPobaESAA1uAkr0YTqAO0Eg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/anthropic": "3.0.82", - "@ai-sdk/google": "3.0.80", - "@ai-sdk/openai-compatible": "2.0.48", + "@ai-sdk/anthropic": "3.0.83", + "@ai-sdk/google": "3.0.81", + "@ai-sdk/openai-compatible": "2.0.49", "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27", + "@ai-sdk/provider-utils": "4.0.28", "google-auth-library": "^10.5.0" }, "engines": { @@ -99,14 +116,65 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", + "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", + "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@ai-sdk/openai-compatible": { - "version": "2.0.48", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.48.tgz", - "integrity": "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q==", + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.49.tgz", + "integrity": "sha512-kLcNh0Ygv7WSzJsp65X4ZC89NRbyOTvEZWQ8UKkgM4oZcK+l0mcmpldaRRJKpeP+qs3O0U3mbxIq3DyARdjfxA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27" + "@ai-sdk/provider-utils": "4.0.28" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", + "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" }, "engines": { "node": ">=18" From 328a43d8c9a13bac36ac3a70e84e750c54b8a4aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:31:52 +0000 Subject: [PATCH 50/70] chore(deps): update dependency ai to v6.0.202 (#3489) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 92 +++++++---------------------------------------- 1 file changed, 12 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index e91bf1c62e..fd22d65573 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,31 +46,14 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.28", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", - "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.127", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.127.tgz", - "integrity": "sha512-Obmw5hmE5x+ccRrMp/Djx5r0rpFVX87YqE6OY06g5fwYlRI30dA84ARfTzX45ivCvkW4eCnBpOVXVWQ/pjH85w==", + "version": "3.0.128", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.128.tgz", + "integrity": "sha512-t9Dyqk5thSmIzsNvVTQtZZiO3xqKGkk1hX3+GNpYmro+GuEdW+E6mKFHihb9Y1vCEt3rEyd4pbGUcin4D57FfQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27", + "@ai-sdk/provider-utils": "4.0.28", "@vercel/oidc": "3.2.0" }, "engines": { @@ -116,40 +99,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.28", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", - "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.28", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", - "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/openai-compatible": { "version": "2.0.49", "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.49.tgz", @@ -166,23 +115,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.28", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", - "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/provider": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz", @@ -196,9 +128,9 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.27", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz", - "integrity": "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==", + "version": "4.0.28", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", + "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -775,14 +707,14 @@ } }, "node_modules/ai": { - "version": "6.0.201", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.201.tgz", - "integrity": "sha512-iDmXSy7csk+SYaCkTUMwsOE/73pksR1spctV+u+DW97v+R/Cj01gchgCUNVrn9QyNHqOJQuTg+JBVsBsp6lOag==", + "version": "6.0.202", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.202.tgz", + "integrity": "sha512-14O7uyHVa5lXyt1RzduRqM1zwOnLBN+EaE+btWiP2R7GLHd5oh+Z9uaebR785V/Y2hXlxvuQGsw2Gj6iHpkxsg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.127", + "@ai-sdk/gateway": "3.0.128", "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27", + "@ai-sdk/provider-utils": "4.0.28", "@opentelemetry/api": "^1.9.0" }, "engines": { From a2dc3afc245ec3154ea6db5eac8c14bcc9c78ab1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:40:08 +0000 Subject: [PATCH 51/70] chore(deps): update rust crate rmcp to v1 [security] (#3277) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Amit Singh Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- Cargo.lock | 216 ++++++------------ Cargo.toml | 3 +- .../forge_infra/src/auth/mcp_token_storage.rs | 14 +- crates/forge_infra/src/mcp_client.rs | 140 ++++-------- 4 files changed, 113 insertions(+), 260 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06aa290e74..4d141b57c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -940,7 +940,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1795,7 +1795,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1968,7 +1968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2951,7 +2951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.4", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -4572,7 +4572,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4831,7 +4831,7 @@ dependencies = [ "socket2 0.6.3", "widestring", "windows-registry", - "windows-result 0.4.1", + "windows-result", "windows-sys 0.61.2", ] @@ -4868,7 +4868,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4920,7 +4920,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5540,18 +5540,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nix" version = "0.31.2" @@ -5607,7 +5595,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5951,7 +5939,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -6294,16 +6282,16 @@ dependencies = [ [[package]] name = "process-wrap" -version = "8.2.1" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" +checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", "indexmap 2.14.0", - "nix 0.30.1", + "nix", "tokio", "tracing", - "windows 0.61.3", + "windows 0.62.2", ] [[package]] @@ -6749,7 +6737,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "winreg 0.50.0", ] @@ -6794,7 +6782,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "webpki-roots", ] @@ -6830,12 +6818,14 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-rustls 0.26.4", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams 0.5.0", "web-sys", ] @@ -6861,9 +6851,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.10.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b18323edc657390a6ed4d7a9110b0dec2dc3ed128eb2a123edfbafabdbddc5" +checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" dependencies = [ "async-trait", "base64 0.22.1", @@ -6874,7 +6864,7 @@ dependencies = [ "pastey", "pin-project-lite", "process-wrap", - "reqwest 0.12.28", + "reqwest 0.13.4", "rmcp-macros", "schemars 1.2.1", "serde", @@ -6890,11 +6880,11 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.10.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75d0a62676bf8c8003c4e3c348e2ceb6a7b3e48323681aaf177fdccdac2ce50" +checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "serde_json", @@ -7054,7 +7044,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7134,7 +7124,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7184,7 +7174,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.31.2", + "nix", "radix_trie", "unicode-segmentation", "unicode-width 0.2.2", @@ -8072,7 +8062,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8135,7 +8125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9116,6 +9106,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -9224,7 +9227,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -9244,38 +9247,16 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -9284,20 +9265,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -9308,20 +9276,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -9330,9 +9287,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -9379,36 +9336,20 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-core", + "windows-link", ] [[package]] @@ -9417,18 +9358,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -9437,16 +9369,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -9455,7 +9378,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -9500,7 +9423,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -9549,22 +9472,13 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f6aaba955c..6b689ee4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,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", diff --git a/crates/forge_infra/src/auth/mcp_token_storage.rs b/crates/forge_infra/src/auth/mcp_token_storage.rs index 544833fe40..50fde300d9 100644 --- a/crates/forge_infra/src/auth/mcp_token_storage.rs +++ b/crates/forge_infra/src/auth/mcp_token_storage.rs @@ -98,11 +98,11 @@ impl CredentialStore for McpTokenStorage { if let Some(entry) = store.get(&self.server_url) { use oauth2::basic::BasicTokenType; use oauth2::{AccessToken, RefreshToken}; - use rmcp::transport::auth::OAuthTokenResponse; + use rmcp::transport::auth::{OAuthTokenResponse, VendorExtraTokenFields}; let access_token = AccessToken::new(entry.tokens.access_token.clone()); let token_type = BasicTokenType::Bearer; - let extra_fields = oauth2::EmptyExtraTokenFields {}; + let extra_fields = VendorExtraTokenFields::default(); let mut token_response = OAuthTokenResponse::new(access_token, token_type, extra_fields); @@ -127,14 +127,16 @@ impl CredentialStore for McpTokenStorage { } } - Ok(Some(StoredCredentials { - client_id: entry + Ok(Some(StoredCredentials::new( + entry .client_registration .as_ref() .map(|r| r.client_id.clone()) .unwrap_or_default(), - token_response: Some(token_response), - })) + Some(token_response), + vec![], + None, + ))) } else { Ok(None) } diff --git a/crates/forge_infra/src/mcp_client.rs b/crates/forge_infra/src/mcp_client.rs index 23e246a1f3..1c0b5db47a 100644 --- a/crates/forge_infra/src/mcp_client.rs +++ b/crates/forge_infra/src/mcp_client.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::collections::BTreeMap; use std::future::Future; -use std::str::FromStr; use std::sync::{Arc, OnceLock, RwLock}; use backon::{ExponentialBuilder, Retryable}; @@ -10,13 +9,11 @@ use forge_app::McpClientInfra; use forge_domain::{ Environment, Image, McpHttpServer, McpServerConfig, ToolDefinition, ToolName, ToolOutput, }; -use reqwest::Client; -use reqwest::header::{HeaderName, HeaderValue}; -use rmcp::model::{CallToolRequestParam, ClientInfo, Implementation, InitializeRequestParam}; +use http::{HeaderName, HeaderValue}; +use rmcp::model::{CallToolRequestParams, ClientInfo, Implementation, InitializeRequestParams}; use rmcp::service::RunningService; -use rmcp::transport::sse_client::SseClientConfig; use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig; -use rmcp::transport::{SseClientTransport, StreamableHttpClientTransport, TokioChildProcess}; +use rmcp::transport::{StreamableHttpClientTransport, TokioChildProcess}; use rmcp::{RoleClient, ServiceExt}; use schemars::Schema; use serde_json::Value; @@ -30,12 +27,11 @@ const VERSION: &str = match option_env!("APP_VERSION") { None => env!("CARGO_PKG_VERSION"), }; -type RmcpClient = RunningService; +type RmcpClient = RunningService; #[derive(Clone)] pub struct ForgeMcpClient { client: Arc>>>, - http_client: Arc, config: McpServerConfig, env_vars: BTreeMap, environment: Environment, @@ -43,49 +39,13 @@ pub struct ForgeMcpClient { } impl ForgeMcpClient { - /// Build a reqwest client with default headers from the MCP server config. - fn build_http_client(http: &McpHttpServer) -> anyhow::Result { - let mut header_map = reqwest::header::HeaderMap::new(); - for (key, value) in &http.headers { - if let Ok(name) = HeaderName::from_str(key) - && let Ok(val) = HeaderValue::from_str(value) - { - header_map.insert(name, val); - } - } - - Ok(Client::builder().default_headers(header_map).build()?) - } - pub fn new( config: McpServerConfig, env_vars: &BTreeMap, environment: Environment, ) -> Self { - // Try to resolve config early so we can extract headers for the HTTP client. - // If resolution fails, fall back to a plain client (headers will be missing - // but the error will surface when create_connection is called). - let resolved = resolve_http_templates( - match &config { - McpServerConfig::Http(http) => http.clone(), - McpServerConfig::Stdio(_) => McpHttpServer { - url: String::new(), - headers: BTreeMap::new(), - timeout: None, - disable: false, - oauth: forge_domain::McpOAuthSetting::default(), - }, - }, - env_vars, - ); - - let http_client = resolved - .and_then(|http| Self::build_http_client(&http)) - .unwrap_or_default(); - Self { client: Default::default(), - http_client: Arc::new(http_client), config, env_vars: env_vars.clone(), environment, @@ -107,17 +67,7 @@ impl ForgeMcpClient { } fn client_info(&self) -> ClientInfo { - ClientInfo { - protocol_version: Default::default(), - capabilities: Default::default(), - client_info: Implementation { - name: "Forge".to_string(), - version: VERSION.to_string(), - icons: None, - title: None, - website_url: None, - }, - } + ClientInfo::new(Default::default(), Implementation::new("Forge", VERSION)) } /// Connects to the MCP server. If `force` is true, it will reconnect even @@ -219,23 +169,10 @@ impl ForgeMcpClient { &self, http: &McpHttpServer, ) -> anyhow::Result { - // Try HTTP first, fall back to SSE if it fails - let client = self.reqwest_client(); - let transport = StreamableHttpClientTransport::with_client( - client.as_ref().clone(), - StreamableHttpClientTransportConfig::with_uri(http.url.clone()), - ); - match self.client_info().serve(transport).await { - Ok(client) => Ok(client), - Err(_e) => { - let transport = SseClientTransport::start_with_client( - client.as_ref().clone(), - SseClientConfig { sse_endpoint: http.url.clone().into(), ..Default::default() }, - ) - .await?; - Ok(self.client_info().serve(transport).await?) - } - } + let config = StreamableHttpClientTransportConfig::with_uri(http.url.clone()) + .custom_headers(build_header_map(&http.headers)); + let transport = StreamableHttpClientTransport::from_config(config); + Ok(self.client_info().serve(transport).await?) } /// Create an OAuth-enabled connection using rmcp's OAuth support. @@ -368,10 +305,12 @@ impl ForgeMcpClient { { use rmcp::transport::auth::CredentialStore; let save_store = McpTokenStorage::new(http.url.clone(), self.environment.clone()); - let stored = rmcp::transport::auth::StoredCredentials { - client_id: credentials.0, - token_response: credentials.1, - }; + let stored = rmcp::transport::auth::StoredCredentials::new( + credentials.0, + credentials.1, + vec![], + None, + ); save_store .save(stored) .await @@ -397,12 +336,9 @@ impl ForgeMcpClient { http: &McpHttpServer, token: &str, ) -> anyhow::Result> { - let client = self.reqwest_client(); - let transport = StreamableHttpClientTransport::with_client( - client.as_ref().clone(), - StreamableHttpClientTransportConfig::with_uri(http.url.clone()).auth_header(token), - ); - + let config = + StreamableHttpClientTransportConfig::with_uri(http.url.clone()).auth_header(token); + let transport = StreamableHttpClientTransport::from_config(config); Ok(Arc::new(self.client_info().serve(transport).await?)) } @@ -495,14 +431,6 @@ impl ForgeMcpClient { Ok((code, state)) } - fn reqwest_client(&self) -> Arc { - // Reuse the cached HTTP client (with pre-configured default headers) - // to prevent file descriptor leaks. Each reqwest::Client manages its - // own connection pool, so creating new clients for each connection - // leads to "Too many open files" errors. - self.http_client.clone() - } - async fn list(&self) -> anyhow::Result> { let client = self.connect().await?; let tools = client.list_tools(None).await?; @@ -527,13 +455,12 @@ impl ForgeMcpClient { async fn call(&self, tool_name: &ToolName, input: &Value) -> anyhow::Result { let client = self.connect().await?; let result = client - .call_tool(CallToolRequestParam { - name: Cow::Owned(tool_name.to_string()), - arguments: if let Value::Object(args) = input { - Some(args.clone()) - } else { - None - }, + .call_tool({ + let mut params = CallToolRequestParams::new(Cow::Owned(tool_name.to_string())); + if let Value::Object(args) = input { + params = params.with_arguments(args.clone()); + } + params }) .await?; @@ -627,6 +554,19 @@ fn resolve_http_templates( Ok(http) } +fn build_header_map( + headers: &BTreeMap, +) -> std::collections::HashMap { + headers + .iter() + .filter_map(|(k, v)| { + let name = k.parse::().ok()?; + let val = v.parse::().ok()?; + Some((name, val)) + }) + .collect() +} + /// Trigger OAuth authentication for a specific MCP server URL. /// /// Runs the full OAuth flow: metadata discovery, dynamic registration, @@ -734,10 +674,8 @@ pub async fn mcp_auth(server_url: &str, env: &Environment) -> anyhow::Result<()> .map_err(|e| anyhow::anyhow!("Failed to get credentials: {}", e))?; let save_store = McpTokenStorage::new(server_url.to_string(), env.clone()); - let stored = rmcp::transport::auth::StoredCredentials { - client_id: credentials.0, - token_response: credentials.1, - }; + let stored = + rmcp::transport::auth::StoredCredentials::new(credentials.0, credentials.1, vec![], None); save_store .save(stored) .await From 6097f8afe598092bb8c4d18ef350593b3b9db8ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:44:01 +0000 Subject: [PATCH 52/70] build(deps): bump openssl from 0.10.78 to 0.10.80 (#3364) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Amit Singh --- Cargo.lock | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d141b57c3..f076e43b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5855,15 +5855,14 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.78" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -5887,9 +5886,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.114" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", From 36993037da02336f4544a80c068ee84b8aaae406 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 12 Jun 2026 12:17:30 +0530 Subject: [PATCH 53/70] fix(config): config auto_install_vscode_extension option (#3485) Co-authored-by: laststylebender <43403528+laststylebender14@users.noreply.github.com> --- crates/forge_config/.forge.toml | 1 + crates/forge_config/src/config.rs | 25 +++++++++++++++++++++++++ crates/forge_main/src/ui.rs | 4 +++- forge.schema.json | 5 +++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/forge_config/.forge.toml b/crates/forge_config/.forge.toml index 2104408aa7..fb51e5bb7a 100644 --- a/crates/forge_config/.forge.toml +++ b/crates/forge_config/.forge.toml @@ -32,6 +32,7 @@ research_subagent = false currency_symbol = "$" currency_conversion_rate = 1.0 subagents = true +auto_install_vscode_extension = true use_forge_committer = true use_text_patch_fallback = false diff --git a/crates/forge_config/src/config.rs b/crates/forge_config/src/config.rs index 9bb4d62091..5c7ed51f90 100644 --- a/crates/forge_config/src/config.rs +++ b/crates/forge_config/src/config.rs @@ -323,6 +323,11 @@ pub struct ForgeConfig { #[serde(default)] pub subagents: bool, + /// Enables automatic VS Code extension installation when Forge runs inside + /// VS Code and the extension is not already installed. + #[serde(default)] + pub auto_install_vscode_extension: bool, + /// When `true`, all system messages in the conversation are merged into a /// single leading system message before the request is sent. Enable this /// for providers that reject requests containing system messages after @@ -488,4 +493,24 @@ models = "http://example.com/v1/models" assert_eq!(actual.providers, expected); } + + #[test] + fn test_auto_install_vscode_extension_defaults_to_true() { + let actual = ConfigReader::default().read_defaults().build().unwrap(); + + assert_eq!(actual.auto_install_vscode_extension, true); + } + + #[test] + fn test_auto_install_vscode_extension_can_be_disabled() { + let toml = "auto_install_vscode_extension = false\n"; + + let actual = ConfigReader::default() + .read_defaults() + .read_toml(toml) + .build() + .unwrap(); + + assert_eq!(actual.auto_install_vscode_extension, false); + } } diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 0bfd3112df..2df655129c 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -3860,7 +3860,9 @@ impl A + Send + Sync> UI async fn on_message(&mut self, content: Option) -> Result<()> { let conversation_id = self.init_conversation().await?; - self.install_vscode_extension(); + if self.config.auto_install_vscode_extension { + self.install_vscode_extension(); + } // Track if content was provided to decide whether to use piped input as // additional context diff --git a/forge.schema.json b/forge.schema.json index 541c4ed6f8..3701953b01 100644 --- a/forge.schema.json +++ b/forge.schema.json @@ -15,6 +15,11 @@ } ] }, + "auto_install_vscode_extension": { + "description": "Enables automatic VS Code extension installation when Forge runs inside\nVS Code and the extension is not already installed.", + "type": "boolean", + "default": false + }, "auto_open_dump": { "description": "Whether to automatically open HTML dump files in the browser after\ncreation.", "type": "boolean", From 6901d649d705bc244cbe7be04f09e66e6b9fb691 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:52:07 +0000 Subject: [PATCH 54/70] chore(deps): update rust crate aws-smithy-types to v1.5.0 (#3490) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f076e43b53..c1f6861550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.4.9" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f93074121a1be41317b9aa607143ae17900631f7f59a99f2b905d519d6783b" +checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85" dependencies = [ "base64-simd", "bytes", From 1aab3ae28ddce95fbcfff7f7f8dd0cf2cef0b76b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:03:44 +0000 Subject: [PATCH 55/70] chore(deps): update rust crate async-openai to 0.41.0 (#3078) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Amit Singh Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- .../src/provider/openai_responses/request.rs | 6 +++--- .../src/provider/openai_responses/response.rs | 14 +++++++++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1f6861550..a3e0a41f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "async-openai" -version = "0.34.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec08254d61379df136135d3d1ac04301be7699fd7d9e57655c63ac7d650a6922" +checksum = "3ec57a13b36ba76764870363a9182d8bc9fb49538dc5a948dd2e5224fe65ce40" dependencies = [ "derive_builder", "getrandom 0.3.4", diff --git a/Cargo.toml b/Cargo.toml index 6b689ee4e3..6b3315d525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,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 diff --git a/crates/forge_repo/src/provider/openai_responses/request.rs b/crates/forge_repo/src/provider/openai_responses/request.rs index e6680b5c0c..219094a8c3 100644 --- a/crates/forge_repo/src/provider/openai_responses/request.rs +++ b/crates/forge_repo/src/provider/openai_responses/request.rs @@ -81,7 +81,7 @@ fn map_reasoning_details_to_input_items( Some(oai::InputItem::Item(oai::Item::Reasoning( oai::ReasoningItem { - id, + id: Some(id), summary, content: None, encrypted_content: Some(encrypted_content), @@ -1306,7 +1306,7 @@ mod tests { }; let expected = oai::ReasoningItem { - id: "rs_123".to_string(), + id: Some("rs_123".to_string()), summary: vec![oai::SummaryPart::SummaryText(oai::SummaryTextContent { text: "Summary of reasoning".to_string(), })], @@ -1362,7 +1362,7 @@ mod tests { }; let expected = oai::ReasoningItem { - id: "rs_ok".to_string(), + id: Some("rs_ok".to_string()), summary: vec![], content: None, encrypted_content: Some("enc_ok".to_string()), diff --git a/crates/forge_repo/src/provider/openai_responses/response.rs b/crates/forge_repo/src/provider/openai_responses/response.rs index 2e58f824c5..9e91097a84 100644 --- a/crates/forge_repo/src/provider/openai_responses/response.rs +++ b/crates/forge_repo/src/provider/openai_responses/response.rs @@ -186,7 +186,7 @@ impl IntoDomain for oai::Response { message.add_reasoning_detail(forge_domain::Reasoning::Full(vec![ forge_domain::ReasoningFull { data: Some(encrypted_content.clone()), - id: Some(reasoning.id.clone()), + id: reasoning.id.clone(), type_of: Some("reasoning.encrypted".to_string()), ..Default::default() }, @@ -195,8 +195,12 @@ impl IntoDomain for oai::Response { // Process reasoning text content if let Some(content) = &reasoning.content { - let reasoning_text = - content.iter().map(|c| c.text.as_str()).collect::(); + let reasoning_text = content + .iter() + .map(|c| match c { + oai::ReasoningItemContent::ReasoningText(t) => t.text.as_str(), + }) + .collect::(); if !reasoning_text.is_empty() { all_reasoning_text.push_str(&reasoning_text); message = @@ -204,7 +208,7 @@ impl IntoDomain for oai::Response { forge_domain::ReasoningFull { text: Some(reasoning_text), type_of: Some("reasoning.text".to_string()), - id: Some(reasoning.id.clone()), + id: reasoning.id.clone(), ..Default::default() }, ])); @@ -231,7 +235,7 @@ impl IntoDomain for oai::Response { forge_domain::ReasoningFull { text: Some(summary_text), type_of: Some("reasoning.summary".to_string()), - id: Some(reasoning.id.clone()), + id: reasoning.id.clone(), ..Default::default() }, ])); From cb202d75503b6f7e6e2e06cac849bd6e28e6b4df Mon Sep 17 00:00:00 2001 From: Akhil Appana Date: Fri, 12 Jun 2026 00:13:23 -0700 Subject: [PATCH 56/70] feat(provider): add claude-fable-5 to vertex_ai_anthropic models (#3480) Co-authored-by: akhilapp Co-authored-by: Amit Singh --- crates/forge_repo/src/provider/provider.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index e01ef63a47..4249a08782 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -2265,6 +2265,16 @@ "response_type": "Anthropic", "url": "{{#if (eq LOCATION \"global\")}}https://aiplatform.googleapis.com/v1/projects/{{PROJECT_ID}}/locations/{{LOCATION}}/publishers/anthropic/models/{{else}}https://{{LOCATION}}-aiplatform.googleapis.com/v1/projects/{{PROJECT_ID}}/locations/{{LOCATION}}/publishers/anthropic/models/{{/if}}", "models": [ + { + "id": "claude-fable-5", + "name": "Claude Fable 5", + "description": "Most capable widely released model for demanding reasoning and agentic work", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "claude-opus-4-8", "name": "Claude Opus 4.8", From a1ad76ded528083d35c2915adc7db7ce81a8f81d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:18:31 +0000 Subject: [PATCH 57/70] chore(deps): update rust crate google-cloud-auth to v1.13.0 (#3491) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3e0a41f3a..6f96f73951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,7 +1043,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -3433,7 +3433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.17.0", + "hashbrown 0.16.1", ] [[package]] @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "google-cloud-auth" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4595b69c95c727ac896a4f2f16d0cdaa6f547de0bc5d64dbe9201487fc3226d5" +checksum = "a300d4011cb53573eafe2419630d303ced54aab6c194a6d9e4156de375800372" dependencies = [ "async-trait", "aws-lc-rs", @@ -4554,7 +4554,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -5698,7 +5698,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.22.1", + "base64 0.21.7", "chrono", "getrandom 0.2.17", "http 1.4.2", @@ -6411,7 +6411,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -6449,7 +6449,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -9226,7 +9226,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From d26a266d9e91ebb3bd2396d3a5c646d6d5d66e1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:22:32 +0000 Subject: [PATCH 58/70] fix(deps): update rust crate posthog-rs to 0.11.0 (#3487) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 128 +++++++++++++++++++++++++++++++- Cargo.toml | 2 +- crates/forge_tracker/Cargo.toml | 2 +- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f96f73951..02118adb88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bstr" version = "1.12.1" @@ -5733,6 +5742,27 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -5757,6 +5787,38 @@ dependencies = [ "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -5770,6 +5832,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.0", + "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -5795,6 +5859,18 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + [[package]] name = "objc2-system-configuration" version = "0.3.2" @@ -5804,6 +5880,37 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -5912,6 +6019,22 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "os_info" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + [[package]] name = "outref" version = "0.5.2" @@ -6167,13 +6290,14 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cda11c0afa113ad8e7da905846ca18cd23b535aef76c66634235b8807c2c321" +checksum = "05496f1939d2ea81626618b458718975068ec11353eb74750e556a2c8fe3ce3a" dependencies = [ "chrono", "derive_builder", "flate2", + "os_info", "regex", "reqwest 0.13.4", "semver", diff --git a/Cargo.toml b/Cargo.toml index 6b3315d525..e10227ca9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ machineid-rs = "1.2.4" mockito = "1.7.2" nom = "8.0.0" nu-ansi-term = "0.50.1" -posthog-rs = "0.10.0" +posthog-rs = "0.11.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" diff --git a/crates/forge_tracker/Cargo.toml b/crates/forge_tracker/Cargo.toml index 14b0f79803..faa852a08b 100644 --- a/crates/forge_tracker/Cargo.toml +++ b/crates/forge_tracker/Cargo.toml @@ -13,7 +13,7 @@ serde_json.workspace = true tokio.workspace = true tracing.workspace = true sysinfo.workspace = true -posthog-rs = "0.10.0" +posthog-rs = "0.11.0" async-trait.workspace = true chrono.workspace = true whoami.workspace = true From db61122c29321e079388dbdfb8f8277ccfea5439 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 12 Jun 2026 14:26:51 +0530 Subject: [PATCH 59/70] fix(forge_select): enter alternate screen to keep prompt visible (#3492) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- crates/forge_main/src/ui.rs | 7 ++++--- crates/forge_select/src/input.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 2df655129c..59a1057328 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -3251,9 +3251,10 @@ impl A + Send + Sync> UI server.wait_for_code().await? } else { // Prompt user to paste authorization code - let code = ForgeWidget::input("Paste the authorization code") - .prompt()? - .ok_or_else(|| anyhow::anyhow!("Authorization code input cancelled"))?; + let code = + ForgeWidget::input(format!("Paste the authorization code for {provider_id}")) + .prompt()? + .ok_or_else(|| anyhow::anyhow!("Authorization code input cancelled"))?; if code.trim().is_empty() { anyhow::bail!("Authorization code cannot be empty"); diff --git a/crates/forge_select/src/input.rs b/crates/forge_select/src/input.rs index 2dc5918adf..515893c8fe 100644 --- a/crates/forge_select/src/input.rs +++ b/crates/forge_select/src/input.rs @@ -1,7 +1,9 @@ -use std::io::IsTerminal; +use std::io::{self, IsTerminal}; use anyhow::Result; use colored::Colorize; +use crossterm::execute; +use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use rustyline::DefaultEditor; use tracing::debug; @@ -63,6 +65,13 @@ impl InputBuilder { return Ok(None); } + // Enter the alternate screen so that the prompt is always visible and + // cannot be scrolled out of the viewport. This fixes an issue in + // terminals like VS Code (xterm.js) where rustyline's per-keystroke + // redraw causes the viewport to jump back to the cursor position, + // scrolling the prompt out of view. + let _guard = AlternateScreenGuard::enter(); + let mut rl = DefaultEditor::new()?; // On Windows, rustyline miscounts ANSI escape bytes as visible characters, @@ -103,6 +112,24 @@ impl InputBuilder { } } +/// Guard that enters the terminal alternate screen on creation and exits it on +/// drop. Failures are silently ignored — the alternate screen is a cosmetic +/// best-effort fix for terminal viewport issues. +struct AlternateScreenGuard; + +impl AlternateScreenGuard { + fn enter() -> Option { + execute!(io::stdout(), EnterAlternateScreen).ok()?; + Some(Self) + } +} + +impl Drop for AlternateScreenGuard { + fn drop(&mut self) { + let _ = execute!(io::stdout(), LeaveAlternateScreen); + } +} + #[cfg(test)] mod tests { use pretty_assertions::assert_eq; From cef7c7ac741c34f9de9cc0da016124a995ea76b6 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 12 Jun 2026 18:26:19 +0530 Subject: [PATCH 60/70] fix(zsh): pad _forge_reset to avoid zle clearing output (#3494) --- crates/forge_main/src/zsh/plugin.rs | 20 +++++++++++++++----- shell-plugin/lib/dispatcher.zsh | 3 --- shell-plugin/lib/helpers.zsh | 11 +++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/forge_main/src/zsh/plugin.rs b/crates/forge_main/src/zsh/plugin.rs index c91ee70a9a..0350f9ad07 100644 --- a/crates/forge_main/src/zsh/plugin.rs +++ b/crates/forge_main/src/zsh/plugin.rs @@ -417,11 +417,21 @@ mod tests { use pretty_assertions::assert_eq; let fixture = generate_zsh_plugin().unwrap(); - let actual = fixture.contains( - " _forge_osc133_emit \"B\"\n _forge_osc133_emit \"C\"\n case \"$user_action\" in", - ) && fixture.contains( - " local action_status=$?\n _forge_osc133_emit \"D;$action_status\"\n _forge_osc133_emit \"A\"\n _forge_reset", - ); + + // Verify OSC 133 B and C markers are emitted before the action dispatch + let actual = fixture.contains(" _forge_osc133_emit \"B\"\n _forge_osc133_emit \"C\"") + // The buffer is redisplayed with cursor at end so user input stays visible + && fixture.contains("CURSOR=${#BUFFER}\n zle redisplay") + // The case dispatch follows the buffer redisplay + && fixture.contains(" case \"$user_action\" in") + // _forge_reset pads with newlines before clearing to prevent ZLE from clearing + // conversation output lines (see helpers.zsh:139-157) + && fixture.contains("for ((_i=1; _i Date: Fri, 12 Jun 2026 21:35:51 +0000 Subject: [PATCH 61/70] chore(deps): update dependency @ai-sdk/google-vertex to v4.0.145 (#3495) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 106 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd22d65573..0a94497cb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,13 +31,30 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "3.0.83", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.83.tgz", - "integrity": "sha512-K4EZaMpZNsflB3dRrOzljEYooW5CI1zFYI/kaNcJ+YJAZe14UOIFL+fC7lg88S21r6RDgFr1Bs9PFFuzZk/4IQ==", + "version": "3.0.84", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.84.tgz", + "integrity": "sha512-BIDaHmCHs6Sr5VUsEkTbbVlAN4GWjg97X9x/IfXyviLtzsXvffui9XIcZugkAi1Ri6FnvI5T5qDGh5YLnSuzRg==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28" + "@ai-sdk/provider-utils": "4.0.29" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.29", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", + "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" }, "engines": { "node": ">=18" @@ -64,13 +81,13 @@ } }, "node_modules/@ai-sdk/google": { - "version": "3.0.81", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.81.tgz", - "integrity": "sha512-Xvr/bAdWY/KC63wd593OJwYtRBXFqDpmbQNMPX5HZTTYs6kYFMJ1KQJ/trwUiD/0RxyIsxIjE/b2SfdKEbBnBg==", + "version": "3.0.82", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.82.tgz", + "integrity": "sha512-md+M92ZJuPIMU2p4v1rGLpJJWTmTh/vpJPkMnQbEdcLaPTZxRaroIKSnmL/9UGJV0BORJlHNDJegkcnhVpTmDA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28" + "@ai-sdk/provider-utils": "4.0.29" }, "engines": { "node": ">=18" @@ -80,16 +97,16 @@ } }, "node_modules/@ai-sdk/google-vertex": { - "version": "4.0.144", - "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.144.tgz", - "integrity": "sha512-3P9p++jUvAirTLB42DFqex+7zmvQ9vy9sNDh1iK41JhjpQ15qoIewvAoqvVDvCaLPobaESAA1uAkr0YTqAO0Eg==", + "version": "4.0.145", + "resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-4.0.145.tgz", + "integrity": "sha512-48wlju7ksjARn6aa1vUZtPFDp+PXadHDpOsE8YHvi4wKVUf7Sxma+WYZNkIDts1b9Alv0BBZlkn5azLNciHD6g==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/anthropic": "3.0.83", - "@ai-sdk/google": "3.0.81", - "@ai-sdk/openai-compatible": "2.0.49", + "@ai-sdk/anthropic": "3.0.84", + "@ai-sdk/google": "3.0.82", + "@ai-sdk/openai-compatible": "2.0.50", "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28", + "@ai-sdk/provider-utils": "4.0.29", "google-auth-library": "^10.5.0" }, "engines": { @@ -99,14 +116,65 @@ "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.29", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", + "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.29", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", + "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@ai-sdk/openai-compatible": { - "version": "2.0.49", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.49.tgz", - "integrity": "sha512-kLcNh0Ygv7WSzJsp65X4ZC89NRbyOTvEZWQ8UKkgM4oZcK+l0mcmpldaRRJKpeP+qs3O0U3mbxIq3DyARdjfxA==", + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.50.tgz", + "integrity": "sha512-HyuxddF2Yv5G8qxK/0uksAINjQ4h6TpwOqHuqzsCM0u78/JWAW2OXcIplQeB44PIAORgPjbMzrw9DhnPYHMskA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28" + "@ai-sdk/provider-utils": "4.0.29" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { + "version": "4.0.29", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", + "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "3.0.10", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.8" }, "engines": { "node": ">=18" From cd1fc3fd58eea2a1025506d5d256ef715b03dfe0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 02:08:10 +0000 Subject: [PATCH 62/70] fix(deps): update rust crate posthog-rs to 0.12.0 (#3498) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 64 ++++++++++++++++++++++++++++----- Cargo.toml | 2 +- crates/forge_tracker/Cargo.toml | 2 +- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02118adb88..c73564ebac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -722,6 +731,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base64" version = "0.21.7" @@ -1052,7 +1076,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3033,6 +3057,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gix" version = "0.84.0" @@ -3442,7 +3472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -4563,7 +4593,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -5707,7 +5737,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "getrandom 0.2.17", "http 1.4.2", @@ -5911,6 +5941,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -6290,10 +6329,11 @@ dependencies = [ [[package]] name = "posthog-rs" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05496f1939d2ea81626618b458718975068ec11353eb74750e556a2c8fe3ce3a" +checksum = "24e1beb349e47c45a4ffdee65e8c096ff0022c0a4d6c566cacdf50f553427fb3" dependencies = [ + "backtrace", "chrono", "derive_builder", "flate2", @@ -6535,7 +6575,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -6573,7 +6613,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.59.0", ] @@ -7129,6 +7169,12 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -9350,7 +9396,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e10227ca9d..db98f1bdb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ machineid-rs = "1.2.4" mockito = "1.7.2" nom = "8.0.0" nu-ansi-term = "0.50.1" -posthog-rs = "0.11.0" +posthog-rs = "0.12.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" diff --git a/crates/forge_tracker/Cargo.toml b/crates/forge_tracker/Cargo.toml index faa852a08b..9fa0d04045 100644 --- a/crates/forge_tracker/Cargo.toml +++ b/crates/forge_tracker/Cargo.toml @@ -13,7 +13,7 @@ serde_json.workspace = true tokio.workspace = true tracing.workspace = true sysinfo.workspace = true -posthog-rs = "0.11.0" +posthog-rs = "0.12.0" async-trait.workspace = true chrono.workspace = true whoami.workspace = true From b5a91a3882288b3691384e8b3ee511e593a5ee1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 05:47:18 +0000 Subject: [PATCH 63/70] chore(deps): update dependency ai to v6.0.204 (#3496) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 92 +++++++---------------------------------------- 1 file changed, 12 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a94497cb2..ed7893d54c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,31 +46,14 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.29", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", - "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.128", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.128.tgz", - "integrity": "sha512-t9Dyqk5thSmIzsNvVTQtZZiO3xqKGkk1hX3+GNpYmro+GuEdW+E6mKFHihb9Y1vCEt3rEyd4pbGUcin4D57FfQ==", + "version": "3.0.130", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.130.tgz", + "integrity": "sha512-qenRdpoYM+2y8Ibj3Y7XngvfcG4NIpejaM+YqAKWXi3/N1qZYeIelrm19jxhIwQW0W/g7WUz0L2Agl7+FnwrQA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28", + "@ai-sdk/provider-utils": "4.0.29", "@vercel/oidc": "3.2.0" }, "engines": { @@ -116,40 +99,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.29", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", - "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.29", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", - "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/openai-compatible": { "version": "2.0.50", "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.50.tgz", @@ -166,23 +115,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { - "version": "4.0.29", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", - "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/provider": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz", @@ -196,9 +128,9 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.28", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.28.tgz", - "integrity": "sha512-nCtGJY43Kqwoyk0rYLj80a3eyXxcaWwyu+lYZDHgY+U6JbYYyvRdRTSy+XCDIB91rFH0Ul0eMDHmDr+YMYtrSw==", + "version": "4.0.29", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.29.tgz", + "integrity": "sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -775,14 +707,14 @@ } }, "node_modules/ai": { - "version": "6.0.202", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.202.tgz", - "integrity": "sha512-14O7uyHVa5lXyt1RzduRqM1zwOnLBN+EaE+btWiP2R7GLHd5oh+Z9uaebR785V/Y2hXlxvuQGsw2Gj6iHpkxsg==", + "version": "6.0.204", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.204.tgz", + "integrity": "sha512-SudB8rUwaVhpWF8+qTJcxUptXPIdN9rWMknzTT3WbKa2QwiGRshyepFKNkDILWm882LgqlEyRZgKhNT14j0jpQ==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.128", + "@ai-sdk/gateway": "3.0.130", "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.28", + "@ai-sdk/provider-utils": "4.0.29", "@opentelemetry/api": "^1.9.0" }, "engines": { From 05e448b929fb8d6bc9cf37993e8b7635640a22e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 10:36:02 +0000 Subject: [PATCH 64/70] chore(deps): update rust crate aws-sdk-bedrockruntime to v1.134.0 (#3499) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c73564ebac..88e47dd3a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.4" +version = "1.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" +checksum = "6c9b9de216a988dd54b754a82a7660cfe14cee4f6782ae4524470972fa0ccb39" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "aws-sdk-bedrockruntime" -version = "1.133.0" +version = "1.134.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76314880945928a4ee3956e92af451ef29ef04b734d008bb94b11158a60a1034" +checksum = "09525553211416fd3c18ead2dd6a29908dcdeb1a032809a23417e7ab848dc23e" dependencies = [ "arc-swap", "aws-credential-types", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.20" +version = "0.60.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +checksum = "78d8391e65fcea47c586a22e1a41f173b38615b112b2c6b7a44e80cec3e6b706" dependencies = [ "aws-smithy-types", "bytes", @@ -1076,7 +1076,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1828,7 +1828,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2001,7 +2001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3472,7 +3472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.17.0", + "hashbrown 0.16.1", ] [[package]] @@ -4907,7 +4907,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5634,7 +5634,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7213,7 +7213,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7293,7 +7293,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8231,7 +8231,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8294,7 +8294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9396,7 +9396,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From 30633800fd233a7efd3202b453a8a307a6138b8f Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Sat, 13 Jun 2026 18:09:33 +0530 Subject: [PATCH 65/70] fix(provider): add missing fireworks models in provider.json (#3504) --- crates/forge_repo/src/provider/provider.json | 70 ++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index 4249a08782..89f52b0df5 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -3638,6 +3638,76 @@ "supports_parallel_tool_calls": true, "supports_reasoning": true, "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/deepseek-v4-flash", + "name": "DeepSeek V4 Flash", + "description": "DeepSeek V4 Flash model", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/deepseek-v4-pro", + "name": "DeepSeek V4 Pro", + "description": "DeepSeek V4 Pro model", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/glm-5p1", + "name": "GLM 5.1", + "description": "GLM 5.1 model", + "context_length": 202800, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/kimi-k2p7-code", + "name": "Kimi K2.7 Code", + "description": "Kimi K2.7 Code model", + "context_length": 262000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, + { + "id": "accounts/fireworks/models/minimax-m2p7", + "name": "MiniMax-M2.7", + "description": "MiniMax-M2.7 model", + "context_length": 196608, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/minimax-m3", + "name": "MiniMax-M3", + "description": "MiniMax-M3 model", + "context_length": 512000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, + { + "id": "accounts/fireworks/models/qwen3p7-plus", + "name": "Qwen 3.7 Plus", + "description": "Qwen 3.7 Plus model", + "context_length": 262144, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] } ] }, From cd597e08cb702f03d344adb5dcef67c69258bdd6 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Sat, 13 Jun 2026 18:25:47 +0530 Subject: [PATCH 66/70] fix(provider): add z.ai glm-5.2 model to provider.json (#3505) --- crates/forge_repo/src/provider/provider.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index 89f52b0df5..2d3353bafd 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -957,6 +957,16 @@ "response_type": "OpenAI", "url": "https://api.z.ai/api/coding/paas/v4/chat/completions", "models": [ + { + "id": "glm-5.2", + "name": "GLM-5.2", + "description": "New flagship model with powerful coding capabilities, 1M context support, and continued strengths in long-horizon tasks", + "context_length": 1000000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text"] + }, { "id": "glm-5.1", "name": "GLM-5.1", From 1ca2237117c3988903171c126d99b3b0e6b3a883 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 12:24:47 +0000 Subject: [PATCH 67/70] chore(deps): update dependency ai to v6.0.205 (#3509) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed7893d54c..71b14d1006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.130", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.130.tgz", - "integrity": "sha512-qenRdpoYM+2y8Ibj3Y7XngvfcG4NIpejaM+YqAKWXi3/N1qZYeIelrm19jxhIwQW0W/g7WUz0L2Agl7+FnwrQA==", + "version": "3.0.131", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.131.tgz", + "integrity": "sha512-CnjOZdywQaUnCyZ0N5wVNm7Sm63+NeHDVZQJKFX2IDq+t03SLwiiuoi3ILTLPlM+YSOhkQ/pvIDoR4qa98Zp5A==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "3.0.10", @@ -707,12 +707,12 @@ } }, "node_modules/ai": { - "version": "6.0.204", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.204.tgz", - "integrity": "sha512-SudB8rUwaVhpWF8+qTJcxUptXPIdN9rWMknzTT3WbKa2QwiGRshyepFKNkDILWm882LgqlEyRZgKhNT14j0jpQ==", + "version": "6.0.205", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.205.tgz", + "integrity": "sha512-F4akEGF41UdgJO3L4v+D5noVD1/czhJy6x0k9R/i1EXfxqrkBh/PdYSgRSLPiGFvrw76dzI8h4w3NYmLrTb8dw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.130", + "@ai-sdk/gateway": "3.0.131", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "@opentelemetry/api": "^1.9.0" From 34cfeb6668fc07669fdcfb6bf8000743720f3ead Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sun, 14 Jun 2026 19:10:59 +0530 Subject: [PATCH 68/70] fix: show only direct conversation initiated by the user via the `:conversation` command (#3510) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- crates/forge_app/src/fmt/todo_fmt.rs | 2 +- crates/forge_main/src/cli.rs | 19 +++- crates/forge_main/src/model.rs | 10 ++ crates/forge_main/src/ui.rs | 114 ++++++++++++++++++++-- shell-plugin/lib/actions/conversation.zsh | 7 ++ shell-plugin/lib/dispatcher.zsh | 3 + 6 files changed, 142 insertions(+), 13 deletions(-) diff --git a/crates/forge_app/src/fmt/todo_fmt.rs b/crates/forge_app/src/fmt/todo_fmt.rs index cee5548184..7b676a21a2 100644 --- a/crates/forge_app/src/fmt/todo_fmt.rs +++ b/crates/forge_app/src/fmt/todo_fmt.rs @@ -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 { diff --git a/crates/forge_main/src/cli.rs b/crates/forge_main/src/cli.rs index a4c859bcd7..1068f77590 100644 --- a/crates/forge_main/src/cli.rs +++ b/crates/forge_main/src/cli.rs @@ -237,6 +237,10 @@ pub enum SelectCommand { /// Initial query text pre-filled in the search box. #[arg(long, short = 'q')] query: Option, + + /// Show child conversations of a parent conversation. + #[arg(long)] + parent: Option, }, /// Select a file interactively with a preview pane. @@ -454,7 +458,11 @@ pub enum ListCommand { /// List conversation history. #[command(alias = "session")] - Conversation, + Conversation { + /// Show child conversations of a parent conversation. + #[arg(long)] + parent: Option, + }, /// List custom commands. #[command(alias = "cmds")] @@ -706,7 +714,6 @@ pub struct ConversationCommandGroup { #[command(subcommand)] pub command: ConversationCommand, } - #[derive(Subcommand, Debug, Clone)] pub enum ConversationCommand { /// List conversation history. @@ -1293,7 +1300,9 @@ mod tests { fn test_list_conversation_command() { let fixture = Cli::parse_from(["forge", "list", "conversation"]); let is_conversation_list = match fixture.subcommands { - Some(TopLevelCommand::List(list)) => matches!(list.command, ListCommand::Conversation), + Some(TopLevelCommand::List(list)) => { + matches!(list.command, ListCommand::Conversation { .. }) + } _ => false, }; assert_eq!(is_conversation_list, true); @@ -1303,7 +1312,9 @@ mod tests { fn test_list_session_alias_command() { let fixture = Cli::parse_from(["forge", "list", "session"]); let is_conversation_list = match fixture.subcommands { - Some(TopLevelCommand::List(list)) => matches!(list.command, ListCommand::Conversation), + Some(TopLevelCommand::List(list)) => { + matches!(list.command, ListCommand::Conversation { .. }) + } _ => false, }; assert_eq!(is_conversation_list, true); diff --git a/crates/forge_main/src/model.rs b/crates/forge_main/src/model.rs index 5ee93c0405..aea647d14e 100644 --- a/crates/forge_main/src/model.rs +++ b/crates/forge_main/src/model.rs @@ -114,6 +114,8 @@ impl ForgeCommandManager { | "logout" | "retry" | "conversations" + | "conversation-tree" + | "ct" | "list" | "commit" | "rename" @@ -650,6 +652,13 @@ pub enum AppCommand { id: Option, }, + /// Show nested conversations spawned by the current conversation + #[strum(props( + usage = "Show nested conversations spawned by the current conversation [alias: ct]" + ))] + #[command(name = "conversation-tree", alias = "ct")] + ConversationTree, + /// Delete a conversation permanently #[strum(props(usage = "Delete a conversation permanently"))] #[command(skip)] @@ -716,6 +725,7 @@ impl AppCommand { AppCommand::Logout => "logout", AppCommand::Retry => "retry", AppCommand::Conversations { .. } => "conversation", + AppCommand::ConversationTree => "conversation-tree", AppCommand::Delete => "delete", AppCommand::Rename { .. } => "rename", AppCommand::AgentSwitch(agent_id) => agent_id, diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 59a1057328..a517907b24 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -501,8 +501,53 @@ impl A + Send + Sync> UI ListCommand::Mcp => { self.on_show_mcp_servers(porcelain).await?; } - ListCommand::Conversation => { - self.on_show_conversations(porcelain).await?; + ListCommand::Conversation { parent } => { + if let Some(parent_id) = parent { + let parent_conv = self.validate_conversation_exists(&parent_id).await?; + let children = self.fetch_related_conversations(&parent_conv).await; + + if children.is_empty() { + self.writeln_title(TitleFormat::info( + "No child conversations found.", + ))?; + } else { + let mut info = Info::new(); + for conv in children.into_iter() { + let title = conv + .title + .as_deref() + .map(|t| t.to_string()) + .unwrap_or_else(|| markers::EMPTY.to_string()); + + let duration = chrono::Utc::now().signed_duration_since( + conv.metadata + .updated_at + .unwrap_or(conv.metadata.created_at), + ); + let duration = std::time::Duration::from_secs( + (duration.num_minutes() * 60).max(0) as u64, + ); + let time_ago = if duration.is_zero() { + "now".to_string() + } else { + format!("{} ago", humantime::format_duration(duration)) + }; + + info = info + .add_title(conv.id) + .add_key_value("Title", title) + .add_key_value("Updated", time_ago); + } + + let porcelain = Porcelain::from(&info) + .drop_col(3) + .truncate(1, 60) + .uppercase_headers(); + self.writeln(porcelain)?; + } + } else { + self.on_show_conversations(porcelain).await?; + } } ListCommand::Cmd => { self.on_show_custom_commands(porcelain).await?; @@ -835,10 +880,16 @@ impl A + Send + Sync> UI self.select_row_output("Command", query.clone(), rows)?; } } - SelectCommand::Conversation { query } => { - let max_conversations = self.config.max_conversations; - let conversations = - self.api.get_conversations(Some(max_conversations)).await?; + SelectCommand::Conversation { query, parent } => { + let conversations = if let Some(parent_id) = parent { + let parent_conv = self.validate_conversation_exists(parent_id).await?; + self.fetch_related_conversations(&parent_conv).await + } else { + let max_conversations = self.config.max_conversations; + let conversations = + self.api.get_conversations(Some(max_conversations)).await?; + Self::user_initiated_conversations(conversations) + }; if !conversations.is_empty() && let Some(conversation) = ConversationSelector::select_conversation( @@ -955,7 +1006,6 @@ impl A + Send + Sync> UI )))?; } } - Ok(()) } @@ -1998,6 +2048,7 @@ impl A + Send + Sync> UI self.spinner.start(Some("Loading Conversations"))?; let max_conversations = self.config.max_conversations; let conversations = self.api.get_conversations(Some(max_conversations)).await?; + let conversations = Self::user_initiated_conversations(conversations); self.spinner.stop(None)?; if conversations.is_empty() { @@ -2035,6 +2086,7 @@ impl A + Send + Sync> UI async fn on_show_conversations(&mut self, porcelain: bool) -> anyhow::Result<()> { let max_conversations = self.config.max_conversations; let conversations = self.api.get_conversations(Some(max_conversations)).await?; + let conversations = Self::user_initiated_conversations(conversations); if conversations.is_empty() { return Ok(()); @@ -2086,6 +2138,25 @@ impl A + Send + Sync> UI Ok(()) } + fn user_initiated_conversations(conversations: Vec) -> Vec { + let related_ids: HashSet = conversations + .iter() + .flat_map(Conversation::related_conversation_ids) + .collect(); + + conversations + .into_iter() + .filter(|conversation| { + conversation + .context + .as_ref() + .and_then(|context| context.initiator.as_deref()) + .is_none_or(|initiator| initiator == "user") + && !related_ids.contains(&conversation.id) + }) + .collect() + } + async fn on_command(&mut self, command: AppCommand) -> anyhow::Result { match command { AppCommand::Conversations { id } => { @@ -2104,6 +2175,33 @@ impl A + Send + Sync> UI self.list_conversations().await?; } } + AppCommand::ConversationTree => { + let conversation_id = self + .state + .conversation_id + .ok_or_else(|| anyhow::anyhow!("No active conversation"))?; + let parent = self.validate_conversation_exists(&conversation_id).await?; + let children = self.fetch_related_conversations(&parent).await; + + if children.is_empty() { + self.writeln_title(TitleFormat::info("No child conversations found."))?; + } else if let Some(conversation) = ConversationSelector::select_conversation( + &children, + self.state.conversation_id, + None, + ) + .await? + { + let conversation_id = conversation.id; + self.state.conversation_id = Some(conversation_id); + self.on_show_last_message(conversation, false).await?; + self.writeln_title(TitleFormat::info(format!( + "Switched to conversation {}", + conversation_id.into_string().bold() + )))?; + self.on_info(false, Some(conversation_id)).await?; + } + } AppCommand::Compact => { self.spinner.start(Some("Compacting"))?; self.on_compaction().await?; diff --git a/shell-plugin/lib/actions/conversation.zsh b/shell-plugin/lib/actions/conversation.zsh index 4a31c8bbf1..132497cbfd 100644 --- a/shell-plugin/lib/actions/conversation.zsh +++ b/shell-plugin/lib/actions/conversation.zsh @@ -6,6 +6,7 @@ # - :conversation - List and switch conversations (with interactive picker) # - :conversation - Switch to specific conversation by ID # - :conversation - - Toggle between current and previous conversation (like cd -) +# - :conversation-tree - Show nested conversations spawned by current conversation # - :clone - Clone current or selected conversation # - :clone - Clone specific conversation by ID # - :copy - Copy last assistant message to OS clipboard as raw markdown @@ -115,6 +116,12 @@ function _forge_action_conversation() { fi } + +# Action handler: Show nested conversations spawned by current conversation +function _forge_action_conversation_tree() { + _forge_select conversation --parent "$_FORGE_CONVERSATION_ID" +} + # Action handler: Clone conversation function _forge_action_clone() { local input_text="$1" diff --git a/shell-plugin/lib/dispatcher.zsh b/shell-plugin/lib/dispatcher.zsh index 0be98eb808..1a0575c06a 100644 --- a/shell-plugin/lib/dispatcher.zsh +++ b/shell-plugin/lib/dispatcher.zsh @@ -169,6 +169,9 @@ function forge-accept-line() { conversation|c) _forge_action_conversation "$input_text" ;; + conversation-tree|ct) + _forge_action_conversation_tree + ;; config-model|cm) _forge_action_model "$input_text" ;; From f3beee1a7c9c81545fac532769b210cc9e6446c6 Mon Sep 17 00:00:00 2001 From: Phenotype Agent Date: Sun, 14 Jun 2026 14:13:56 -0700 Subject: [PATCH 69/70] feat: add parent_id, source, FTS5, subagent hiding, and loop commands --- crates/forge_api/src/api.rs | 19 ++ crates/forge_api/src/forge_api.rs | 34 ++++ crates/forge_app/src/agent_executor.rs | 9 +- crates/forge_app/src/orch.rs | 10 +- crates/forge_app/src/services.rs | 47 +++++ crates/forge_app/src/tool_registry.rs | 8 +- crates/forge_domain/src/conversation.rs | 4 + crates/forge_domain/src/repo.rs | 38 ++++ crates/forge_domain/src/tools/call/context.rs | 40 +++- .../forge_main/src/conversation_selector.rs | 111 +++++------- crates/forge_main/src/model.rs | 39 ++++ crates/forge_main/src/state.rs | 20 +- crates/forge_main/src/ui.rs | 171 +++++++++++++++++- .../src/conversation/conversation_record.rs | 6 + .../src/conversation/conversation_repo.rs | 89 +++++++++ .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 2 + .../up.sql | 4 + .../down.sql | 4 + .../up.sql | 46 +++++ crates/forge_repo/src/database/schema.rs | 2 + crates/forge_repo/src/forge_repo.rs | 33 +++- crates/forge_services/src/conversation.rs | 28 +++ 24 files changed, 687 insertions(+), 79 deletions(-) create mode 100644 crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/down.sql create mode 100644 crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/up.sql create mode 100644 crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/down.sql create mode 100644 crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/up.sql create mode 100644 crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/down.sql create mode 100644 crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/up.sql diff --git a/crates/forge_api/src/api.rs b/crates/forge_api/src/api.rs index 5a2a5217fe..87a52054a8 100644 --- a/crates/forge_api/src/api.rs +++ b/crates/forge_api/src/api.rs @@ -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>; + + /// Lists all top-level (parent) conversations, excluding subagents + async fn get_parent_conversations( + &self, + limit: Option, + ) -> Result>; + + /// Lists conversations by source (e.g., "interactive", "headless", "forge-p") + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> Result>; + /// Renames a conversation by setting its title /// /// # Arguments diff --git a/crates/forge_api/src/forge_api.rs b/crates/forge_api/src/forge_api.rs index a056705761..67fde17aac 100644 --- a/crates/forge_api/src/forge_api.rs +++ b/crates/forge_api/src/forge_api.rs @@ -192,6 +192,40 @@ impl< self.services.delete_conversation(conversation_id).await } + async fn get_subagents( + &self, + parent_id: &ConversationId, + ) -> Result> { + Ok(self + .services + .get_conversations_by_parent(parent_id) + .await? + .unwrap_or_default()) + } + + async fn get_parent_conversations( + &self, + limit: Option, + ) -> Result> { + Ok(self + .services + .get_parent_conversations(limit) + .await? + .unwrap_or_default()) + } + + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> Result> { + Ok(self + .services + .get_conversations_by_source(source, limit) + .await? + .unwrap_or_default()) + } + async fn rename_conversation( &self, conversation_id: &ConversationId, diff --git a/crates/forge_app/src/agent_executor.rs b/crates/forge_app/src/agent_executor.rs index fe92b7c7d4..36c22795a9 100644 --- a/crates/forge_app/src/agent_executor.rs +++ b/crates/forge_app/src/agent_executor.rs @@ -45,6 +45,7 @@ impl> AgentEx task: String, ctx: &ToolCallContext, conversation_id: Option, + parent_id: Option, ) -> anyhow::Result { ctx.send_tool_input( TitleFormat::debug(format!( @@ -66,9 +67,15 @@ impl> 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()) diff --git a/crates/forge_app/src/orch.rs b/crates/forge_app/src/orch.rs index e63ce75f1e..cf1d01a67e 100644 --- a/crates/forge_app/src/orch.rs +++ b/crates/forge_app/src/orch.rs @@ -262,8 +262,14 @@ impl> 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 diff --git a/crates/forge_app/src/services.rs b/crates/forge_app/src/services.rs index a64aaa92bb..d6708a219e 100644 --- a/crates/forge_app/src/services.rs +++ b/crates/forge_app/src/services.rs @@ -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>>; + + /// Find all top-level conversations (those without a parent) + async fn get_parent_conversations( + &self, + limit: Option, + ) -> anyhow::Result>>; + + /// Find conversations by source (e.g., "interactive", "headless", "forge-p") + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> anyhow::Result>>; } #[async_trait::async_trait] @@ -634,6 +653,34 @@ impl ConversationService for I { .delete_conversation(conversation_id) .await } + + async fn get_conversations_by_parent( + &self, + parent_id: &ConversationId, + ) -> anyhow::Result>> { + self.conversation_service() + .get_conversations_by_parent(parent_id) + .await + } + + async fn get_parent_conversations( + &self, + limit: Option, + ) -> anyhow::Result>> { + self.conversation_service() + .get_parent_conversations(limit) + .await + } + + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> anyhow::Result>> { + self.conversation_service() + .get_conversations_by_source(source, limit) + .await + } } #[async_trait::async_trait] impl ProviderService for I { diff --git a/crates/forge_app/src/tool_registry.rs b/crates/forge_app/src/tool_registry.rs index dbfff3da06..f3ff6a541f 100644 --- a/crates/forge_app/src/tool_registry.rs +++ b/crates/forge_app/src/tool_registry.rs @@ -110,6 +110,7 @@ impl> 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)) @@ -120,9 +121,10 @@ impl> 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 } })) @@ -169,13 +171,15 @@ impl> 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 } })) diff --git a/crates/forge_domain/src/conversation.rs b/crates/forge_domain/src/conversation.rs index c0bde6e4e8..c9c9d4321c 100644 --- a/crates/forge_domain/src/conversation.rs +++ b/crates/forge_domain/src/conversation.rs @@ -46,6 +46,8 @@ pub struct Conversation { pub context: Option, pub metrics: Metrics, pub metadata: MetaData, + pub parent_id: Option, + pub source: Option, } #[derive(Debug, Setters, Serialize, Deserialize, Clone)] @@ -71,6 +73,8 @@ impl Conversation { metadata: MetaData::new(created_at), title: None, context: None, + parent_id: None, + source: None, } } /// Creates a new conversation with a new conversation ID. diff --git a/crates/forge_domain/src/repo.rs b/crates/forge_domain/src/repo.rs index 558d54244a..ef12f814d6 100644 --- a/crates/forge_domain/src/repo.rs +++ b/crates/forge_domain/src/repo.rs @@ -93,6 +93,44 @@ pub trait ConversationRepository: Send + Sync { /// # Errors /// Returns an error if the operation fails async fn delete_conversation(&self, conversation_id: &ConversationId) -> Result<()>; + + /// Retrieves all conversations that have the given parent_id + /// + /// # Arguments + /// * `parent_id` - The ID of the parent conversation + /// + /// # Errors + /// Returns an error if the operation fails + async fn get_conversations_by_parent( + &self, + parent_id: &ConversationId, + ) -> Result>>; + + /// Retrieves all top-level conversations (those without a parent_id) + /// + /// # Arguments + /// * `limit` - Optional maximum number of conversations to retrieve + /// + /// # Errors + /// Returns an error if the operation fails + async fn get_parent_conversations( + &self, + limit: Option, + ) -> Result>>; + + /// Retrieves conversations by source (e.g., "interactive", "headless", "forge-p") + /// + /// # Arguments + /// * `source` - The source to filter by + /// * `limit` - Optional maximum number of conversations to retrieve + /// + /// # Errors + /// Returns an error if the operation fails + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> Result>>; } #[async_trait::async_trait] diff --git a/crates/forge_domain/src/tools/call/context.rs b/crates/forge_domain/src/tools/call/context.rs index b9625e7fb6..d098bd8005 100644 --- a/crates/forge_domain/src/tools/call/context.rs +++ b/crates/forge_domain/src/tools/call/context.rs @@ -2,19 +2,25 @@ use std::sync::{Arc, Mutex}; use derive_setters::Setters; -use crate::{ArcSender, ChatResponse, Metrics, TitleFormat, Todo, TodoItem}; +use crate::{ArcSender, ChatResponse, ConversationId, Metrics, TitleFormat, Todo, TodoItem}; /// Provides additional context for tool calls. #[derive(Debug, Clone, Setters)] pub struct ToolCallContext { sender: Option, metrics: Arc>, + #[setters(skip)] + conversation_id: Option, + #[setters(skip)] + parent_id: Option, + #[setters(skip)] + source: Option, } impl ToolCallContext { /// Creates a new ToolCallContext with default values pub fn new(metrics: Metrics) -> Self { - Self { sender: None, metrics: Arc::new(Mutex::new(metrics)) } + Self { sender: None, metrics: Arc::new(Mutex::new(metrics)), conversation_id: None, parent_id: None, source: None } } /// Send a message through the sender if available @@ -59,6 +65,36 @@ impl ToolCallContext { f(&mut metrics) } + /// Returns the conversation ID associated with this tool call context, if any. + pub fn conversation_id(&self) -> Option { + self.conversation_id + } + + /// Sets the conversation ID for this tool call context. + pub fn set_conversation_id(&mut self, id: Option) { + self.conversation_id = id; + } + + /// Returns the parent conversation ID associated with this tool call context, if any. + pub fn parent_id(&self) -> Option { + self.parent_id + } + + /// Sets the parent conversation ID for this tool call context. + pub fn set_parent_id(&mut self, id: Option) { + self.parent_id = id; + } + + /// Returns the source associated with this tool call context, if any. + pub fn source(&self) -> Option<&str> { + self.source.as_deref() + } + + /// Sets the source for this tool call context. + pub fn set_source(&mut self, source: Option) { + self.source = source; + } + /// Returns all known todos (active and historical completed todos). /// /// # Errors diff --git a/crates/forge_main/src/conversation_selector.rs b/crates/forge_main/src/conversation_selector.rs index ea755621e6..fe9ef4cb7f 100644 --- a/crates/forge_main/src/conversation_selector.rs +++ b/crates/forge_main/src/conversation_selector.rs @@ -5,8 +5,43 @@ use forge_domain::ConversationId; use forge_select::{ForgeWidget, PreviewLayout, PreviewPlacement, SelectRow}; use crate::display_constants::markers; -use crate::info::Info; -use crate::porcelain::Porcelain; + +/// Fast display format for a conversation row in the selector. +/// Avoids the Info/Porcelain overhead for large conversation lists. +struct FastConversationRow<'a> { + conv: &'a Conversation, + now: chrono::DateTime, +} + +impl<'a> FastConversationRow<'a> { + fn new(conv: &'a Conversation, now: chrono::DateTime) -> Self { + Self { conv, now } + } +} + +impl<'a> std::fmt::Display for FastConversationRow<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let id = self.conv.id.to_string(); + let short_id = &id[..8.min(id.len())]; + let title = self.conv + .title + .as_deref() + .unwrap_or(markers::EMPTY); + let duration = self.now.signed_duration_since( + self.conv.metadata.updated_at.unwrap_or(self.conv.metadata.created_at), + ); + let time_ago = if duration.num_seconds() < 60 { + "now".to_string() + } else if duration.num_minutes() < 60 { + format!("{}m ago", duration.num_minutes()) + } else if duration.num_hours() < 24 { + format!("{}h ago", duration.num_hours()) + } else { + format!("{}d ago", duration.num_days()) + }; + write!(f, "[{}] {} ({})", short_id, title, time_ago) + } +} /// Logic for selecting conversations from a list pub struct ConversationSelector; @@ -39,69 +74,21 @@ impl ConversationSelector { return Ok(None); } - // Build Info structure for display + // Build SelectRow items directly — no Info/Porcelain overhead. + // This keeps the selector fast even with thousands of conversations. let now = Utc::now(); - let mut info = Info::new(); + let mut rows: Vec = Vec::with_capacity(valid_conversations.len() + 1); + rows.push(SelectRow::header("ID Title Updated")); for conv in &valid_conversations { - let title = conv - .title - .as_deref() - .map(|t| t.to_string()) - .unwrap_or_else(|| markers::EMPTY.to_string()); - - let duration = now.signed_duration_since( - conv.metadata.updated_at.unwrap_or(conv.metadata.created_at), - ); - let duration = - std::time::Duration::from_secs((duration.num_minutes() * 60).max(0) as u64); - let time_ago = if duration.is_zero() { - "now".to_string() - } else { - format!("{} ago", humantime::format_duration(duration)) - }; - - info = info - .add_title(conv.id) - .add_key_value("Title", title) - .add_key_value("Updated", time_ago); - } - - // Convert to porcelain, drop the UUID title column (col 0), truncate the - // Title column for display, uppercase headers - let porcelain_output = Porcelain::from(&info) - .drop_col(0) - .truncate(0, 60) - .uppercase_headers(); - let porcelain_str = porcelain_output.to_string(); - - let all_lines: Vec<&str> = porcelain_str.lines().collect(); - if all_lines.is_empty() { - return Ok(None); - } - - // Build SelectRow items for the shared Rust selector UI. - // Each row stores the UUID in `fields[0]` so that `{1}` in the preview - // command resolves to the conversation ID. The `raw` field is what gets - // returned on selection (the UUID). - let mut rows: Vec = Vec::with_capacity(all_lines.len()); - - // Header row (non-selectable via header_lines=1) - if let Some(header) = all_lines.first() { - rows.push(SelectRow::header(header.to_string())); - } - - // Data rows: each maps to a conversation - for (i, line) in all_lines.iter().skip(1).enumerate() { - if let Some(conv) = valid_conversations.get(i) { - let uuid = conv.id.to_string(); - rows.push(SelectRow { - raw: uuid.clone(), - display: line.to_string(), - search: line.to_string(), - fields: vec![uuid], - }); - } + let uuid = conv.id.to_string(); + let display = FastConversationRow::new(conv, now).to_string(); + rows.push(SelectRow { + raw: uuid.clone(), + display: display.clone(), + search: display, + fields: vec![uuid], + }); } // Build a lookup map from UUID to Conversation for the result diff --git a/crates/forge_main/src/model.rs b/crates/forge_main/src/model.rs index 5ee93c0405..3315bca1d7 100644 --- a/crates/forge_main/src/model.rs +++ b/crates/forge_main/src/model.rs @@ -153,6 +153,14 @@ impl ForgeCommandManager { | "sync-info" | "workspace-init" | "sync-init" + | "subagents" + | "sa" + | "goal" + | "g" + | "loop" + | "l" + | "parent" + | "p" ) } @@ -650,6 +658,33 @@ pub enum AppCommand { id: Option, }, + /// List all subagent conversations for the current parent session + #[strum(props(usage = "List subagents for the current session"))] + #[command(name = "subagents", aliases = ["sa"])] + Subagents, + + /// Set or view the current looping goal + #[strum(props(usage = "Set or view the current goal. Usage: :goal "))] + #[command(alias = "g")] + Goal { + /// Goal description (optional — shows current goal if absent) + #[arg(trailing_var_arg = true, num_args = 0..)] + description: Vec, + }, + + /// Toggle looping mode on/off + #[strum(props(usage = "Toggle looping mode. Usage: :loop [on|off]"))] + #[command(alias = "l")] + Loop { + /// Loop state (optional — toggles if absent) + state: Option, + }, + + /// Jump to the parent conversation of the current subagent session + #[strum(props(usage = "Jump to the parent conversation of the current session"))] + #[command(alias = "p")] + Parent, + /// Delete a conversation permanently #[strum(props(usage = "Delete a conversation permanently"))] #[command(skip)] @@ -716,6 +751,10 @@ impl AppCommand { AppCommand::Logout => "logout", AppCommand::Retry => "retry", AppCommand::Conversations { .. } => "conversation", + AppCommand::Subagents => "subagents", + AppCommand::Goal { .. } => "goal", + AppCommand::Loop { .. } => "loop", + AppCommand::Parent => "parent", AppCommand::Delete => "delete", AppCommand::Rename { .. } => "rename", AppCommand::AgentSwitch(agent_id) => agent_id, diff --git a/crates/forge_main/src/state.rs b/crates/forge_main/src/state.rs index 61513caf12..1556919b99 100644 --- a/crates/forge_main/src/state.rs +++ b/crates/forge_main/src/state.rs @@ -1,19 +1,35 @@ use std::path::PathBuf; +use std::time::Instant; use derive_setters::Setters; use forge_api::{ConversationId, Environment}; //TODO: UIState and ForgePrompt seem like the same thing and can be merged /// State information for the UI -#[derive(Debug, Default, Clone, Setters)] +#[derive(Debug, Clone, Setters)] #[setters(strip_option)] pub struct UIState { pub cwd: PathBuf, pub conversation_id: Option, + pub goal: Option, + pub loop_enabled: bool, + pub last_activity: Instant, +} + +impl Default for UIState { + fn default() -> Self { + Self { + cwd: PathBuf::from("."), + conversation_id: None, + goal: None, + loop_enabled: false, + last_activity: Instant::now(), + } + } } impl UIState { pub fn new(env: Environment) -> Self { - Self { cwd: env.cwd, conversation_id: Default::default() } + Self { cwd: env.cwd, conversation_id: Default::default(), goal: None, loop_enabled: false, last_activity: Instant::now() } } } diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 59a1057328..3aa24aa914 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -56,6 +56,20 @@ use crate::{TRACKER, banner, tracker}; // File-specific constants const MISSING_AGENT_TITLE: &str = ""; +/// Detects the source of the conversation based on CLI arguments. +/// Returns "interactive", "forge-p", "headless", or the subcommand name. +fn detect_source(cli: &Cli) -> String { + if cli.subcommands.is_some() { + "subcommand".to_string() + } else if cli.prompt.is_some() { + "forge-p".to_string() + } else if cli.piped_input.is_some() { + "headless".to_string() + } else { + "interactive".to_string() + } +} + /// Conversation dump format used by the /dump command #[derive(Debug, serde::Deserialize, serde::Serialize)] struct ConversationDump { @@ -838,7 +852,7 @@ impl A + Send + Sync> UI SelectCommand::Conversation { query } => { let max_conversations = self.config.max_conversations; let conversations = - self.api.get_conversations(Some(max_conversations)).await?; + self.api.get_parent_conversations(Some(max_conversations)).await?; if !conversations.is_empty() && let Some(conversation) = ConversationSelector::select_conversation( @@ -1997,7 +2011,7 @@ impl A + Send + Sync> UI async fn list_conversations(&mut self) -> anyhow::Result<()> { self.spinner.start(Some("Loading Conversations"))?; let max_conversations = self.config.max_conversations; - let conversations = self.api.get_conversations(Some(max_conversations)).await?; + let conversations = self.api.get_parent_conversations(Some(max_conversations)).await?; self.spinner.stop(None)?; if conversations.is_empty() { @@ -2032,9 +2046,56 @@ impl A + Send + Sync> UI Ok(()) } + async fn list_subagents(&mut self) -> anyhow::Result<()> { + let parent_id = match self.state.conversation_id { + Some(id) => id, + None => { + self.writeln_title(TitleFormat::error( + "No active session. Start a conversation first.", + ))?; + return Ok(()); + } + }; + + self.spinner.start(Some("Loading Subagents"))?; + let conversations = self.api.get_subagents(&parent_id).await?; + self.spinner.stop(None)?; + + if conversations.is_empty() { + self.writeln_title(TitleFormat::info( + "No subagents found for this session.", + ))?; + return Ok(()); + } + + if let Some(conversation) = ConversationSelector::select_conversation( + &conversations, + self.state.conversation_id, + None, + ) + .await? + { + let conversation_id = conversation.id; + self.state.conversation_id = Some(conversation_id); + + // Show conversation content + self.on_show_last_message(conversation, false).await?; + + // Print log about conversation switching + self.writeln_title(TitleFormat::info(format!( + "Switched to subagent {}", + conversation_id.into_string().bold() + )))?; + + // Show conversation info + self.on_info(false, Some(conversation_id)).await?; + } + Ok(()) + } + async fn on_show_conversations(&mut self, porcelain: bool) -> anyhow::Result<()> { let max_conversations = self.config.max_conversations; - let conversations = self.api.get_conversations(Some(max_conversations)).await?; + let conversations = self.api.get_parent_conversations(Some(max_conversations)).await?; if conversations.is_empty() { return Ok(()); @@ -2086,6 +2147,80 @@ impl A + Send + Sync> UI Ok(()) } + async fn handle_goal(&mut self, description: Option) -> anyhow::Result<()> { + if let Some(desc) = description { + self.state.goal = Some(desc.clone()); + self.writeln_title(TitleFormat::info(format!( + "Goal set: {}", + desc.bold() + )))?; + } else { + match &self.state.goal { + Some(goal) => { + self.writeln_title(TitleFormat::info(format!( + "Current goal: {}", + goal.bold() + )))?; + } + None => { + self.writeln_title(TitleFormat::info("No goal set. Usage: :goal "))?; + } + } + } + Ok(()) + } + + async fn handle_loop(&mut self, state: Option) -> anyhow::Result<()> { + if let Some(s) = state { + let enabled = s.trim().eq_ignore_ascii_case("on"); + self.state.loop_enabled = enabled; + self.writeln_title(TitleFormat::info(format!( + "Loop mode {}", + if enabled { "enabled".bold() } else { "disabled".bold() } + )))?; + } else { + self.state.loop_enabled = !self.state.loop_enabled; + self.writeln_title(TitleFormat::info(format!( + "Loop mode {}", + if self.state.loop_enabled { "enabled".bold() } else { "disabled".bold() } + )))?; + } + Ok(()) + } + + async fn handle_parent(&mut self) -> anyhow::Result<()> { + let conversation_id = match self.state.conversation_id { + Some(id) => id, + None => { + self.writeln_title(TitleFormat::error( + "No active session. Start a conversation first.", + ))?; + return Ok(()); + } + }; + + let conversation = self.validate_conversation_exists(&conversation_id).await?; + + match conversation.parent_id { + Some(parent_id) => { + let parent = self.validate_conversation_exists(&parent_id).await?; + self.state.conversation_id = Some(parent_id); + self.on_show_last_message(parent, false).await?; + self.writeln_title(TitleFormat::info(format!( + "Switched to parent conversation {}", + parent_id.into_string().bold() + )))?; + self.on_info(false, Some(parent_id)).await?; + } + None => { + self.writeln_title(TitleFormat::info( + "This is a root conversation — it has no parent.", + ))?; + } + } + Ok(()) + } + async fn on_command(&mut self, command: AppCommand) -> anyhow::Result { match command { AppCommand::Conversations { id } => { @@ -2104,6 +2239,23 @@ impl A + Send + Sync> UI self.list_conversations().await?; } } + AppCommand::Subagents => { + self.list_subagents().await?; + } + AppCommand::Goal { description } => { + let desc = if description.is_empty() { + None + } else { + Some(description.join(" ").trim().to_string()) + }; + self.handle_goal(desc).await?; + } + AppCommand::Loop { state } => { + self.handle_loop(state).await?; + } + AppCommand::Parent => { + self.handle_parent().await?; + } AppCommand::Compact => { self.spinner.start(Some("Compacting"))?; self.on_compaction().await?; @@ -2300,6 +2452,7 @@ impl A + Send + Sync> UI } } + self.state.last_activity = std::time::Instant::now(); Ok(false) } async fn on_compaction(&mut self) -> Result<(), anyhow::Error> { @@ -2574,7 +2727,7 @@ impl A + Send + Sync> UI // Show conversation picker let conversations = self .api - .get_conversations(Some(self.config.max_conversations)) + .get_parent_conversations(Some(self.config.max_conversations)) .await?; if conversations.is_empty() { @@ -2653,7 +2806,7 @@ impl A + Send + Sync> UI // Interactive: show picker then prompt for new name let conversations = self .api - .get_conversations(Some(self.config.max_conversations)) + .get_parent_conversations(Some(self.config.max_conversations)) .await?; if conversations.is_empty() { @@ -3734,7 +3887,8 @@ impl A + Send + Sync> UI // Check if conversation exists, if not create it if self.api.conversation(&id).await?.is_none() { - let conversation = Conversation::new(id); + let mut conversation = Conversation::new(id); + conversation.source = Some(detect_source(&self.cli)); self.api.upsert_conversation(conversation).await?; is_new = true; } @@ -3754,10 +3908,12 @@ impl A + Send + Sync> UI }; let id = conversation.id; + conversation.source = Some(detect_source(&self.cli)); self.api.upsert_conversation(conversation).await?; id } else { - let conversation = Conversation::generate(); + let mut conversation = Conversation::generate(); + conversation.source = Some(detect_source(&self.cli)); let id = conversation.id; is_new = true; self.api.upsert_conversation(conversation).await?; @@ -3922,6 +4078,7 @@ impl A + Send + Sync> UI writer.finish()?; self.spinner.stop(None)?; self.spinner.reset(); + self.state.last_activity = std::time::Instant::now(); Ok(()) } diff --git a/crates/forge_repo/src/conversation/conversation_record.rs b/crates/forge_repo/src/conversation/conversation_record.rs index 7df99bf5a3..26252f006c 100644 --- a/crates/forge_repo/src/conversation/conversation_record.rs +++ b/crates/forge_repo/src/conversation/conversation_record.rs @@ -949,6 +949,8 @@ pub(super) struct ConversationRecord { pub created_at: chrono::NaiveDateTime, pub updated_at: Option, pub metrics: Option, + pub parent_id: Option, + pub source: Option, } impl ConversationRecord { @@ -975,6 +977,8 @@ impl ConversationRecord { updated_at, workspace_id: workspace_id.id() as i64, metrics, + parent_id: conversation.parent_id.map(|id| id.into_string()), + source: conversation.source.clone(), } } } @@ -1021,6 +1025,8 @@ impl TryFrom for forge_domain::Conversation { .context(context) .title(record.title) .metrics(metrics) + .parent_id(record.parent_id.and_then(|id| ConversationId::parse(id).ok())) + .source(record.source) .metadata( forge_domain::MetaData::new(record.created_at.and_utc()) .updated_at(record.updated_at.map(|updated_at| updated_at.and_utc())), diff --git a/crates/forge_repo/src/conversation/conversation_repo.rs b/crates/forge_repo/src/conversation/conversation_repo.rs index eeef25af71..2cea2b7b00 100644 --- a/crates/forge_repo/src/conversation/conversation_repo.rs +++ b/crates/forge_repo/src/conversation/conversation_repo.rs @@ -56,6 +56,8 @@ impl ConversationRepository for ConversationRepositoryImpl { conversations::context.eq(&record.context), conversations::updated_at.eq(record.updated_at), conversations::metrics.eq(&record.metrics), + conversations::parent_id.eq(&record.parent_id), + conversations::source.eq(&record.source), )) .execute(connection)?; Ok(()) @@ -144,6 +146,93 @@ impl ConversationRepository for ConversationRepositoryImpl { }) .await } + + async fn get_conversations_by_parent( + &self, + parent_id: &ConversationId, + ) -> anyhow::Result>> { + let parent_id = parent_id.into_string(); + self.run_with_connection(move |connection, wid| { + let workspace_id = wid.id() as i64; + let records: Vec = conversations::table + .filter(conversations::workspace_id.eq(&workspace_id)) + .filter(conversations::parent_id.eq(&parent_id)) + .filter(conversations::context.is_not_null()) + .order(conversations::updated_at.desc()) + .load(connection)?; + + if records.is_empty() { + return Ok(None); + } + + let conversations: Result, _> = + records.into_iter().map(Conversation::try_from).collect(); + Ok(Some(conversations?)) + }) + .await + } + + async fn get_parent_conversations( + &self, + limit: Option, + ) -> anyhow::Result>> { + self.run_with_connection(move |connection, wid| { + let workspace_id = wid.id() as i64; + let mut query = conversations::table + .filter(conversations::workspace_id.eq(&workspace_id)) + .filter(conversations::context.is_not_null()) + .filter(conversations::parent_id.is_null()) + .order(conversations::updated_at.desc()) + .into_boxed(); + + if let Some(limit_value) = limit { + query = query.limit(limit_value as i64); + } + + let records: Vec = query.load(connection)?; + + if records.is_empty() { + return Ok(None); + } + + let conversations: Result, _> = + records.into_iter().map(Conversation::try_from).collect(); + Ok(Some(conversations?)) + }) + .await + } + + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> anyhow::Result>> { + let source = source.to_string(); + self.run_with_connection(move |connection, wid| { + let workspace_id = wid.id() as i64; + let mut query = conversations::table + .filter(conversations::workspace_id.eq(&workspace_id)) + .filter(conversations::context.is_not_null()) + .filter(conversations::source.eq(&source)) + .order(conversations::updated_at.desc()) + .into_boxed(); + + if let Some(limit_value) = limit { + query = query.limit(limit_value as i64); + } + + let records: Vec = query.load(connection)?; + + if records.is_empty() { + return Ok(None); + } + + let conversations: Result, _> = + records.into_iter().map(Conversation::try_from).collect(); + Ok(Some(conversations?)) + }) + .await + } } #[cfg(test)] diff --git a/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/down.sql b/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/down.sql new file mode 100644 index 0000000000..890b1a039c --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/down.sql @@ -0,0 +1 @@ +ALTER TABLE conversations DROP COLUMN parent_id; \ No newline at end of file diff --git a/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/up.sql b/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/up.sql new file mode 100644 index 0000000000..06dcbb1116 --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-13-000000_add_parent_id_to_conversations/up.sql @@ -0,0 +1 @@ +ALTER TABLE conversations ADD COLUMN parent_id TEXT DEFAULT NULL; \ No newline at end of file diff --git a/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/down.sql b/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/down.sql new file mode 100644 index 0000000000..88aabc42c1 --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_conversations_source; +ALTER TABLE conversations DROP COLUMN source; diff --git a/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/up.sql b/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/up.sql new file mode 100644 index 0000000000..d994c84cec --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-14-000001_add_source_to_conversations/up.sql @@ -0,0 +1,4 @@ +ALTER TABLE conversations ADD COLUMN source TEXT; + +-- Create index for filtering by source +CREATE INDEX idx_conversations_source ON conversations(source); diff --git a/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/down.sql b/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/down.sql new file mode 100644 index 0000000000..1e91135674 --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/down.sql @@ -0,0 +1,4 @@ +DROP TABLE IF EXISTS conversations_fts; +DROP TRIGGER IF EXISTS conversations_fts_insert; +DROP TRIGGER IF EXISTS conversations_fts_update; +DROP TRIGGER IF EXISTS conversations_fts_delete; diff --git a/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/up.sql b/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/up.sql new file mode 100644 index 0000000000..3b58cb5781 --- /dev/null +++ b/crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/up.sql @@ -0,0 +1,46 @@ +-- Create FTS5 virtual table for conversation search +-- This indexes both title and context content for full-text search +CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5( + conversation_id UNINDEXED, + title, + content, + tokenize='porter' +); + +-- Trigger to insert into FTS5 when a new conversation is created +CREATE TRIGGER IF NOT EXISTS conversations_fts_insert +AFTER INSERT ON conversations +BEGIN + INSERT INTO conversations_fts(conversation_id, title, content) + VALUES ( + NEW.conversation_id, + COALESCE(NEW.title, ''), + COALESCE(NEW.context, '') + ); +END; + +-- Trigger to update FTS5 when a conversation is updated +CREATE TRIGGER IF NOT EXISTS conversations_fts_update +AFTER UPDATE ON conversations +BEGIN + DELETE FROM conversations_fts WHERE conversation_id = OLD.conversation_id; + INSERT INTO conversations_fts(conversation_id, title, content) + VALUES ( + NEW.conversation_id, + COALESCE(NEW.title, ''), + COALESCE(NEW.context, '') + ); +END; + +-- Trigger to delete from FTS5 when a conversation is deleted +CREATE TRIGGER IF NOT EXISTS conversations_fts_delete +AFTER DELETE ON conversations +BEGIN + DELETE FROM conversations_fts WHERE conversation_id = OLD.conversation_id; +END; + +-- Populate the FTS5 table with existing conversations +INSERT INTO conversations_fts(conversation_id, title, content) +SELECT conversation_id, COALESCE(title, ''), COALESCE(context, '') +FROM conversations +WHERE context IS NOT NULL; diff --git a/crates/forge_repo/src/database/schema.rs b/crates/forge_repo/src/database/schema.rs index cfe1bc8e0d..71764b67d7 100644 --- a/crates/forge_repo/src/database/schema.rs +++ b/crates/forge_repo/src/database/schema.rs @@ -9,5 +9,7 @@ diesel::table! { created_at -> Timestamp, updated_at -> Nullable, metrics -> Nullable, + parent_id -> Nullable, + source -> Nullable, } } diff --git a/crates/forge_repo/src/forge_repo.rs b/crates/forge_repo/src/forge_repo.rs index 555758c7b5..9368420fc6 100644 --- a/crates/forge_repo/src/forge_repo.rs +++ b/crates/forge_repo/src/forge_repo.rs @@ -140,7 +140,38 @@ impl ConversationRepository for ForgeRepo { self.conversation_repository.get_last_conversation().await } - async fn delete_conversation(&self, conversation_id: &ConversationId) -> anyhow::Result<()> { + async fn get_conversations_by_parent( + &self, + parent_id: &ConversationId, + ) -> anyhow::Result>> { + self.conversation_repository + .get_conversations_by_parent(parent_id) + .await + } + + async fn get_parent_conversations( + &self, + limit: Option, + ) -> anyhow::Result>> { + self.conversation_repository + .get_parent_conversations(limit) + .await + } + + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> anyhow::Result>> { + self.conversation_repository + .get_conversations_by_source(source, limit) + .await + } + + async fn delete_conversation( + &self, + conversation_id: &ConversationId, + ) -> anyhow::Result<()> { self.conversation_repository .delete_conversation(conversation_id) .await diff --git a/crates/forge_services/src/conversation.rs b/crates/forge_services/src/conversation.rs index adb81e6c11..73d8187c24 100644 --- a/crates/forge_services/src/conversation.rs +++ b/crates/forge_services/src/conversation.rs @@ -66,4 +66,32 @@ impl ConversationService for ForgeConversationService .delete_conversation(conversation_id) .await } + + async fn get_conversations_by_parent( + &self, + parent_id: &ConversationId, + ) -> Result>> { + self.conversation_repository + .get_conversations_by_parent(parent_id) + .await + } + + async fn get_parent_conversations( + &self, + limit: Option, + ) -> Result>> { + self.conversation_repository + .get_parent_conversations(limit) + .await + } + + async fn get_conversations_by_source( + &self, + source: &str, + limit: Option, + ) -> Result>> { + self.conversation_repository + .get_conversations_by_source(source, limit) + .await + } } From 7065903a01f176e5bd4e73d977ba919b2000a79d Mon Sep 17 00:00:00 2001 From: Phenotype Agent Date: Sun, 14 Jun 2026 22:35:04 -0700 Subject: [PATCH 70/70] fix(ui): apply user_initiated_conversations filter to SelectCommand::Conversation --- crates/forge_main/src/ui.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 6c8165c9ed..5e96ab0e5a 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -894,10 +894,11 @@ impl A + Send + Sync> UI self.select_row_output("Command", query.clone(), rows)?; } } - SelectCommand::Conversation { query } => { + SelectCommand::Conversation { query, .. } => { let max_conversations = self.config.max_conversations; let conversations = self.api.get_parent_conversations(Some(max_conversations)).await?; + let conversations = Self::user_initiated_conversations(conversations); if !conversations.is_empty() && let Some(conversation) = ConversationSelector::select_conversation( @@ -2267,6 +2268,25 @@ impl A + Send + Sync> UI Ok(()) } + fn user_initiated_conversations(conversations: Vec) -> Vec { + let related_ids: HashSet = conversations + .iter() + .flat_map(Conversation::related_conversation_ids) + .collect(); + + conversations + .into_iter() + .filter(|conversation| { + conversation + .context + .as_ref() + .and_then(|context| context.initiator.as_deref()) + .is_none_or(|initiator| initiator == "user") + && !related_ids.contains(&conversation.id) + }) + .collect() + } + async fn on_command(&mut self, command: AppCommand) -> anyhow::Result { match command { AppCommand::Conversations { id } => { @@ -3943,7 +3963,7 @@ impl A + Send + Sync> UI let content = ForgeFS::read_utf8(path).await?; // Try to parse as a dump file first (with "conversation" wrapper) - let conversation: Conversation = if let Ok(dump) = + let mut conversation: Conversation = if let Ok(dump) = serde_json::from_str::(&content) { dump.conversation