File bug directly (#6413)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Douwe Osinga
2026-01-12 12:13:01 -05:00
committed by GitHub
parent 26c5a6c1dd
commit 5a460fd3b1
10 changed files with 359 additions and 112 deletions
+3 -1
View File
@@ -7,7 +7,7 @@ use goose::conversation::Conversation;
use goose::model::ModelConfig;
use goose::permission::permission_confirmation::PrincipalType;
use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata, ProviderType};
use goose::session::{Session, SessionInsights, SessionType};
use goose::session::{Session, SessionInsights, SessionType, SystemInfo};
use rmcp::model::{
Annotations, Content, EmbeddedResource, Icon, ImageContent, JsonObject, RawAudioContent,
RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ResourceContents, Role,
@@ -327,6 +327,7 @@ derive_utoipa!(Icon as IconSchema);
#[openapi(
paths(
super::routes::status::status,
super::routes::status::system_info,
super::routes::status::diagnostics,
super::routes::mcp_ui_proxy::mcp_ui_proxy,
super::routes::config_management::backup_config,
@@ -483,6 +484,7 @@ derive_utoipa!(Icon as IconSchema);
Session,
SessionInsights,
SessionType,
SystemInfo,
Conversation,
IconSchema,
goose::session::extension_data::ExtensionData,
+12 -2
View File
@@ -1,8 +1,8 @@
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;
use axum::{extract::Path, http::StatusCode, routing::get, Json, Router};
use goose::session::{generate_diagnostics, get_system_info, SystemInfo};
#[utoipa::path(get, path = "/status",
responses(
@@ -13,6 +13,15 @@ async fn status() -> String {
"ok".to_string()
}
#[utoipa::path(get, path = "/system_info",
responses(
(status = 200, description = "System information", body = SystemInfo),
)
)]
async fn system_info() -> Json<SystemInfo> {
Json(get_system_info())
}
#[utoipa::path(get, path = "/diagnostics/{session_id}",
responses(
(status = 200, description = "Diagnostics zip file", content_type = "application/zip", body = Vec<u8>),
@@ -42,5 +51,6 @@ async fn diagnostics(Path(session_id): Path<String>) -> impl IntoResponse {
pub fn routes() -> Router {
Router::new()
.route("/status", get(status))
.route("/system_info", get(system_info))
.route("/diagnostics/{session_id}", get(diagnostics))
}
+64 -13
View File
@@ -1,30 +1,81 @@
use crate::config::base::Config;
use crate::config::extensions::get_enabled_extensions;
use crate::config::paths::Paths;
use crate::providers::utils::LOGS_TO_KEEP;
use crate::session::SessionManager;
use serde::{Deserialize, Serialize};
use std::fs::{self};
use std::io::Cursor;
use std::io::Write;
use utoipa::ToSchema;
use zip::write::FileOptions;
use zip::ZipWriter;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct SystemInfo {
pub app_version: String,
pub os: String,
pub os_version: String,
pub architecture: String,
pub provider: Option<String>,
pub model: Option<String>,
pub enabled_extensions: Vec<String>,
}
impl SystemInfo {
pub fn collect() -> Self {
let config = Config::global();
let provider = config.get_goose_provider().ok();
let model = config.get_goose_model().ok();
let enabled_extensions = get_enabled_extensions()
.into_iter()
.map(|ext| ext.name().to_string())
.collect();
Self {
app_version: env!("CARGO_PKG_VERSION").to_string(),
os: std::env::consts::OS.to_string(),
os_version: sys_info::os_release().unwrap_or_else(|_| "unknown".to_string()),
architecture: std::env::consts::ARCH.to_string(),
provider,
model,
enabled_extensions,
}
}
pub fn to_text(&self) -> String {
format!(
"App Version: {}\n\
OS: {}\n\
OS Version: {}\n\
Architecture: {}\n\
Provider: {}\n\
Model: {}\n\
Enabled Extensions: {}\n\
Timestamp: {}\n",
self.app_version,
self.os,
self.os_version,
self.architecture,
self.provider.as_deref().unwrap_or("unknown"),
self.model.as_deref().unwrap_or("unknown"),
self.enabled_extensions.join(", "),
chrono::Utc::now().to_rfc3339()
)
}
}
pub fn get_system_info() -> SystemInfo {
SystemInfo::collect()
}
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 data_dir = Paths::data_dir();
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 system_info = SystemInfo::collect();
let mut buffer = Vec::new();
{
@@ -55,7 +106,7 @@ pub async fn generate_diagnostics(session_id: &str) -> anyhow::Result<Vec<u8>> {
}
zip.start_file("system.txt", options)?;
zip.write_all(system_info.as_bytes())?;
zip.write_all(system_info.to_text().as_bytes())?;
let schedule_json = data_dir.join("schedule.json");
if schedule_json.exists() {
+1 -1
View File
@@ -4,6 +4,6 @@ pub mod extension_data;
mod legacy;
pub mod session_manager;
pub use diagnostics::generate_diagnostics;
pub use diagnostics::{generate_diagnostics, get_system_info, SystemInfo};
pub use extension_data::{EnabledExtensionsState, ExtensionData, ExtensionState, TodoState};
pub use session_manager::{Session, SessionInsights, SessionManager, SessionType};
+58
View File
@@ -2531,6 +2531,26 @@
}
}
},
"/system_info": {
"get": {
"tags": [
"super::routes::status"
],
"operationId": "system_info",
"responses": {
"200": {
"description": "System information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SystemInfo"
}
}
}
}
}
}
},
"/telemetry/event": {
"post": {
"tags": [
@@ -5711,6 +5731,44 @@
"propertyName": "type"
}
},
"SystemInfo": {
"type": "object",
"required": [
"app_version",
"os",
"os_version",
"architecture",
"enabled_extensions"
],
"properties": {
"app_version": {
"type": "string"
},
"architecture": {
"type": "string"
},
"enabled_extensions": {
"type": "array",
"items": {
"type": "string"
}
},
"model": {
"type": "string",
"nullable": true
},
"os": {
"type": "string"
},
"os_version": {
"type": "string"
},
"provider": {
"type": "string",
"nullable": true
}
}
},
"SystemNotificationContent": {
"type": "object",
"required": [
+3 -1
View File
@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetPricingData, GetPricingResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetPricingData, GetPricingResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SystemInfoData, SystemInfoResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, 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> & {
/**
@@ -435,6 +435,8 @@ export const updateSessionUserRecipeValues = <ThrowOnError extends boolean = fal
export const status = <ThrowOnError extends boolean = false>(options?: Options<StatusData, ThrowOnError>) => (options?.client ?? client).get<StatusResponses, unknown, ThrowOnError>({ url: '/status', ...options });
export const systemInfo = <ThrowOnError extends boolean = false>(options?: Options<SystemInfoData, ThrowOnError>) => (options?.client ?? client).get<SystemInfoResponses, unknown, ThrowOnError>({ url: '/system_info', ...options });
export const sendTelemetryEvent = <ThrowOnError extends boolean = false>(options: Options<SendTelemetryEventData, ThrowOnError>) => (options.client ?? client).post<SendTelemetryEventResponses, unknown, ThrowOnError>({
url: '/telemetry/event',
...options,
+26
View File
@@ -988,6 +988,16 @@ export type SuccessCheck = {
type: 'Shell';
};
export type SystemInfo = {
app_version: string;
architecture: string;
enabled_extensions: Array<string>;
model?: string | null;
os: string;
os_version: string;
provider?: string | null;
};
export type SystemNotificationContent = {
msg: string;
notificationType: SystemNotificationType;
@@ -3137,6 +3147,22 @@ export type StatusResponses = {
export type StatusResponse = StatusResponses[keyof StatusResponses];
export type SystemInfoData = {
body?: never;
path?: never;
query?: never;
url: '/system_info';
};
export type SystemInfoResponses = {
/**
* System information
*/
200: SystemInfo;
};
export type SystemInfoResponse = SystemInfoResponses[keyof SystemInfoResponses];
export type SendTelemetryEventData = {
body: TelemetryEventRequest;
path?: never;
+1 -1
View File
@@ -26,7 +26,7 @@ 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 { DiagnosticsModal } from './ui/Diagnostics';
import { getSession, Message } from '../api';
import CreateRecipeFromSessionModal from './recipes/CreateRecipeFromSessionModal';
import CreateEditRecipeModal from './recipes/CreateEditRecipeModal';
@@ -0,0 +1,191 @@
import React, { useState } from 'react';
import { AlertTriangle, Download, Github } from 'lucide-react';
import { Button } from './button';
import { toastError } from '../../toasts';
import { diagnostics, systemInfo } 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 [isFilingBug, setIsFilingBug] = 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);
}
};
const handleFileGitHubIssue = async () => {
setIsFilingBug(true);
try {
const response = await systemInfo({ throwOnError: true });
const info = response.data;
const providerModel =
info.provider && info.model
? `${info.provider} ${info.model}`
: info.provider || info.model || '[e.g. Google gemini-1.5-pro]';
const extensions =
info.enabled_extensions.length > 0
? info.enabled_extensions.join(', ')
: '[e.g. Computer Controller, Figma]';
const body = `**Describe the bug**
💡 Before filing, please check common issues:
https://block.github.io/goose/docs/troubleshooting
📦 To help us debug faster, attach your **diagnostics zip** if possible.
👉 How to capture it: https://block.github.io/goose/docs/troubleshooting/diagnostics-and-reporting/
A clear and concise description of what the bug is.
---
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
---
**Expected behavior**
A clear and concise description of what you expected to happen.
---
**Screenshots**
If applicable, add screenshots to help explain your problem.
---
**Please provide the following information**
- **OS & Arch:** ${info.os} ${info.os_version} ${info.architecture}
- **Interface:** UI
- **Version:** ${info.app_version}
- **Extensions enabled:** ${extensions}
- **Provider & Model:** ${providerModel}
---
**Additional context**
Add any other context about the problem here.
`;
const params = new URLSearchParams({
template: 'bug_report.md',
body: body,
labels: 'bug',
});
window.open(`https://github.com/block/goose/issues/new?${params.toString()}`, '_blank');
onClose();
} catch {
toastError({
title: 'Error',
msg: 'Failed to get system information',
});
} finally {
setIsFilingBug(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">Report a Problem</h3>
<p className="text-sm text-textSubtle mb-3">
You can download a diagnostics zip file to share with the team, or file a bug directly
on GitHub with your system details pre-filled. A diagnostics report contains the
following:
</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
the diagnostics file publicly.
</p>
<p className="text-sm text-textSubtle">
If you file a bug, consider attaching the diagnostics report to it.
</p>
</div>
</div>
<div className="flex gap-2 justify-end">
<Button
onClick={onClose}
variant="outline"
size="sm"
disabled={isDownloading || isFilingBug}
>
Cancel
</Button>
<Button
onClick={handleDownload}
variant="outline"
size="sm"
disabled={isDownloading || isFilingBug}
>
<Download size={16} className="mr-1" />
{isDownloading ? 'Downloading...' : 'Download'}
</Button>
<Button
onClick={handleFileGitHubIssue}
variant="outline"
size="sm"
disabled={isDownloading || isFilingBug}
className="bg-slate-600 text-white hover:bg-slate-700"
>
<Github size={16} className="mr-1" />
{isFilingBug ? 'Opening...' : 'File Bug on GitHub'}
</Button>
</div>
</div>
</div>
);
};
@@ -1,93 +0,0 @@
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>
);
};