Generated
+92
-5
@@ -253,8 +253,8 @@ dependencies = [
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
"zstd 0.13.3",
|
||||
"zstd-safe 7.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1075,7 +1075,7 @@ dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"constant_time_eq 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1203,6 +1203,26 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.13+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@@ -1524,6 +1544,12 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
@@ -2634,6 +2660,7 @@ dependencies = [
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"sqlx",
|
||||
"sys-info",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
@@ -2655,6 +2682,7 @@ dependencies = [
|
||||
"webbrowser 0.8.15",
|
||||
"winapi",
|
||||
"wiremock",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2810,7 +2838,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"config",
|
||||
"etcetera",
|
||||
"futures",
|
||||
"goose",
|
||||
"goose-mcp",
|
||||
@@ -4650,6 +4677,17 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
@@ -4671,6 +4709,18 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.5"
|
||||
@@ -6539,6 +6589,16 @@ dependencies = [
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-info"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.32.1"
|
||||
@@ -8580,10 +8640,18 @@ version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"constant_time_eq 0.1.5",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8615,13 +8683,32 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||
dependencies = [
|
||||
"zstd-safe 5.0.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
"zstd-safe 7.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "5.0.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -135,7 +135,7 @@ enum SessionCommand {
|
||||
#[arg(short, long, help = "Regex for removing matched sessions (optional)")]
|
||||
regex: Option<String>,
|
||||
},
|
||||
#[command(about = "Export a session to Markdown format")]
|
||||
#[command(about = "Export a session")]
|
||||
Export {
|
||||
#[command(flatten)]
|
||||
identifier: Option<Identifier>,
|
||||
@@ -156,6 +156,16 @@ enum SessionCommand {
|
||||
)]
|
||||
format: String,
|
||||
},
|
||||
#[command(name = "diagnostics")]
|
||||
Diagnostics {
|
||||
/// Session ID to generate diagnostics for
|
||||
#[arg(short, long)]
|
||||
session_id: String,
|
||||
|
||||
/// Output path for the diagnostics zip file (optional, defaults to current directory)
|
||||
#[arg(short, long)]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
@@ -847,6 +857,10 @@ pub async fn cli() -> Result<()> {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Some(SessionCommand::Diagnostics { session_id, output }) => {
|
||||
crate::commands::session::handle_diagnostics(&session_id, output).await?;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
let session_start = std::time::Instant::now();
|
||||
let session_type = if resume { "resumed" } else { "new" };
|
||||
|
||||
@@ -2,10 +2,11 @@ use crate::session::message_to_markdown;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use cliclack::{confirm, multiselect, select};
|
||||
use goose::session::{Session, SessionManager};
|
||||
use goose::session::{generate_diagnostics, Session, SessionManager};
|
||||
use goose::utils::safe_truncate;
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TRUNCATED_DESC_LENGTH: usize = 60;
|
||||
@@ -204,10 +205,39 @@ pub async fn handle_session_export(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Convert a list of messages to markdown format for session export
|
||||
///
|
||||
/// This function handles the formatting of a complete session including headers,
|
||||
/// message organization, and proper tool request/response pairing.
|
||||
|
||||
pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>) -> Result<()> {
|
||||
println!(
|
||||
"Generating diagnostics bundle for session '{}'...",
|
||||
session_id
|
||||
);
|
||||
|
||||
let diagnostics_data = generate_diagnostics(session_id).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write to generate diagnostics bundle for session '{}'",
|
||||
session_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let output_file = if let Some(path) = output_path {
|
||||
path.clone()
|
||||
} else {
|
||||
PathBuf::from(format!("diagnostics_{}.zip", session_id))
|
||||
};
|
||||
|
||||
let mut file = fs::File::create(&output_file).context(format!(
|
||||
"Failed to create output file: {}",
|
||||
output_file.display()
|
||||
))?;
|
||||
|
||||
file.write_all(&diagnostics_data)
|
||||
.context("Failed to write diagnostics data")?;
|
||||
|
||||
println!("Diagnostics bundle saved to: {}", output_file.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn export_session_to_markdown(
|
||||
messages: Vec<goose::conversation::message::Message>,
|
||||
session_name: &String,
|
||||
|
||||
@@ -33,7 +33,6 @@ base64 = "0.21"
|
||||
config = { version = "0.14.1", features = ["toml"] }
|
||||
thiserror = "1.0"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
etcetera = "0.8.0"
|
||||
serde_yaml = "0.9.34"
|
||||
utoipa = { version = "4.1", features = ["axum_extras", "chrono"] }
|
||||
reqwest = { version = "0.12.9", features = ["json", "rustls-tls", "blocking", "multipart"], default-features = false }
|
||||
|
||||
@@ -322,7 +322,8 @@ derive_utoipa!(Icon as IconSchema);
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
super::routes::health::status,
|
||||
super::routes::status::status,
|
||||
super::routes::status::diagnostics,
|
||||
super::routes::config_management::backup_config,
|
||||
super::routes::config_management::recover_config,
|
||||
super::routes::config_management::validate_config,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
#[utoipa::path(get, path = "/status",
|
||||
responses(
|
||||
(status = 200, description = "ok", body = String),
|
||||
)
|
||||
)]
|
||||
async fn status() -> String {
|
||||
"ok".to_string()
|
||||
}
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new().route("/status", get(status))
|
||||
}
|
||||
@@ -4,13 +4,13 @@ pub mod config_management;
|
||||
pub mod context;
|
||||
pub mod errors;
|
||||
pub mod extension;
|
||||
pub mod health;
|
||||
pub mod recipe;
|
||||
pub mod recipe_utils;
|
||||
pub mod reply;
|
||||
pub mod schedule;
|
||||
pub mod session;
|
||||
pub mod setup;
|
||||
pub mod status;
|
||||
pub mod utils;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -19,7 +19,7 @@ use axum::Router;
|
||||
// Function to configure all routes
|
||||
pub fn configure(state: Arc<crate::state::AppState>) -> Router {
|
||||
Router::new()
|
||||
.merge(health::routes())
|
||||
.merge(status::routes())
|
||||
.merge(reply::routes(state.clone()))
|
||||
.merge(agent::routes(state.clone()))
|
||||
.merge(audio::routes(state.clone()))
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use axum::body::Body;
|
||||
use axum::http::HeaderValue;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::Path, http::StatusCode, routing::get, Router};
|
||||
use goose::session::generate_diagnostics;
|
||||
|
||||
#[utoipa::path(get, path = "/status",
|
||||
responses(
|
||||
(status = 200, description = "ok", body = String),
|
||||
)
|
||||
)]
|
||||
async fn status() -> String {
|
||||
"ok".to_string()
|
||||
}
|
||||
|
||||
#[utoipa::path(get, path = "/diagnostics/{session_id}",
|
||||
responses(
|
||||
(status = 200, description = "Diagnostics zip file", content_type = "application/zip", body = Vec<u8>),
|
||||
(status = 500, description = "Failed to generate diagnostics"),
|
||||
)
|
||||
)]
|
||||
async fn diagnostics(Path(session_id): Path<String>) -> impl IntoResponse {
|
||||
match generate_diagnostics(&session_id).await {
|
||||
Ok(zip_data) => {
|
||||
let filename = format!("attachment; filename=\"diagnostics_{}.zip\"", session_id);
|
||||
let headers = [
|
||||
(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/zip"),
|
||||
),
|
||||
(
|
||||
http::header::CONTENT_DISPOSITION,
|
||||
HeaderValue::from_str(&filename).map_err(|_e| StatusCode::BAD_REQUEST)?,
|
||||
),
|
||||
];
|
||||
|
||||
Ok((headers, Body::from(zip_data)))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.route("/status", get(status))
|
||||
.route("/diagnostics/{session_id}", get(diagnostics))
|
||||
}
|
||||
@@ -99,11 +99,14 @@ dashmap = "6.1"
|
||||
ahash = "0.8"
|
||||
tokio-util = "0.7.15"
|
||||
unicode-normalization = "0.1"
|
||||
zip = "0.6"
|
||||
sys-info = "0.9"
|
||||
|
||||
oauth2 = "5.0.0"
|
||||
schemars = { version = "1.0.4", default-features = false, features = ["derive"] }
|
||||
insta = "1.43.2"
|
||||
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["wincred"] }
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::config::paths::Paths;
|
||||
use crate::session::SessionManager;
|
||||
use std::fs::{self};
|
||||
use std::io::Cursor;
|
||||
use std::io::Write;
|
||||
use zip::write::FileOptions;
|
||||
use zip::ZipWriter;
|
||||
|
||||
pub async fn generate_diagnostics(session_id: &str) -> anyhow::Result<Vec<u8>> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
let config_dir = Paths::config_dir();
|
||||
let config_path = config_dir.join("config.yaml");
|
||||
|
||||
let system_info = format!(
|
||||
"App Version: {}\n\
|
||||
OS: {}\n\
|
||||
OS Version: {}\n\
|
||||
Architecture: {}\n\
|
||||
Timestamp: {}\n",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
std::env::consts::OS,
|
||||
sys_info::os_release().unwrap_or_else(|_| "unknown".to_string()),
|
||||
std::env::consts::ARCH,
|
||||
chrono::Utc::now().to_rfc3339()
|
||||
);
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
{
|
||||
let mut zip = ZipWriter::new(Cursor::new(&mut buffer));
|
||||
let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
|
||||
|
||||
let mut log_files: Vec<_> = fs::read_dir(&logs_dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().extension().is_some_and(|ext| ext == "jsonl"))
|
||||
.collect();
|
||||
|
||||
log_files.sort_by_key(|e| e.metadata().ok().and_then(|m| m.modified().ok()));
|
||||
|
||||
for entry in log_files.iter().rev().take(3) {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap().to_str().unwrap();
|
||||
zip.start_file(format!("logs/{}", name), options)?;
|
||||
zip.write_all(&fs::read(&path)?)?;
|
||||
}
|
||||
|
||||
let session_data = SessionManager::export_session(session_id).await?;
|
||||
zip.start_file("session.json", options)?;
|
||||
zip.write_all(session_data.as_bytes())?;
|
||||
|
||||
if config_path.exists() {
|
||||
zip.start_file("config.yaml", options)?;
|
||||
zip.write_all(&fs::read(&config_path)?)?;
|
||||
}
|
||||
|
||||
zip.start_file("system.txt", options)?;
|
||||
zip.write_all(system_info.as_bytes())?;
|
||||
|
||||
zip.finish()?;
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
mod diagnostics;
|
||||
pub mod extension_data;
|
||||
mod legacy;
|
||||
pub mod session_manager;
|
||||
|
||||
pub use diagnostics::generate_diagnostics;
|
||||
pub use extension_data::{EnabledExtensionsState, ExtensionData, ExtensionState, TodoState};
|
||||
pub use session_manager::{Session, SessionInsights, SessionManager};
|
||||
|
||||
+35
-1
@@ -896,6 +896,40 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/diagnostics/{session_id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::status"
|
||||
],
|
||||
"operationId": "diagnostics",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Diagnostics zip file",
|
||||
"content": {
|
||||
"application/zip": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Failed to generate diagnostics"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/handle_openrouter": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1972,7 +2006,7 @@
|
||||
"/status": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::health"
|
||||
"super::routes::status"
|
||||
],
|
||||
"operationId": "status",
|
||||
"responses": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Client, Options as Options2, TDataShape } from './client';
|
||||
import { client } from './client.gen';
|
||||
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, ConfirmPermissionData, ConfirmPermissionErrors, ConfirmPermissionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetToolsData, GetToolsErrors, GetToolsResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, ManageContextData, ManageContextErrors, ManageContextResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StatusData, StatusResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionDescriptionData, UpdateSessionDescriptionErrors, UpdateSessionDescriptionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
|
||||
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, ConfirmPermissionData, ConfirmPermissionErrors, ConfirmPermissionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetToolsData, GetToolsErrors, GetToolsResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, ManageContextData, ManageContextErrors, ManageContextResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StatusData, StatusResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionDescriptionData, UpdateSessionDescriptionErrors, UpdateSessionDescriptionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
||||
/**
|
||||
@@ -256,6 +256,13 @@ export const manageContext = <ThrowOnError extends boolean = false>(options: Opt
|
||||
});
|
||||
};
|
||||
|
||||
export const diagnostics = <ThrowOnError extends boolean = false>(options: Options<DiagnosticsData, ThrowOnError>) => {
|
||||
return (options.client ?? client).get<DiagnosticsResponses, DiagnosticsErrors, ThrowOnError>({
|
||||
url: '/diagnostics/{session_id}',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const startOpenrouterSetup = <ThrowOnError extends boolean = false>(options?: Options<StartOpenrouterSetupData, ThrowOnError>) => {
|
||||
return (options?.client ?? client).post<StartOpenrouterSetupResponses, unknown, ThrowOnError>({
|
||||
url: '/handle_openrouter',
|
||||
|
||||
@@ -1594,6 +1594,31 @@ export type ManageContextResponses = {
|
||||
|
||||
export type ManageContextResponse = ManageContextResponses[keyof ManageContextResponses];
|
||||
|
||||
export type DiagnosticsData = {
|
||||
body?: never;
|
||||
path: {
|
||||
session_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/diagnostics/{session_id}';
|
||||
};
|
||||
|
||||
export type DiagnosticsErrors = {
|
||||
/**
|
||||
* Failed to generate diagnostics
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type DiagnosticsResponses = {
|
||||
/**
|
||||
* Diagnostics zip file
|
||||
*/
|
||||
200: Blob | File;
|
||||
};
|
||||
|
||||
export type DiagnosticsResponse = DiagnosticsResponses[keyof DiagnosticsResponses];
|
||||
|
||||
export type StartOpenrouterSetupData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { FolderKey, ScrollText } from 'lucide-react';
|
||||
import { Bug, FolderKey, ScrollText } from 'lucide-react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
|
||||
import { Button } from './ui/button';
|
||||
import type { View } from '../utils/navigationUtils';
|
||||
@@ -8,7 +8,6 @@ import { Attach, Send, Close, Microphone } from './icons';
|
||||
import { ChatState } from '../types/chatState';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { LocalMessageStorage } from '../utils/localMessageStorage';
|
||||
import { Message } from '../api';
|
||||
import { DirSwitcher } from './bottom_menu/DirSwitcher';
|
||||
import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar';
|
||||
import { BottomMenuModeSelection } from './bottom_menu/BottomMenuModeSelection';
|
||||
@@ -28,6 +27,8 @@ import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
||||
import { Recipe } from '../recipe';
|
||||
import MessageQueue from './MessageQueue';
|
||||
import { detectInterruption } from '../utils/interruptionDetector';
|
||||
import { DiagnosticsModal } from './ui/DownloadDiagnostics';
|
||||
import { Message } from '../api';
|
||||
|
||||
interface QueuedMessage {
|
||||
id: string;
|
||||
@@ -149,6 +150,8 @@ export default function ChatInput({
|
||||
const agentIsReady = chatContext === null || chatContext.agentWaitingMessage === null;
|
||||
const draftLoadedRef = useRef(false);
|
||||
|
||||
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
|
||||
|
||||
// Debug logging for draft context
|
||||
useEffect(() => {
|
||||
// Debug logging removed - draft functionality is working correctly
|
||||
@@ -1548,7 +1551,6 @@ export default function ChatInput({
|
||||
<DirSwitcher className="mr-0" />
|
||||
<div className="w-px h-4 bg-border-default mx-2" />
|
||||
|
||||
{/* Attach button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
@@ -1563,6 +1565,7 @@ export default function ChatInput({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Attach file or directory</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="w-px h-4 bg-border-default mx-2" />
|
||||
|
||||
{/* Model selector, mode selector, alerts, summarize button */}
|
||||
@@ -1610,8 +1613,32 @@ export default function ChatInput({
|
||||
<TooltipContent>Configure goosehints</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{sessionId && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setDiagnosticsOpen(true)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer transition-colors"
|
||||
>
|
||||
<Bug className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Generate diagnostics bundle</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{sessionId && diagnosticsOpen && (
|
||||
<DiagnosticsModal
|
||||
isOpen={diagnosticsOpen}
|
||||
onClose={() => setDiagnosticsOpen(false)}
|
||||
sessionId={sessionId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MentionPopover
|
||||
ref={mentionPopoverRef}
|
||||
isOpen={mentionPopover.isOpen}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { Button } from './button';
|
||||
import { toastError } from '../../toasts';
|
||||
import { diagnostics } from '../../api';
|
||||
|
||||
interface DiagnosticsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export const DiagnosticsModal: React.FC<DiagnosticsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
sessionId,
|
||||
}) => {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
|
||||
const handleDownload = async () => {
|
||||
setIsDownloading(true);
|
||||
|
||||
try {
|
||||
const response = await diagnostics({
|
||||
path: { session_id: sessionId },
|
||||
throwOnError: true,
|
||||
});
|
||||
|
||||
const blob = new Blob([response.data], { type: 'application/zip' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `diagnostics_${sessionId}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
onClose();
|
||||
} catch {
|
||||
toastError({
|
||||
title: 'Diagnostics Error',
|
||||
msg: 'Failed to download diagnostics',
|
||||
});
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-background-default border border-borderStandard rounded-lg p-6 max-w-md mx-4">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<AlertTriangle className="text-orange-500 flex-shrink-0 mt-1" size={20} />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-textStandard mb-2">Download Diagnostics</h3>
|
||||
<p className="text-sm text-textSubtle mb-3">
|
||||
Hit the download button to get a zip file containing all the important information to
|
||||
diagnose a problem in goose. You can share this file with the team or if you are a
|
||||
developer look at it yourself.
|
||||
</p>
|
||||
<ul className="text-sm text-textSubtle list-disc list-inside space-y-1 mb-3">
|
||||
<li>Basic system info</li>
|
||||
<li>Your current session messages</li>
|
||||
<li>Recent log files</li>
|
||||
<li>Configuration settings</li>
|
||||
</ul>
|
||||
<p className="text-sm text-textSubtle">
|
||||
<strong>Warning:</strong> If your session contains sensitive information, do not share
|
||||
this file publicly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button onClick={onClose} variant="outline" size="sm" disabled={isDownloading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isDownloading}
|
||||
className="bg-slate-600 text-white hover:bg-slate-700"
|
||||
>
|
||||
{isDownloading ? 'Downloading...' : 'Download'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user