mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
Remove clippy too_many_lines lint and decompose long functions (#7064)
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Release Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Release Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
This commit is contained in:
parent
948cb91d54
commit
b18120bec3
12 changed files with 256 additions and 412 deletions
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
|
|
@ -56,7 +56,7 @@
|
||||||
**Rust checks:**
|
**Rust checks:**
|
||||||
- `cargo fmt --check` - Code formatting (rustfmt)
|
- `cargo fmt --check` - Code formatting (rustfmt)
|
||||||
- `cargo test --jobs 2` - All tests
|
- `cargo test --jobs 2` - All tests
|
||||||
- `./scripts/clippy-lint.sh` - Linting (clippy)
|
- `cargo clippy --all-targets -- -D warnings` - Linting (clippy)
|
||||||
- `just check-openapi-schema` - OpenAPI schema validation
|
- `just check-openapi-schema` - OpenAPI schema validation
|
||||||
|
|
||||||
**Desktop app checks:**
|
**Desktop app checks:**
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
|
|
||||||
Do not comment on:
|
Do not comment on:
|
||||||
- **Style/formatting** - CI handles this (rustfmt, prettier)
|
- **Style/formatting** - CI handles this (rustfmt, prettier)
|
||||||
- **Clippy warnings** - CI handles this (clippy-lint.sh)
|
- **Clippy warnings** - CI handles this (clippy)
|
||||||
- **Test failures** - CI handles this (full test suite)
|
- **Test failures** - CI handles this (full test suite)
|
||||||
- **Missing dependencies** - CI handles this (npm ci will fail)
|
- **Missing dependencies** - CI handles this (npm ci will fail)
|
||||||
- **Minor naming suggestions** - unless truly confusing
|
- **Minor naming suggestions** - unless truly confusing
|
||||||
|
|
|
||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -95,7 +95,10 @@ jobs:
|
||||||
# play nicely with hermit-managed rust
|
# play nicely with hermit-managed rust
|
||||||
hermit uninstall rustup
|
hermit uninstall rustup
|
||||||
export CARGO_INCREMENTAL=0
|
export CARGO_INCREMENTAL=0
|
||||||
./scripts/clippy-lint.sh
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Check for banned TLS crates
|
||||||
|
run: ./scripts/check-no-native-tls.sh
|
||||||
|
|
||||||
openapi-schema-check:
|
openapi-schema-check:
|
||||||
name: Check OpenAPI Schema is Up-to-Date
|
name: Check OpenAPI Schema is Up-to-Date
|
||||||
|
|
|
||||||
2
.github/workflows/goose-issue-solver.yml
vendored
2
.github/workflows/goose-issue-solver.yml
vendored
|
|
@ -95,7 +95,7 @@ env:
|
||||||
- [ ] cargo check
|
- [ ] cargo check
|
||||||
- [ ] cargo test (affected crates)
|
- [ ] cargo test (affected crates)
|
||||||
- [ ] cargo fmt
|
- [ ] cargo fmt
|
||||||
- [ ] ./scripts/clippy-lint.sh
|
- [ ] cargo clippy --all-targets -- -D warnings
|
||||||
- [ ] Fix failures, retry up to 3 times
|
- [ ] Fix failures, retry up to 3 times
|
||||||
|
|
||||||
## Phase 6: Confirm (MANDATORY)
|
## Phase 6: Confirm (MANDATORY)
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ just record-mcp-tests # record MCP
|
||||||
### Lint/Format
|
### Lint/Format
|
||||||
```bash
|
```bash
|
||||||
cargo fmt
|
cargo fmt
|
||||||
./scripts/clippy-lint.sh
|
cargo clippy --all-targets -- -D warnings
|
||||||
cargo clippy --fix
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### UI
|
### UI
|
||||||
|
|
@ -63,7 +62,7 @@ ui/desktop/ # Electron app
|
||||||
# 3. cargo fmt
|
# 3. cargo fmt
|
||||||
# 4. cargo build
|
# 4. cargo build
|
||||||
# 5. cargo test -p <crate>
|
# 5. cargo test -p <crate>
|
||||||
# 6. ./scripts/clippy-lint.sh
|
# 6. cargo clippy --all-targets -- -D warnings
|
||||||
# 7. [if server] just generate-openapi
|
# 7. [if server] just generate-openapi
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -92,7 +91,7 @@ Logging: Clean up existing logs, don't add more unless for errors or security ev
|
||||||
Never: Edit ui/desktop/openapi.json manually
|
Never: Edit ui/desktop/openapi.json manually
|
||||||
Never: Edit Cargo.toml use cargo add
|
Never: Edit Cargo.toml use cargo add
|
||||||
Never: Skip cargo fmt
|
Never: Skip cargo fmt
|
||||||
Never: Merge without ./scripts/clippy-lint.sh
|
Never: Merge without running clippy
|
||||||
Never: Comment self-evident operations (`// Initialize`, `// Return result`), getters/setters, constructors, or standard Rust idioms
|
Never: Comment self-evident operations (`// Initialize`, `// Return result`), getters/setters, constructors, or standard Rust idioms
|
||||||
|
|
||||||
## Entry Points
|
## Entry Points
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ When making changes to the Rust code, test them on the CLI or run checks, tests,
|
||||||
cargo check # verify changes compile
|
cargo check # verify changes compile
|
||||||
cargo test # run tests with changes
|
cargo test # run tests with changes
|
||||||
cargo fmt # format code
|
cargo fmt # format code
|
||||||
./scripts/clippy-lint.sh # run the linter
|
cargo clippy --all-targets -- -D warnings # run the linter
|
||||||
```
|
```
|
||||||
|
|
||||||
### Node
|
### Node
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ If you're new to Rust, configure your AI tool to help you learn:
|
||||||
This is a Rust project using cargo workspaces.
|
This is a Rust project using cargo workspaces.
|
||||||
- Follow existing error handling patterns using anyhow::Result
|
- Follow existing error handling patterns using anyhow::Result
|
||||||
- Use async/await for I/O operations
|
- Use async/await for I/O operations
|
||||||
- Follow the project's clippy lints (see clippy-baselines/)
|
- Follow the project's clippy lints (see clippy.toml)
|
||||||
- Run cargo fmt before committing
|
- Run cargo fmt before committing
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -240,7 +240,7 @@ cargo build -p goose-mcp
|
||||||
cargo test -p goose-mcp
|
cargo test -p goose-mcp
|
||||||
|
|
||||||
# Run clippy
|
# Run clippy
|
||||||
./scripts/clippy-lint.sh
|
cargo clippy --all-targets -- -D warnings
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example 2: Fixing a Rust Compiler Error
|
### Example 2: Fixing a Rust Compiler Error
|
||||||
|
|
@ -314,4 +314,4 @@ cargo build -p goose-cli -p goose
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
cargo test -p goose-cli
|
cargo test -p goose-cli
|
||||||
```
|
```
|
||||||
|
|
|
||||||
4
Justfile
4
Justfile
|
|
@ -10,7 +10,9 @@ check-everything:
|
||||||
@echo " → Formatting Rust code..."
|
@echo " → Formatting Rust code..."
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
@echo " → Running clippy linting..."
|
@echo " → Running clippy linting..."
|
||||||
./scripts/clippy-lint.sh
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
@echo " → Checking for banned TLS crates..."
|
||||||
|
./scripts/check-no-native-tls.sh
|
||||||
@echo " → Checking UI code formatting..."
|
@echo " → Checking UI code formatting..."
|
||||||
cd ui/desktop && npm run lint:check
|
cd ui/desktop && npm run lint:check
|
||||||
@echo " → Validating OpenAPI schema..."
|
@echo " → Validating OpenAPI schema..."
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
crates/goose-cli/src/commands/configure.rs::configure_provider_dialog
|
|
||||||
crates/goose-cli/src/commands/configure.rs::configure_tool_permissions_dialog
|
|
||||||
crates/goose-cli/src/commands/project.rs::handle_project_default
|
|
||||||
crates/goose-cli/src/commands/project.rs::handle_projects_interactive
|
|
||||||
crates/goose-cli/src/session/builder.rs::build_session
|
|
||||||
crates/goose-cli/src/session/export.rs::tool_response_to_markdown
|
|
||||||
crates/goose-cli/src/session/mod.rs::process_agent_response
|
|
||||||
crates/goose-mcp/src/computercontroller/mod.rs::new
|
|
||||||
crates/goose-mcp/src/computercontroller/pdf_tool.rs::pdf_tool
|
|
||||||
crates/goose-mcp/src/memory/mod.rs::new
|
|
||||||
crates/goose-server/src/openapi.rs::convert_typed_schema
|
|
||||||
crates/goose-server/src/openapi.rs::convert_typed_schema
|
|
||||||
crates/goose/src/agents/agent.rs::create_recipe
|
|
||||||
crates/goose/src/agents/agent.rs::dispatch_tool_call
|
|
||||||
crates/goose/src/agents/agent.rs::reply
|
|
||||||
crates/goose/src/agents/agent.rs::reply_internal
|
|
||||||
crates/goose/src/providers/claude_code.rs::execute_command
|
|
||||||
crates/goose/src/providers/codex.rs::execute_command
|
|
||||||
crates/goose/src/providers/formats/anthropic.rs::format_messages
|
|
||||||
crates/goose/src/providers/formats/anthropic.rs::response_to_streaming_message
|
|
||||||
crates/goose/src/providers/formats/databricks.rs::format_messages
|
|
||||||
crates/goose/src/providers/formats/google.rs::format_messages
|
|
||||||
crates/goose/src/providers/formats/openai.rs::format_messages
|
|
||||||
crates/goose/src/providers/formats/openai.rs::response_to_streaming_message
|
|
||||||
crates/goose/src/providers/snowflake.rs::post
|
|
||||||
crates/goose/src/security/mod.rs::analyze_tool_requests
|
|
||||||
1
clippy.toml
Normal file
1
clippy.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
too-many-lines-threshold = 200
|
||||||
|
|
@ -337,36 +337,26 @@ async fn load_extensions(
|
||||||
agent_ptr
|
agent_ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
struct ResolvedProviderConfig {
|
||||||
goose::posthog::set_session_context("cli", session_config.resume);
|
provider_name: String,
|
||||||
|
model_name: String,
|
||||||
|
model_config: goose::model::ModelConfig,
|
||||||
|
}
|
||||||
|
|
||||||
let config = Config::global();
|
fn resolve_provider_and_model(
|
||||||
let agent: Agent = Agent::new();
|
session_config: &SessionBuilderConfig,
|
||||||
|
config: &Config,
|
||||||
if session_config.container.is_some() {
|
saved_provider: Option<String>,
|
||||||
agent.set_container(session_config.container.clone()).await;
|
saved_model_config: Option<goose::model::ModelConfig>,
|
||||||
}
|
) -> ResolvedProviderConfig {
|
||||||
|
let recipe_settings = session_config
|
||||||
let session_manager = agent.config.session_manager.clone();
|
.recipe
|
||||||
|
.as_ref()
|
||||||
let (saved_provider, saved_model_config) = if session_config.resume {
|
.and_then(|r| r.settings.as_ref());
|
||||||
if let Some(ref session_id) = session_config.session_id {
|
|
||||||
match session_manager.get_session(session_id, false).await {
|
|
||||||
Ok(session_data) => (session_data.provider_name, session_data.model_config),
|
|
||||||
Err(_) => (None, None),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let recipe = session_config.recipe.as_ref();
|
|
||||||
let recipe_settings = recipe.and_then(|r| r.settings.as_ref());
|
|
||||||
|
|
||||||
let provider_name = session_config
|
let provider_name = session_config
|
||||||
.provider
|
.provider
|
||||||
|
.clone()
|
||||||
.or(saved_provider)
|
.or(saved_provider)
|
||||||
.or_else(|| recipe_settings.and_then(|s| s.goose_provider.clone()))
|
.or_else(|| recipe_settings.and_then(|s| s.goose_provider.clone()))
|
||||||
.or_else(|| config.get_goose_provider().ok())
|
.or_else(|| config.get_goose_provider().ok())
|
||||||
|
|
@ -374,6 +364,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
|
|
||||||
let model_name = session_config
|
let model_name = session_config
|
||||||
.model
|
.model
|
||||||
|
.clone()
|
||||||
.or_else(|| saved_model_config.as_ref().map(|mc| mc.model_name.clone()))
|
.or_else(|| saved_model_config.as_ref().map(|mc| mc.model_name.clone()))
|
||||||
.or_else(|| recipe_settings.and_then(|s| s.goose_model.clone()))
|
.or_else(|| recipe_settings.and_then(|s| s.goose_model.clone()))
|
||||||
.or_else(|| config.get_goose_model().ok())
|
.or_else(|| config.get_goose_model().ok())
|
||||||
|
|
@ -399,6 +390,215 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
.with_temperature(temperature)
|
.with_temperature(temperature)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ResolvedProviderConfig {
|
||||||
|
provider_name,
|
||||||
|
model_name,
|
||||||
|
model_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve_session_id(
|
||||||
|
session_config: &SessionBuilderConfig,
|
||||||
|
session_manager: &goose::session::session_manager::SessionManager,
|
||||||
|
) -> String {
|
||||||
|
if session_config.no_session {
|
||||||
|
let working_dir = std::env::current_dir().expect("Could not get working directory");
|
||||||
|
let session = session_manager
|
||||||
|
.create_session(working_dir, "CLI Session".to_string(), SessionType::Hidden)
|
||||||
|
.await
|
||||||
|
.expect("Could not create session");
|
||||||
|
session.id
|
||||||
|
} else if session_config.resume {
|
||||||
|
if let Some(ref session_id) = session_config.session_id {
|
||||||
|
match session_manager.get_session(session_id, false).await {
|
||||||
|
Ok(_) => session_id.clone(),
|
||||||
|
Err(_) => {
|
||||||
|
output::render_error(&format!(
|
||||||
|
"Cannot resume session {} - no such session exists",
|
||||||
|
style(session_id).cyan()
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match session_manager.list_sessions().await {
|
||||||
|
Ok(sessions) if !sessions.is_empty() => sessions[0].id.clone(),
|
||||||
|
_ => {
|
||||||
|
output::render_error("Cannot resume - no previous sessions found");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session_config.session_id.clone().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_resumed_session_workdir(agent: &Agent, session_id: &str, interactive: bool) {
|
||||||
|
let session = agent
|
||||||
|
.config
|
||||||
|
.session_manager
|
||||||
|
.get_session(session_id, false)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
output::render_error(&format!("Failed to read session metadata: {}", e));
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let current_workdir = std::env::current_dir().expect("Failed to get current working directory");
|
||||||
|
if current_workdir == session.working_dir {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
let change_workdir = cliclack::confirm(format!(
|
||||||
|
"{} The original working directory of this session was set to {}. \
|
||||||
|
Your current directory is {}. \
|
||||||
|
Do you want to switch back to the original working directory?",
|
||||||
|
style("WARNING:").yellow(),
|
||||||
|
style(session.working_dir.display()).cyan(),
|
||||||
|
style(current_workdir.display()).cyan(),
|
||||||
|
))
|
||||||
|
.initial_value(true)
|
||||||
|
.interact()
|
||||||
|
.expect("Failed to get user input");
|
||||||
|
|
||||||
|
if change_workdir {
|
||||||
|
if !session.working_dir.exists() {
|
||||||
|
output::render_error(&format!(
|
||||||
|
"Cannot switch to original working directory - {} no longer exists",
|
||||||
|
style(session.working_dir.display()).cyan()
|
||||||
|
));
|
||||||
|
} else if let Err(e) = std::env::set_current_dir(&session.working_dir) {
|
||||||
|
output::render_error(&format!(
|
||||||
|
"Failed to switch to original working directory: {}",
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
style(format!(
|
||||||
|
"Warning: Working directory differs from session (current: {}, session: {}). \
|
||||||
|
Staying in current directory.",
|
||||||
|
current_workdir.display(),
|
||||||
|
session.working_dir.display()
|
||||||
|
))
|
||||||
|
.yellow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve_and_load_extensions(
|
||||||
|
agent: Agent,
|
||||||
|
session_config: &SessionBuilderConfig,
|
||||||
|
recipe: Option<&Recipe>,
|
||||||
|
session_id: &str,
|
||||||
|
provider_for_debug: Arc<dyn goose::providers::base::Provider>,
|
||||||
|
) -> Arc<Agent> {
|
||||||
|
for warning in goose::config::get_warnings() {
|
||||||
|
eprintln!("{}", style(format!("Warning: {}", warning)).yellow());
|
||||||
|
}
|
||||||
|
|
||||||
|
let configured_extensions: Vec<ExtensionConfig> = if session_config.resume {
|
||||||
|
agent
|
||||||
|
.config
|
||||||
|
.session_manager
|
||||||
|
.get_session(session_id, false)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| EnabledExtensionsState::from_extension_data(&s.extension_data))
|
||||||
|
.map(|state| state.extensions)
|
||||||
|
.unwrap_or_else(get_enabled_extensions)
|
||||||
|
} else if session_config.no_profile {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
resolve_extensions_for_new_session(recipe.and_then(|r| r.extensions.as_deref()), None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let cli_flag_extensions = parse_cli_flag_extensions(
|
||||||
|
&session_config.extensions,
|
||||||
|
&session_config.streamable_http_extensions,
|
||||||
|
&session_config.builtins,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut extensions_to_load: Vec<(String, ExtensionConfig)> = configured_extensions
|
||||||
|
.iter()
|
||||||
|
.map(|cfg| (cfg.name(), cfg.clone()))
|
||||||
|
.collect();
|
||||||
|
extensions_to_load.extend(cli_flag_extensions);
|
||||||
|
|
||||||
|
load_extensions(
|
||||||
|
agent,
|
||||||
|
extensions_to_load,
|
||||||
|
provider_for_debug,
|
||||||
|
session_config.interactive,
|
||||||
|
session_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_session_prompts(
|
||||||
|
session: &CliSession,
|
||||||
|
config: &Config,
|
||||||
|
session_config: &SessionBuilderConfig,
|
||||||
|
session_id: &str,
|
||||||
|
) {
|
||||||
|
if let Err(e) = session.agent.persist_extension_state(session_id).await {
|
||||||
|
tracing::warn!("Failed to save extension state: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
session
|
||||||
|
.agent
|
||||||
|
.extend_system_prompt(super::prompt::get_cli_prompt())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(ref additional_prompt) = session_config.additional_system_prompt {
|
||||||
|
session
|
||||||
|
.agent
|
||||||
|
.extend_system_prompt(additional_prompt.clone())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let system_prompt_file: Option<String> = config.get_param("GOOSE_SYSTEM_PROMPT_FILE_PATH").ok();
|
||||||
|
if let Some(ref path) = system_prompt_file {
|
||||||
|
let override_prompt =
|
||||||
|
std::fs::read_to_string(path).expect("Failed to read system prompt file");
|
||||||
|
session.agent.override_system_prompt(override_prompt).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
|
goose::posthog::set_session_context("cli", session_config.resume);
|
||||||
|
|
||||||
|
let config = Config::global();
|
||||||
|
let agent: Agent = Agent::new();
|
||||||
|
|
||||||
|
if session_config.container.is_some() {
|
||||||
|
agent.set_container(session_config.container.clone()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session_manager = agent.config.session_manager.clone();
|
||||||
|
|
||||||
|
let (saved_provider, saved_model_config) = if session_config.resume {
|
||||||
|
if let Some(ref session_id) = session_config.session_id {
|
||||||
|
match session_manager.get_session(session_id, false).await {
|
||||||
|
Ok(session_data) => (session_data.provider_name, session_data.model_config),
|
||||||
|
Err(_) => (None, None),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let resolved =
|
||||||
|
resolve_provider_and_model(&session_config, config, saved_provider, saved_model_config);
|
||||||
|
|
||||||
|
let recipe = session_config.recipe.as_ref();
|
||||||
|
|
||||||
agent
|
agent
|
||||||
.apply_recipe_components(
|
.apply_recipe_components(
|
||||||
recipe.and_then(|r| r.sub_recipes.clone()),
|
recipe.and_then(|r| r.sub_recipes.clone()),
|
||||||
|
|
@ -407,7 +607,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let new_provider = match create(&provider_name, model_config).await {
|
let new_provider = match create(&resolved.provider_name, resolved.model_config).await {
|
||||||
Ok(provider) => provider,
|
Ok(provider) => provider,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
output::render_error(&format!(
|
output::render_error(&format!(
|
||||||
|
|
@ -430,40 +630,10 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
worker_model
|
worker_model
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("🤖 Using model: {}", model_name);
|
tracing::info!("🤖 Using model: {}", resolved.model_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let session_id: String = if session_config.no_session {
|
let session_id = resolve_session_id(&session_config, &session_manager).await;
|
||||||
let working_dir = std::env::current_dir().expect("Could not get working directory");
|
|
||||||
let session = session_manager
|
|
||||||
.create_session(working_dir, "CLI Session".to_string(), SessionType::Hidden)
|
|
||||||
.await
|
|
||||||
.expect("Could not create session");
|
|
||||||
session.id
|
|
||||||
} else if session_config.resume {
|
|
||||||
if let Some(session_id) = session_config.session_id {
|
|
||||||
match session_manager.get_session(&session_id, false).await {
|
|
||||||
Ok(_) => session_id,
|
|
||||||
Err(_) => {
|
|
||||||
output::render_error(&format!(
|
|
||||||
"Cannot resume session {} - no such session exists",
|
|
||||||
style(&session_id).cyan()
|
|
||||||
));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match session_manager.list_sessions().await {
|
|
||||||
Ok(sessions) if !sessions.is_empty() => sessions[0].id.clone(),
|
|
||||||
_ => {
|
|
||||||
output::render_error("Cannot resume - no previous sessions found");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
session_config.session_id.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
agent
|
agent
|
||||||
.update_provider(new_provider, &session_id)
|
.update_provider(new_provider, &session_id)
|
||||||
|
|
@ -474,96 +644,19 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
});
|
});
|
||||||
|
|
||||||
if session_config.resume {
|
if session_config.resume {
|
||||||
let session = agent
|
handle_resumed_session_workdir(&agent, &session_id, session_config.interactive).await;
|
||||||
.config
|
|
||||||
.session_manager
|
|
||||||
.get_session(&session_id, false)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
output::render_error(&format!("Failed to read session metadata: {}", e));
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
let current_workdir =
|
|
||||||
std::env::current_dir().expect("Failed to get current working directory");
|
|
||||||
if current_workdir != session.working_dir {
|
|
||||||
if session_config.interactive {
|
|
||||||
let change_workdir = cliclack::confirm(format!("{} The original working directory of this session was set to {}. Your current directory is {}. Do you want to switch back to the original working directory?", style("WARNING:").yellow(), style(session.working_dir.display()).cyan(), style(current_workdir.display()).cyan()))
|
|
||||||
.initial_value(true)
|
|
||||||
.interact().expect("Failed to get user input");
|
|
||||||
|
|
||||||
if change_workdir {
|
|
||||||
if !session.working_dir.exists() {
|
|
||||||
output::render_error(&format!(
|
|
||||||
"Cannot switch to original working directory - {} no longer exists",
|
|
||||||
style(session.working_dir.display()).cyan()
|
|
||||||
));
|
|
||||||
} else if let Err(e) = std::env::set_current_dir(&session.working_dir) {
|
|
||||||
output::render_error(&format!(
|
|
||||||
"Failed to switch to original working directory: {}",
|
|
||||||
e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"{}",
|
|
||||||
style(format!(
|
|
||||||
"Warning: Working directory differs from session (current: {}, session: {}). Staying in current directory.",
|
|
||||||
current_workdir.display(),
|
|
||||||
session.working_dir.display()
|
|
||||||
))
|
|
||||||
.yellow()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup extensions for the agent
|
// Extensions are loaded after session creation because we may change directory when resuming
|
||||||
// Extensions need to be added after the session is created because we change directory when resuming a session
|
let agent_ptr = resolve_and_load_extensions(
|
||||||
|
|
||||||
for warning in goose::config::get_warnings() {
|
|
||||||
eprintln!("{}", style(format!("Warning: {}", warning)).yellow());
|
|
||||||
}
|
|
||||||
|
|
||||||
let configured_extensions: Vec<ExtensionConfig> = if session_config.resume {
|
|
||||||
agent
|
|
||||||
.config
|
|
||||||
.session_manager
|
|
||||||
.get_session(&session_id, false)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| EnabledExtensionsState::from_extension_data(&s.extension_data))
|
|
||||||
.map(|state| state.extensions)
|
|
||||||
.unwrap_or_else(get_enabled_extensions)
|
|
||||||
} else if session_config.no_profile {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
resolve_extensions_for_new_session(recipe.and_then(|r| r.extensions.as_deref()), None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let cli_flag_extensions_to_load = parse_cli_flag_extensions(
|
|
||||||
&session_config.extensions,
|
|
||||||
&session_config.streamable_http_extensions,
|
|
||||||
&session_config.builtins,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut extensions_to_load: Vec<(String, ExtensionConfig)> = configured_extensions
|
|
||||||
.iter()
|
|
||||||
.map(|cfg| (cfg.name(), cfg.clone()))
|
|
||||||
.collect();
|
|
||||||
extensions_to_load.extend(cli_flag_extensions_to_load);
|
|
||||||
|
|
||||||
let agent_ptr = load_extensions(
|
|
||||||
agent,
|
agent,
|
||||||
extensions_to_load,
|
&session_config,
|
||||||
Arc::clone(&provider_for_display),
|
recipe,
|
||||||
session_config.interactive,
|
|
||||||
&session_id,
|
&session_id,
|
||||||
|
Arc::clone(&provider_for_display),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Determine editor mode
|
|
||||||
let edit_mode = config
|
let edit_mode = config
|
||||||
.get_param::<String>("EDIT_MODE")
|
.get_param::<String>("EDIT_MODE")
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -590,38 +683,13 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Err(e) = session
|
configure_session_prompts(&session, config, &session_config, &session_id).await;
|
||||||
.agent
|
|
||||||
.persist_extension_state(&session_id.clone())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::warn!("Failed to save extension state: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CLI-specific system prompt extension
|
|
||||||
session
|
|
||||||
.agent
|
|
||||||
.extend_system_prompt(super::prompt::get_cli_prompt())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Some(additional_prompt) = session_config.additional_system_prompt {
|
|
||||||
session.agent.extend_system_prompt(additional_prompt).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only override system prompt if a system override exists
|
|
||||||
let system_prompt_file: Option<String> = config.get_param("GOOSE_SYSTEM_PROMPT_FILE_PATH").ok();
|
|
||||||
if let Some(ref path) = system_prompt_file {
|
|
||||||
let override_prompt =
|
|
||||||
std::fs::read_to_string(path).expect("Failed to read system prompt file");
|
|
||||||
session.agent.override_system_prompt(override_prompt).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display session information unless in quiet mode
|
|
||||||
if !session_config.quiet {
|
if !session_config.quiet {
|
||||||
output::display_session_info(
|
output::display_session_info(
|
||||||
session_config.resume,
|
session_config.resume,
|
||||||
&provider_name,
|
&resolved.provider_name,
|
||||||
&model_name,
|
&resolved.model_name,
|
||||||
&Some(session_id),
|
&Some(session_id),
|
||||||
Some(&provider_for_display),
|
Some(&provider_for_display),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Baseline clippy rules - only fail on NEW violations
|
|
||||||
#
|
|
||||||
# Format: "rule_name|violation_parser"
|
|
||||||
#
|
|
||||||
# Violation parsers (run clippy on your rule to see which fits):
|
|
||||||
# function_name - When spans show: "fn my_function(..."
|
|
||||||
# type_name - When spans show: "struct MyStruct" or "enum MyEnum"
|
|
||||||
# file_only - When spans show file-level issues
|
|
||||||
#
|
|
||||||
# Note: If your rule doesn't fit these parsers, you may need to add a new parser
|
|
||||||
# to the parse_violation() function below
|
|
||||||
#
|
|
||||||
# To add new rules:
|
|
||||||
# 1. Add rule below: "clippy::your_rule|violation_parser"
|
|
||||||
# 2. Generate baseline: ./scripts/clippy-baseline.sh generate clippy::your_rule
|
|
||||||
|
|
||||||
BASELINE_RULES=(
|
|
||||||
"clippy::too_many_lines|function_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
parse_violation() {
|
|
||||||
local rule_code="$1"
|
|
||||||
local violation_parser="$2"
|
|
||||||
|
|
||||||
case "$violation_parser" in
|
|
||||||
"function_name")
|
|
||||||
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
|
||||||
.message.spans[0] as $span |
|
|
||||||
($span.text | map(.text) | map(select(test("\\bfn\\b"))) | first // "") as $line |
|
|
||||||
if $line == "" then empty else "\($span.file_name)::\($line | capture("fn\\s+(?<name>[a-z_][a-z0-9_]*)") | .name)" end'
|
|
||||||
;;
|
|
||||||
"type_name")
|
|
||||||
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
|
||||||
"\(.message.spans[0].file_name)::\(.message.spans[0].text[0].text | split(" ")[1] | split(" ")[0])"'
|
|
||||||
;;
|
|
||||||
"file_only")
|
|
||||||
jq -r 'select(.message.code.code == "'"$rule_code"'") |
|
|
||||||
"\(.message.spans[0].file_name)"'
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown violation parser: $violation_parser" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
get_baseline_file() {
|
|
||||||
local rule_name="$1"
|
|
||||||
local safe_name=$(echo "$rule_name" | sed 's/clippy:://' | sed 's/:/-/g')
|
|
||||||
echo "clippy-baselines/${safe_name}.txt"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
generate_baseline() {
|
|
||||||
local rule_name="$1"
|
|
||||||
|
|
||||||
[[ -z "$rule_name" ]] && { echo "Missing rule name"; return 1; }
|
|
||||||
|
|
||||||
local violation_parser=""
|
|
||||||
for rule in "${BASELINE_RULES[@]}"; do
|
|
||||||
[[ "${rule%|*}" == "$rule_name" ]] && { violation_parser="${rule#*|}"; break; }
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ -z "$violation_parser" ]] && { echo "Unknown rule: $rule_name"; return 1; }
|
|
||||||
|
|
||||||
local baseline_file=$(get_baseline_file "$rule_name")
|
|
||||||
|
|
||||||
cargo clippy --jobs 2 --message-format=json -- -W "$rule_name" | \
|
|
||||||
parse_violation "$rule_name" "$violation_parser" | \
|
|
||||||
sort > "$baseline_file"
|
|
||||||
|
|
||||||
echo "✅ Generated baseline for $rule_name ($(wc -l < "$baseline_file") violations)"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Check a single rule from pre-generated JSON (optimized version)
|
|
||||||
check_rule_from_json() {
|
|
||||||
local temp_json="$1"
|
|
||||||
local rule_name="$2"
|
|
||||||
local violation_parser="$3"
|
|
||||||
local baseline_file="$4"
|
|
||||||
|
|
||||||
echo " → Checking $rule_name"
|
|
||||||
|
|
||||||
if [[ ! -f "$baseline_file" ]]; then
|
|
||||||
echo " ❌ $rule_name: baseline file not found"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local temp_parsed=$(mktemp)
|
|
||||||
cat "$temp_json" | parse_violation "$rule_name" "$violation_parser" | sort > "$temp_parsed"
|
|
||||||
|
|
||||||
local new_violations_file=$(mktemp)
|
|
||||||
diff <(sort "$baseline_file") <(sort "$temp_parsed") | grep "^>" | cut -c3- > "$new_violations_file"
|
|
||||||
|
|
||||||
if [[ -s "$new_violations_file" ]]; then
|
|
||||||
echo " ❌ $rule_name: NEW violations found:"
|
|
||||||
|
|
||||||
while IFS= read -r violation; do
|
|
||||||
# Extract all violations for this rule and find the matching one
|
|
||||||
cat "$temp_json" | jq -c 'select(.message.code.code == "'"$rule_name"'")' 2>/dev/null | while read -r json_line; do
|
|
||||||
parsed_id=$(echo "$json_line" | parse_violation "$rule_name" "$violation_parser")
|
|
||||||
if [[ "$parsed_id" == "$violation" ]]; then
|
|
||||||
echo "$json_line" | jq -r '.message.rendered' | sed 's/^/ /'
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done < "$new_violations_file"
|
|
||||||
|
|
||||||
rm "$temp_parsed" "$new_violations_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm "$new_violations_file"
|
|
||||||
|
|
||||||
echo " ✅ $rule_name: ok"
|
|
||||||
rm "$temp_parsed"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
check_all_baseline_rules() {
|
|
||||||
echo "🔍 Checking baseline clippy rules..."
|
|
||||||
|
|
||||||
local clippy_flags=""
|
|
||||||
for rule in "${BASELINE_RULES[@]}"; do
|
|
||||||
local rule_name="${rule%|*}"
|
|
||||||
clippy_flags="$clippy_flags -W $rule_name"
|
|
||||||
done
|
|
||||||
|
|
||||||
local temp_json=$(mktemp)
|
|
||||||
cargo clippy --jobs 2 --message-format=json -- $clippy_flags | tee "$temp_json"
|
|
||||||
|
|
||||||
local failed_rules=()
|
|
||||||
|
|
||||||
# Check each rule against its baseline
|
|
||||||
for rule in "${BASELINE_RULES[@]}"; do
|
|
||||||
local rule_name="${rule%|*}"
|
|
||||||
local violation_parser="${rule#*|}"
|
|
||||||
local baseline_file=$(get_baseline_file "$rule_name")
|
|
||||||
|
|
||||||
if ! check_rule_from_json "$temp_json" "$rule_name" "$violation_parser" "$baseline_file"; then
|
|
||||||
failed_rules+=("$rule_name")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
rm "$temp_json"
|
|
||||||
|
|
||||||
if [[ ${#failed_rules[@]} -gt 0 ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "❌ Failed baseline checks for: ${failed_rules[*]}"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo "✅ All baseline clippy checks passed!"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "$1" == "generate" ]]; then
|
|
||||||
generate_baseline "$2"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Combined lint script
|
|
||||||
# Runs standard clippy (strict) + baseline clippy rules
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
# Source the baseline functions
|
|
||||||
source "$SCRIPT_DIR/clippy-baseline.sh"
|
|
||||||
|
|
||||||
echo "🔍 Running all clippy checks..."
|
|
||||||
|
|
||||||
FIX_MODE=0
|
|
||||||
[[ "$1" == "--fix" ]] && FIX_MODE=1
|
|
||||||
|
|
||||||
run_clippy() {
|
|
||||||
if [[ "$FIX_MODE" -eq 1 ]]; then
|
|
||||||
cargo fmt
|
|
||||||
cargo clippy --all-targets --jobs 2 \
|
|
||||||
--fix --allow-dirty --allow-staged \
|
|
||||||
-- -D warnings
|
|
||||||
else
|
|
||||||
cargo clippy --all-targets --jobs 2 -- -D warnings
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "$FIX_MODE" -eq 1 ]]; then
|
|
||||||
echo "🛠 Applying fixes..."
|
|
||||||
else
|
|
||||||
echo "🔍 Running clippy..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
run_clippy
|
|
||||||
echo ""
|
|
||||||
check_all_baseline_rules
|
|
||||||
echo ""
|
|
||||||
echo "🔒 Checking for banned TLS crates..."
|
|
||||||
"$SCRIPT_DIR/check-no-native-tls.sh"
|
|
||||||
echo ""
|
|
||||||
echo "✅ Done"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue