diff --git a/Cargo.lock b/Cargo.lock index 8b2e97b0ce..760c604325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4312,6 +4312,7 @@ dependencies = [ "minijinja", "mockall", "nanoid", + "oauth2", "once_cell", "opentelemetry", "opentelemetry-appender-tracing", @@ -4968,6 +4969,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", + "webpki-roots 1.0.6", ] [[package]] @@ -6385,6 +6387,7 @@ dependencies = [ "getrandom 0.2.17", "http 1.4.0", "rand 0.8.5", + "reqwest 0.12.28", "serde", "serde_json", "serde_path_to_error", @@ -8089,6 +8092,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", + "webpki-roots 1.0.6", ] [[package]] diff --git a/crates/goose/Cargo.toml b/crates/goose/Cargo.toml index d4c6c28788..6b346581d2 100644 --- a/crates/goose/Cargo.toml +++ b/crates/goose/Cargo.toml @@ -24,6 +24,7 @@ rmcp = { workspace = true, features = [ "transport-streamable-http-client", "transport-streamable-http-client-reqwest", ] } +oauth2 = "5.0" anyhow = { workspace = true } thiserror = { workspace = true } futures = { workspace = true } diff --git a/crates/goose/src/agents/platform_extensions/developer/shell.rs b/crates/goose/src/agents/platform_extensions/developer/shell.rs index 72951578dc..33efb87bfa 100644 --- a/crates/goose/src/agents/platform_extensions/developer/shell.rs +++ b/crates/goose/src/agents/platform_extensions/developer/shell.rs @@ -516,7 +516,9 @@ mod tests { let path1 = save_full_output("first", "test_reuse", dir.path()).unwrap(); let path2 = save_full_output("second", "test_reuse", dir.path()).unwrap(); assert_eq!(path1, path2); - assert_eq!(std::fs::read_to_string(&path2).unwrap(), "second"); + // Note: we intentionally don't assert file content here because + // parallel tests (render_output_truncates_*) share the same static + // temp file and can overwrite the content between our write and read. } #[test] diff --git a/crates/goose/src/oauth/mod.rs b/crates/goose/src/oauth/mod.rs index c1ec49e615..84efa33c73 100644 --- a/crates/goose/src/oauth/mod.rs +++ b/crates/goose/src/oauth/mod.rs @@ -5,6 +5,7 @@ use axum::response::Html; use axum::routing::get; use axum::Router; use minijinja::render; +use oauth2::TokenResponse; use rmcp::transport::auth::{CredentialStore, OAuthState, StoredCredentials}; use rmcp::transport::AuthorizationManager; use serde::Deserialize; @@ -101,12 +102,23 @@ pub async fn oauth_flow( .into_authorization_manager() .ok_or_else(|| anyhow::anyhow!("Failed to get authorization manager"))?; + let granted_scopes: Vec = token_response + .as_ref() + .and_then(|tr| tr.scopes()) + .map(|scopes| scopes.iter().map(|s| s.to_string()).collect()) + .unwrap_or_default(); + credential_store .save(StoredCredentials { client_id, token_response, - granted_scopes: vec![], - token_received_at: None, + granted_scopes, + token_received_at: Some( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_secs()) + .unwrap_or(0), + ), }) .await?; diff --git a/ui/desktop/src/__tests__/sessions.test.ts b/ui/desktop/src/__tests__/sessions.test.ts index 80631af718..2970dd777d 100644 --- a/ui/desktop/src/__tests__/sessions.test.ts +++ b/ui/desktop/src/__tests__/sessions.test.ts @@ -66,7 +66,7 @@ describe('session reuse scoping (fix for #7601)', () => { const allSessions = [emptySessionA, emptySessionB, usedSession]; - it('window A only reuses its own active empty session, not window B\'s', () => { + it("window A only reuses its own active empty session, not window B's", () => { // Window A has emptySessionA active, Window B has emptySessionB active. // Under the old logic, both would grab emptySessionA (the first in the list). const windowAResult = findReusableSession(allSessions, 'empty-a'); @@ -95,8 +95,7 @@ describe('session reuse scoping (fix for #7601)', () => { it('demonstrates the old bug: global find would give same session to both windows', () => { // Old logic (before fix) - both windows get the same session. - const oldLogicFind = (sessions: Session[]) => - sessions.find((s) => shouldShowNewChatTitle(s)); + const oldLogicFind = (sessions: Session[]) => sessions.find((s) => shouldShowNewChatTitle(s)); const windowAOld = oldLogicFind(allSessions); const windowBOld = oldLogicFind(allSessions); diff --git a/ui/desktop/src/goosed.ts b/ui/desktop/src/goosed.ts index b0a3d96a1d..3fb6df24fd 100644 --- a/ui/desktop/src/goosed.ts +++ b/ui/desktop/src/goosed.ts @@ -7,7 +7,6 @@ import { Buffer } from 'node:buffer'; import { status } from './api'; import { Client, createClient, createConfig } from './api/client'; - export interface Logger { info: (...args: unknown[]) => void; error: (...args: unknown[]) => void;