Skip to content

Commit e5d66f6

Browse files
committed
Merge remote-tracking branch 'remotes/origin/main'
2 parents d23ffce + 85c7f97 commit e5d66f6

108 files changed

Lines changed: 3618 additions & 11746 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
- Async/await misuse or blocking operations in async contexts
3535
- Improper trait implementations
3636

37-
### No Prerelease Docs
38-
- If the PR contains both code changes to features/functionality AND updates in `/documentation`: Documentation updates must be separated to keep public docs in sync with released versions. Either mark new topics with `unlisted: true` or remove/hide the documentation.
37+
### No Doc Updates with Code Changes
38+
- PRs with code changes shouldn't update `/documentation` - docs deploy on merge, code on release. Use `unlisted: true` or remove/hide docs.
3939

4040
## Project-Specific Context
4141

CUSTOM_DISTROS.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ goose's architecture is designed for extensibility. Organizations can create "re
4646
|---------------|---------------|------------|
4747
| Preconfigure a model/provider | `config.yaml`, `init-config.yaml`, environment variables | Low |
4848
| Add custom AI providers | `crates/goose/src/providers/declarative/` | Low |
49-
| Bundle custom MCP extensions | `config.yaml` extensions section, `ui/desktop/src/built-in-extensions.json` | Medium |
49+
| Bundle custom MCP extensions | `config.yaml` extensions section, `ui/desktop/src/built-in-extensions.json`, `ui/desktop/src/components/settings/extensions/bundled-extensions.json` | Medium |
5050
| Modify system prompts | `crates/goose/src/prompts/` | Low |
5151
| Customize desktop branding | `ui/desktop/` (icons, names, colors) | Medium |
5252
| Build a new UI (web, mobile) | Integrate with `goose-server` REST API | High |
@@ -191,7 +191,11 @@ async def query_data_lake(query: str) -> str:
191191
return results
192192
```
193193

194-
2. **Bundle as a built-in extension** by adding to `ui/desktop/src/built-in-extensions.json`:
194+
2. **Bundle as a built-in extension** by adding to either:
195+
- `ui/desktop/src/built-in-extensions.json` (core built-ins surfaced in extension UI)
196+
- `ui/desktop/src/components/settings/extensions/bundled-extensions.json` (bundled extension catalog in Settings)
197+
198+
Example:
195199

196200
```json
197201
{
@@ -268,6 +272,26 @@ You are an AI assistant called [YourName], created by [YourCompany].
268272
- Component text and labels
269273
- Feature visibility
270274

275+
5. **Align packaging and updater names** when rebranding:
276+
- Update static branding metadata in `ui/desktop/package.json` (`productName`, description) and Linux desktop templates (`ui/desktop/forge.deb.desktop`, `ui/desktop/forge.rpm.desktop`)
277+
278+
- Set build/release environment variables consistently:
279+
- `GITHUB_OWNER` and `GITHUB_REPO` for publisher + updater repository lookup
280+
- `GOOSE_BUNDLE_NAME` for bundle/debug scripts and updater asset naming (defaults to `Goose`)
281+
282+
Example:
283+
284+
```bash
285+
export GITHUB_OWNER="your-org"
286+
export GITHUB_REPO="your-goose-fork"
287+
export GOOSE_BUNDLE_NAME="InsightStream-goose"
288+
```
289+
290+
6. **Use this branding consistency checklist** before release:
291+
- Application metadata (`forge.config.ts`, `package.json`, `index.html`) uses your distro name
292+
- Release artifact names and updater lookup names are consistent
293+
- Desktop launchers (Linux `.desktop` templates) point to the same executable name produced by packaging
294+
271295
### Technical Details
272296

273297
- Electron config: `ui/desktop/forge.config.ts`

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Designed for maximum flexibility, goose works with any LLM and supports multi-mo
3535
- [Diagnostics & Reporting](https://block.github.io/goose/docs/troubleshooting/diagnostics-and-reporting)
3636
- [Known Issues](https://block.github.io/goose/docs/troubleshooting/known-issues)
3737

38-
# a little goose humor 🦢
38+
# a little goose humor 🪿
3939

4040
> Why did the developer choose goose as their AI agent?
4141
>

crates/goose-acp/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ goose = { path = "../goose" }
2323
goose-mcp = { path = "../goose-mcp" }
2424
rmcp = { workspace = true }
2525
sacp = "10.1.0"
26-
agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_model"] }
26+
agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_model", "unstable_session_list"] }
2727
anyhow = { workspace = true }
2828
tokio = { workspace = true }
2929
tokio-util = { workspace = true, features = ["compat", "rt"] }

crates/goose-acp/src/custom_requests.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ pub struct GetSessionResponse {
8282
pub session: serde_json::Value,
8383
}
8484

85-
/// List all sessions.
86-
#[derive(Debug, Serialize, JsonSchema)]
87-
pub struct ListSessionsResponse {
88-
pub sessions: Vec<serde_json::Value>,
89-
}
90-
9185
/// Delete a session.
9286
#[derive(Debug, Deserialize, JsonSchema)]
9387
pub struct DeleteSessionRequest {

crates/goose-acp/src/server.rs

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ use sacp::schema::{
2424
AgentCapabilities, AuthMethod, AuthenticateRequest, AuthenticateResponse, BlobResourceContents,
2525
CancelNotification, Content, ContentBlock, ContentChunk, EmbeddedResource,
2626
EmbeddedResourceResource, ImageContent, InitializeRequest, InitializeResponse,
27-
LoadSessionRequest, LoadSessionResponse, McpCapabilities, McpServer, ModelId, ModelInfo,
28-
NewSessionRequest, NewSessionResponse, PermissionOption, PermissionOptionKind,
29-
PromptCapabilities, PromptRequest, PromptResponse, RequestPermissionOutcome,
30-
RequestPermissionRequest, ResourceLink, SessionId, SessionModelState, SessionNotification,
27+
ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, McpCapabilities, McpServer,
28+
ModelId, ModelInfo, NewSessionRequest, NewSessionResponse, PermissionOption,
29+
PermissionOptionKind, PromptCapabilities, PromptRequest, PromptResponse,
30+
RequestPermissionOutcome, RequestPermissionRequest, ResourceLink, SessionCapabilities,
31+
SessionId, SessionInfo, SessionListCapabilities, SessionModelState, SessionNotification,
3132
SessionUpdate, SetSessionModelRequest, SetSessionModelResponse, StopReason, TextContent,
3233
TextResourceContents, ToolCall, ToolCallContent, ToolCallId, ToolCallLocation, ToolCallStatus,
3334
ToolCallUpdate, ToolCallUpdateFields, ToolKind,
@@ -102,24 +103,34 @@ fn create_tool_location(path: &str, line: Option<u32>) -> ToolCallLocation {
102103
loc
103104
}
104105

106+
fn is_developer_file_tool(tool_name: &str) -> bool {
107+
matches!(tool_name, "write" | "edit")
108+
}
109+
105110
fn extract_tool_locations(
106111
tool_request: &goose::conversation::message::ToolRequest,
107112
tool_response: &goose::conversation::message::ToolResponse,
108113
) -> Vec<ToolCallLocation> {
109114
let mut locations = Vec::new();
110115

111116
if let Ok(tool_call) = &tool_request.tool_call {
112-
if tool_call.name != "developer__text_editor" {
117+
if !is_developer_file_tool(tool_call.name.as_ref()) {
113118
return locations;
114119
}
115120

121+
let tool_name = tool_call.name.as_ref();
116122
let path_str = tool_call
117123
.arguments
118124
.as_ref()
119125
.and_then(|args| args.get("path"))
120126
.and_then(|p| p.as_str());
121127

122128
if let Some(path_str) = path_str {
129+
if matches!(tool_name, "write" | "edit") {
130+
locations.push(create_tool_location(path_str, Some(1)));
131+
return locations;
132+
}
133+
123134
let command = tool_call
124135
.arguments
125136
.as_ref()
@@ -276,20 +287,21 @@ async fn add_extensions(agent: &Agent, extensions: Vec<ExtensionConfig>) {
276287
}
277288
}
278289

279-
async fn build_model_state(
280-
provider: &dyn Provider,
281-
current_model: &str,
282-
) -> Result<SessionModelState, sacp::Error> {
283-
let models = provider.fetch_recommended_models().await.map_err(|e| {
284-
sacp::Error::internal_error().data(format!("Failed to fetch models: {}", e))
285-
})?;
286-
Ok(SessionModelState::new(
290+
async fn build_model_state(provider: &dyn Provider, current_model: &str) -> SessionModelState {
291+
let models = match provider.fetch_recommended_models().await {
292+
Ok(models) => models,
293+
Err(e) => {
294+
warn!(error = %e, "failed to fetch models, model selection will be unavailable");
295+
vec![]
296+
}
297+
};
298+
SessionModelState::new(
287299
ModelId::new(current_model),
288300
models
289301
.iter()
290302
.map(|name| ModelInfo::new(ModelId::new(&**name), &**name))
291303
.collect(),
292-
))
304+
)
293305
}
294306

295307
impl GooseAcpAgent {
@@ -653,6 +665,7 @@ impl GooseAcpAgent {
653665

654666
let capabilities = AgentCapabilities::new()
655667
.load_session(true)
668+
.session_capabilities(SessionCapabilities::new().list(SessionListCapabilities::new()))
656669
.prompt_capabilities(
657670
PromptCapabilities::new()
658671
.image(true)
@@ -728,7 +741,7 @@ impl GooseAcpAgent {
728741
);
729742

730743
let model_state =
731-
build_model_state(&*provider, &provider.get_model_config().model_name).await?;
744+
build_model_state(&*provider, &provider.get_model_config().model_name).await;
732745

733746
Ok(NewSessionResponse::new(SessionId::new(goose_session.id)).models(model_state))
734747
}
@@ -853,7 +866,7 @@ impl GooseAcpAgent {
853866
);
854867

855868
let model_state =
856-
build_model_state(&*provider, &provider.get_model_config().model_name).await?;
869+
build_model_state(&*provider, &provider.get_model_config().model_name).await;
857870

858871
Ok(LoadSessionResponse::new().models(model_state))
859872
}
@@ -1086,14 +1099,15 @@ impl GooseAcpAgent {
10861099
.list_sessions()
10871100
.await
10881101
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
1089-
let sessions_json = sessions
1102+
let session_infos: Vec<SessionInfo> = sessions
10901103
.into_iter()
1091-
.map(|s| serde_json::to_value(&s))
1092-
.collect::<Result<Vec<_>, _>>()
1093-
.map_err(|e| sacp::Error::internal_error().data(e.to_string()))?;
1094-
Ok(ListSessionsResponse {
1095-
sessions: sessions_json,
1096-
})
1104+
.map(|s| {
1105+
SessionInfo::new(SessionId::new(s.id), s.working_dir)
1106+
.title(s.name)
1107+
.updated_at(s.updated_at.to_rfc3339())
1108+
})
1109+
.collect();
1110+
Ok(ListSessionsResponse::new(session_infos))
10971111
}
10981112

10991113
#[custom_method("session/get")]
@@ -1275,6 +1289,14 @@ impl JrMessageHandler for GooseAcpHandler {
12751289
request_cx.respond(json)?;
12761290
Ok(())
12771291
}
1292+
MessageCx::Request(req, request_cx) if req.method == "session/list" => {
1293+
let resp = agent.on_list_sessions().await?;
1294+
let json = serde_json::to_value(resp).map_err(|e| {
1295+
sacp::Error::internal_error().data(e.to_string())
1296+
})?;
1297+
request_cx.respond(json)?;
1298+
Ok(())
1299+
}
12781300
MessageCx::Request(req, request_cx) if req.method.starts_with('_') => {
12791301
match agent.handle_custom_request(&req.method, req.params).await {
12801302
Ok(json) => request_cx.respond(json)?,
@@ -1432,10 +1454,7 @@ print(\"hello, world\")
14321454

14331455
#[test]
14341456
fn test_format_tool_name_with_extension() {
1435-
assert_eq!(
1436-
format_tool_name("developer__text_editor"),
1437-
"Developer: Text Editor"
1438-
);
1457+
assert_eq!(format_tool_name("developer__edit"), "Developer: Edit");
14391458
assert_eq!(
14401459
format_tool_name("platform__manage_extensions"),
14411460
"Platform: Manage Extensions"
@@ -1521,37 +1540,37 @@ print(\"hello, world\")
15211540

15221541
#[test_case(
15231542
"model-a", Ok(vec!["model-a".into(), "model-b".into()])
1524-
=> Ok(SessionModelState::new(
1543+
=> SessionModelState::new(
15251544
ModelId::new("model-a"),
15261545
vec![ModelInfo::new(ModelId::new("model-a"), "model-a"),
15271546
ModelInfo::new(ModelId::new("model-b"), "model-b")],
1528-
))
1547+
)
15291548
; "returns current and available models"
15301549
)]
15311550
#[test_case(
15321551
"model-a", Ok(vec![])
1533-
=> Ok(SessionModelState::new(ModelId::new("model-a"), vec![]))
1552+
=> SessionModelState::new(ModelId::new("model-a"), vec![])
15341553
; "empty model list"
15351554
)]
15361555
#[test_case(
15371556
"model-a", Err(ProviderError::ExecutionError("fail".into()))
1538-
=> matches Err(_)
1539-
; "fetch error propagates"
1557+
=> SessionModelState::new(ModelId::new("model-a"), vec![])
1558+
; "fetch error falls back to current model only"
15401559
)]
15411560
#[test_case(
15421561
"switched-model", Ok(vec!["model-a".into(), "switched-model".into()])
1543-
=> Ok(SessionModelState::new(
1562+
=> SessionModelState::new(
15441563
ModelId::new("switched-model"),
15451564
vec![ModelInfo::new(ModelId::new("model-a"), "model-a"),
15461565
ModelInfo::new(ModelId::new("switched-model"), "switched-model")],
1547-
))
1566+
)
15481567
; "current model reflects switched model"
15491568
)]
15501569
#[tokio::test]
15511570
async fn test_build_model_state(
15521571
current_model: &str,
15531572
models: Result<Vec<String>, ProviderError>,
1554-
) -> Result<SessionModelState, sacp::Error> {
1573+
) -> SessionModelState {
15551574
let provider = MockModelProvider { models };
15561575
build_model_state(&provider, current_model).await
15571576
}

crates/goose-acp/tests/common_tests/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ pub async fn run_prompt_basic<C: Connection>() {
313313
pub async fn run_prompt_codemode<C: Connection>() {
314314
let expected_session_id = ExpectedSessionId::default();
315315
let prompt =
316-
"Search for getCode and textEditor tools. Use them to save the code to /tmp/result.txt.";
316+
"Search for getCode and write tools. Use them to save the code to /tmp/result.txt.";
317317
let mcp = McpFixture::new(Some(expected_session_id.clone())).await;
318318
let openai = OpenAiFixture::new(
319319
vec![
@@ -326,7 +326,7 @@ pub async fn run_prompt_codemode<C: Connection>() {
326326
include_str!("../test_data/openai_builtin_execute.txt"),
327327
),
328328
(
329-
r#"Successfully wrote to /tmp/result.txt"#.into(),
329+
r#"Created /tmp/result.txt"#.into(),
330330
include_str!("../test_data/openai_builtin_final.txt"),
331331
),
332332
],
@@ -352,7 +352,7 @@ pub async fn run_prompt_codemode<C: Connection>() {
352352
}
353353

354354
let result = fs::read_to_string("/tmp/result.txt").unwrap_or_default();
355-
assert_eq!(result, format!("{FAKE_CODE}\n"));
355+
assert_eq!(result, FAKE_CODE);
356356
expected_session_id.assert_matches(&session.session_id().0);
357357
}
358358

0 commit comments

Comments
 (0)